在Java8开始引入的语法规则,这个属于基础语法,这个语法可以理解成是一个语法糖,是对匿名内部类的包装实现,这是非常重要的使用,在Java8中许多地方使用了这个特性,这个使用一般不能单独使用,要依靠接口函数来实现,上面说了是匿名内部类的包装实现,这个理解对下面的局部变量的理解的非常重要的。
函数式接口要求这个接口只能有一个必须实现的函数,其他方法要么是默认方法,要么是静态方法,用lambda表达式实现的只会是这个必须实现的函数。
表达式组成由()
标识参数的接收参数的口子,用->
连接函数体{}
;这里有一个特性,当入参只有一个参数的时候,()
可以省略,例如 a->{return a;};如果函数体只有一行,{}
也可以省略,如果还有返回值,修饰词return也可以省略,例如:a-> a。
所在路径:java.util.function
这个接口是以接收T类型的参数,返回R类型的结果,只接受一个参数
@FunctionalInterface public interface Function<T, R> { R apply(T t); default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } static <T> Function<T, T> identity() { return t -> t; } }
由上面的源码,可以知道,这个接口的使用需要实现apply
方法,原来使用的方式:
public class TestFunction { public static void main(String[] args) { Function<String, Integer> function = new Function<String, Integer>() { @Override public Integer apply(String s) { return Integer.valueOf(s); } }; System.out.println(function.apply("123")); } }
使用lambda表达式:
public class TestFunction { public static void main(String[] args) { Function<String, Integer> function = s -> Integer.valueOf(s); System.out.println(function.apply("123")); } }
这样大大简化了我们的写法,同时他们是等价的;
接收一个T类型参数,返回一个布尔型返回值
@FunctionalInterface public interface Predicate<T> { boolean test(T t); default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } default Predicate<T> negate() { return (t) -> !test(t); } default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } static <T> Predicate<T> isEqual(Object targetRef) { return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object); } }
通过源码,这个接口实现的是test
函数;
原来的写法:
public class TestPredicate { public static void main(String[] args) { Predicate<String> predicate = new Predicate<String>() { @Override public boolean test(String s) { return StringUtils.isNotBlank(s); } }; System.out.println(predicate); } }
使用lambda表达式:
public class TestPredicate { public static void main(String[] args) { Predicate<String> predicate = s -> StringUtils.isNotBlank(s); System.out.println(predicate); } }
这就简化了我们的写法,上下两种是等价的,这个方法可以用来校验。
这个接口接收一个T类型的参数,没有返回值:
@FunctionalInterface public interface Consumer<T> { void accept(T t); default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } }
通过上面的源码,要实现的方法是accept
.
原来接口的使用:
public class TestConsumer { public static void main(String[] args) { Consumer<String> consumer = new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }; consumer.accept("测试 consumer"); } }
使用lambda表达式:
public class TestConsumer { public static void main(String[] args) { Consumer<String> consumer = s -> System.out.println(s); consumer.accept("测试 consumer"); } }
这个消费者接口一般用来处理任务,不用返回值。
这是一个无参的接口函数,返回值是T类型。
@FunctionalInterface public interface Supplier<T> { T get(); }
通过源码,这里要实现的是get
方法
使用原来的方法来实现:
public class TestSupplier { public static void main(String[] args) { Supplier<String> supplier = new Supplier<String>() { @Override public String get() { return String.valueOf(Math.random()); } }; System.out.println(supplier.get()); } }
使用lambda表达式:
public class TestSupplier { public static void main(String[] args) { Supplier<String> supplier = () -> String.valueOf(Math.random()); System.out.println(supplier.get()); } }
这些就是四个标准接口函数,在学习Java8的时候这些是基础的。
我们可以根据上面的接口来自己定义自己的函数接口,首先定义一个自定义接口,接口要求有三个泛型,两个参数,一个返回值,效果如下;
@FunctionalInterface interface CustomizeFunction<T, V, R> { R customize(T t, V v); }
自定义接口的使用:
public class TestCustomize { public static void main(String[] args) { CustomizeFunction<Integer,Float, BigDecimal> customizeFunction = (t,v)->{ BigDecimal tt = BigDecimal.valueOf(t); BigDecimal vv = BigDecimal.valueOf(v); return tt.add(vv).setScale(2, RoundingMode.HALF_UP); }; System.out.println(customizeFunction.customize(2,0.2f)); } }
这样就完成了对自定义接口的使用了。
说完了函数式接口,在Java里,可以把这些函数当作参数来传入使用,例如多线的Thread类的使用,例子如下:
public static void main(String[] args) { new Thread(()->{ System.out.println(Thread.currentThread().getName()); }).start(); }
从这里可以看到Thread接收的是一个lambda表达式,这样的表达式是可以当作参数使用的;这里理解也是,比较简单的,上面说到这个表达式是为了处理匿名类的,我们把上面的写法拆开看;
public class test { public static void main(String[] args) { Runnable runnable = () -> { System.out.println(Thread.currentThread().getName()); }; new Thread(runnable).start(); } }
这样就可以看出,本质传递的就是一个对象,但是上面的写法表象就是传递了一个表达式,一个函数。
这里我们使用自定义接口来测试。
public class TestCustomize { public static void main(String[] args) { CustomizeClass<Integer, Float, BigDecimal> integerFloatCustomizeClass = new CustomizeClass<>(); integerFloatCustomizeClass.task((t, v) -> { BigDecimal tt = BigDecimal.valueOf(t); BigDecimal vv = BigDecimal.valueOf(v); return tt.add(vv).setScale(2, RoundingMode.HALF_UP); }, 2, 1.11f); } } @FunctionalInterface interface CustomizeFunction<T, V, R> { R customize(T t, V v); } class CustomizeClass<T, V, R> { void task(CustomizeFunction<T, V, R> c, T t, V v) { System.out.println(c.customize(t, v)); } }
从上面可以知道,定义了一个自定义类,里面有一个方法,这个方法有三个参数,一个是自定义的函数式接口,两个运行参数,这两个参数又是函数式参数的入参,然后调用这个函数式参数的方法,完成任务,打印计算结果。
在Java8中使用流元素对数据进行操作,可以快速完成运算结果,这里要知道一个点,就是操作是对数据计算的,并不会改变数据本身,调用赋值方法除外。下面给个例子:
public class testA { public static void main(String[] args) { List<User> list = Arrays.asList( new User("1", User.SexEnum.Female, 12) , new User("2", User.SexEnum.Female, 22) , new User("3", User.SexEnum.Female, 17) , new User("4", User.SexEnum.Female, 32) , new User("5", User.SexEnum.Female, 45) , new User("6", User.SexEnum.Female, 23) , new User("7", User.SexEnum.Female, 14) , new User("8", User.SexEnum.Female, 11)); list.stream().map(e -> e.setSex(User.SexEnum.Male)).forEach(System.out::println); list.forEach(System.out::println); } }
这里可以对List的数据进行批量赋值,上面我们说了,链式是不能改变数据源的,但是后面却改变了,是不对吗,当然不是,这里要知道,赋值不是流操作导致的,是User的set方法导致的,这个一定要区分出来,否则后面会难以理解一些操作,例如:
public class testA { public static void main(String[] args) { List<User> list = Arrays.asList( new User("1", User.SexEnum.Female, 12) , new User("2", User.SexEnum.Female, 22) , new User("3", User.SexEnum.Female, 17) , new User("4", User.SexEnum.Female, 32) , new User("5", User.SexEnum.Female, 45) , new User("6", User.SexEnum.Female, 23) , new User("7", User.SexEnum.Female, 14) , new User("8", User.SexEnum.Female, 11)); list.stream().filter(e -> e.getAge() >= 18).forEach(e -> System.out.println("stream" + e)); list.forEach(e -> System.out.println("list" + e)); } }
这个操作是通过筛选来处理年龄大于等于18的操作,操作结果:
streamUser{name='2', sex=Female, age=22} streamUser{name='4', sex=Female, age=32} streamUser{name='5', sex=Female, age=45} streamUser{name='6', sex=Female, age=23} listUser{name='1', sex=Female, age=12} listUser{name='2', sex=Female, age=22} listUser{name='3', sex=Female, age=17} listUser{name='4', sex=Female, age=32} listUser{name='5', sex=Female, age=45} listUser{name='6', sex=Female, age=23} listUser{name='7', sex=Female, age=14} listUser{name='8', sex=Female, age=11}
从上面可以知道,数据源没有变化,输出结果是处理后的,这里就要注意,要使用处理后的结果,一定要注意接收。
上面不难看出,这些操作十分类似于SQL语法,所以我们可以通过这个操作来处理流数据,因为优化的缘故,流运算效果要比普通的逻辑写法效率高很多。同时这里也看出来,这里的许多写法使用到了上面的lambda表达式,和函数式参数,同时通过.
来连接的编程方式,被称为链式编程。
下面简单的介绍一些常用的方法。
这两个方法使用的比较高,两个都有遍历的效果,但是也有不同,他们的入参不同导致了性质不同:
这个方法的入参是这样的
<R> Stream<R> map(Function<? super T,? extends R> mapper)
这个不难看出,这个方法是有返回值的,同时他的返回结果是一个流数据。遍历后处理的结果集合会被返回。
这个方法如下:
void forEach(Consumer<? super T> action)
这个可以看出,方法是没有返回值的,同时对处理结果不会收集,使用的是消费者接口。
通过上面可以看出一个有对返回的操作收集结果,一个不会。
这个方法如下:
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
这个方法也会一个一个遍历,同时还会把处理结果返回出来:但是看map会发现函数式参数返回的结果是不同的,一个返回的是元素,一个返回的是集合,表现如下,也就是说map无论返回的是什么都当作一个元素里替换原来的值,flatMap无论返回的是什么,都当做集合来合并原来的集合,然后剔除原来的数。下面一个例子来说明:
public class testB { public static void main(String[] args) { List<Integer> list1 = Arrays.asList(1,2,3,4); System.out.println(JSON.toJSONString(list1.stream().map(e -> { List<Integer> list2 = new ArrayList<>(); list2.add(e); list2.add(e + 10); return list2; }).collect(Collectors.toList()))); System.out.println(JSON.toJSONString(list1.stream().flatMap(e -> { List<Integer> list2 = new ArrayList<>(); list2.add(e); list2.add(e + 10); return list2.stream(); }).collect(Collectors.toList()))); } }
为了好看出关系,这里用JSON来输出:
[[1,11],[2,12],[3,13],[4,14]] [1,11,2,12,3,13,4,14]
这样就好理解了,例如1来说,map是把返回的值作为元素来替换1的,从对象类型理解就是用List替换原来Integer类型的值,但是flatMap是把值插入了原来的位置,是集合的合并。
一个是单纯的替换,一个是数据的扁平化处理。
这个方法是筛选功能:
Stream<T> filter(Predicate<? super T> predicate)
这个方法会遍历流数据,把符合条件的数据组建成新的集合,这个入参的断言接口函数;
public class testB { public static void main(String[] args) { List<Integer> list1 = Arrays.asList(1,2,3,4); System.out.println(JSON.toJSONString(list1.stream().filter(e->e>2).collect(Collectors.toList()))); } }
返回结果:
[3,4]
方法如下,是限制数量的函数,类似于mysql的limit,但是这个参数只有一个参数:
Stream<T> limit(long maxSize)
使用方法:
public class testB { public static void main(String[] args) { List<Integer> list1 = Arrays.asList(1,2,3,4); System.out.println(JSON.toJSONString(list1.stream().limit(2).collect(Collectors.toList()))); } }
输出结果:
[1,2]
这个方法用来排序使用的。无参以自然排序,有参的可以使用比较大小的参数来比较
Stream<T> sorted() Stream<T> sorted(Comparator<? super T> comparator)
案例如下:
public class testB { public static void main(String[] args) { List<Integer> list1 = Arrays.asList(1,5,2,6,3,4); System.out.println(JSON.toJSONString(list1.stream().sorted().collect(Collectors.toList()))); System.out.println(JSON.toJSONString(list1.stream().sorted((e1,e2)->e2.compareTo(e1)).collect(Collectors.toList()))); } }
运行结果:
[1,2,3,4,5,6] [6,5,4,3,2,1]
但是推荐使用Comparator的方法实现,这个也是推荐的方法:
public class testB { public static void main(String[] args) { List<Integer> list1 = Arrays.asList(1,5,2,6,3,4); System.out.println(JSON.toJSONString(list1.stream().sorted(Comparator.naturalOrder()).collect(Collectors.toList()))); System.out.println(JSON.toJSONString(list1.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList()))); } }
这样也可以实现,同时推荐使用这个方法,实现正反排序。当然自定义也是可以的,例如先排序奇数再排序偶数,这就要自定义了。
public class testB { public static void main(String[] args) { List<Integer> list1 = Arrays.asList(1,5,2,6,3,4); System.out.println(JSON.toJSONString(list1.stream().sorted((e1,e2)->{ Integer ee1; Integer ee2; if (e1%2==0){ ee1 = e1+10; }else { ee1 = e1- 10; } if (e2%2==0){ ee2 = e2+10; }else { ee2 = e2- 10; } return ee1.compareTo(ee2); }).collect(Collectors.toList()))); } }
结果如下:
[1,3,5,2,4,6]
其他的高级使用就不去一个个介绍了。
有一组用户信息,先男女性别互换,获取年龄大于18岁的,按年龄排序倒序,按性别分组;
测试代码:
public class testA { public static void main(String[] args) { List<User> list = Arrays.asList( new User("1", User.SexEnum.Female, 12) , new User("2", User.SexEnum.Female, 22) , new User("3", User.SexEnum.Female, 17) , new User("4", User.SexEnum.Male, 32) , new User("5", User.SexEnum.Female, 45) , new User("6", User.SexEnum.Male, 23) , new User("7", User.SexEnum.Male, 14) , new User("8", User.SexEnum.Male, 11)); System.out.println(JSON.toJSONString( list.stream().map(e->e.setSex(User.SexEnum.Female.equals(e.getSex())?User.SexEnum.Male:User.SexEnum.Female)) .filter(e->e.getAge()>18) .sorted((e1,e2)->e2.getAge().compareTo(e1.getAge())) .collect(Collectors.groupingBy(User::getSex)) )); } }
测试结果如下:
{ "Male": [ { "age": 45, "name": "5", "sex": "Male" }, { "age": 22, "name": "2", "sex": "Male" } ], "Female": [ { "age": 32, "name": "4", "sex": "Female" }, { "age": 23, "name": "6", "sex": "Female" } ] }
这样就完成了案例的测试了。在Java8里,这样的链式计算使用是非常频繁的,也是一种非常基础的使用。