很多人认为 java 冗余,啰嗦。在 Java 8 之前也许的确是这样,但是Java8 14年到现在,Java11马上就要来了,Java已经有了巨大的改变,lambda, Stream,Flow api 等等,Spring5 完全基于Java8 开发。因此现在到了必须学习Java 8新特性的时候。
现在就来详解Java8 最重要的特性,lambda和函数接口,走进函数式编程大门,本片文章主要详解使用,对原理不会深究。
Java8 之前,一般都是通过匿名类实现回调接口。
1 2 3 4 5 6 7 8 9 10 11 public class Hello { public static void main (String[] args) throws Exception { Runnable run = new Runnable() { @Override public void run () { System.out.println("hello world!" ); } }; new Thread(run).start(); } }
看看匿名类生成的文件
1 2 3 4 5 6 7 class Hello $1 implements Runnable { Hello$1 () { } public void run () { System.out.println("hello world!" ); } }
Java8 之后lambda和方法引用很简单美观实现接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Runnable run = () -> System.out.println("hello world!" ); Runnable run1 = Hello::say; Runnable run2 = sayHello("hello world!" ); new Thread(run).start();public static void say () { System.out.println("hello world!" ); } public static Runnable sayHello (String content) { return () -> System.out.println(content); }
我们先来学习一下什么是函数接口,Java最大缺点就是方法(函数)无法独立存在,除了基本类型就只有对象了,不像其他语言可以直接把方法当参数传递(如C#委托),只能通过变通的方式如demo中的匿名内部类,因此Java8引入函数接口来表示方法类型,函数接口概念非常简单,只有一个抽象方法的接口就是函数接口。注:Java8 接口可以有任意多个静态方法和默认方法,Java9 又添加了私有方法,因此接口甚至可以完全取代抽象类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @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); }; } }
Java8 新增 java.util.function 包表示常用的函数接口,我们来看一下 这些接口具体表示的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 大致分为下面4 类 <T> 代表泛型对象,不能为基本类型,Java 这一点还是很蛋疼 1 .Consumer 2 .Function 3 .Predicate 4 .Supplier 5 .UnaryOperator<T> extends Function<T, T> 同理其他的一看就知道是什么了 BiConsumer BiFunction BinaryOperator<T> extends BiFunction<T,T,T> BiPredicate 因为Java泛型不能表示基本类型,所以提供了基本类型的函数接口。 IntBinaryOperator IntConsumer IntFunction IntPredicate IntSupplier IntUnaryOperator To 开头代表返回 ToDoubleBiFunction ToDoubleFunction ToIntBiFunction ToIntFunction ToLongBiFunction ToLongFunction 其他的想 ObjDoubleConsumer, ObjIntConsumer, ObjLongConsumer 之类的也可以望文生义了。这里再一次吐槽Java 基本类型的类类型的差别。所以 Stream 也有了与基本类型对应 IntStream,LongStream,DoubleStream
介绍完函数接口后,我们再来介绍一下 lambda 表达式 和 方法引用。
所谓 lambda 表达式简单理解就是一个匿名方法,可以简化方法的定义。
1 2 3 4 5 6 7 let add = (a, b) => a + b; add(1 , 2 ); IntBinaryOperator intAdds = (a, b) -> a + b; intAdds.applyAsInt(1 , 2 );
看到什么了吗?虽然 lambda 没有 Js 那么灵活。但是 没有lambda 之前,Java 方法只能所属于具体类,不能单独存在(即所谓的方法不是一级公民),方法无法临时创建,也无法传递。而动态语言和其他支持 lambda 的静态语言,就很灵活,因为Java 与时俱进,在Java 8 中支持了此特性。
没有lambda 之前,Java 无法创建局部方法[即在方法中定义方法]想要实现回调方法一般只能定义一个匿名类,生成其对象,然后传入参数,如文章最开始的时候 。Java8 之后 集合框架加入了大量 函数式支持。
1 2 3 4 5 6 7 8 9 10 List<String> list = Arrays.asList("hello" , "world" , "!!!" , null ); list.forEach(s -> System.out.println(s)); list.forEach(s -> { if (s == null ) { System.out.println("字符串为null" ); return ; } System.out.println(s.length()); });
如果每次都要写很长的 lambda,那其实没省效率,而且代码重用率极低。因此,方法引用登场。
1 2 3 4 5 Function<String, Integer> fun = s -> s.length(); Function<String, Integer> fun1 = String::length;
:: 方法引用运算符,标准形式是:类名::方法名 有下面四种形式引用静态方法
Class::staticMethodName 引用静态方法[类名::静态方法名]
obj::instanceMethodName 引用某个对象的实例方法[对象::实例方法名]
ClassName::new 引用构造方法
Type::methodName 引用某个类型的任意对象的实例方法
下面重点介绍一下,尤其是令人困惑第四种。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Function<Object, String> fun = String::valueOf; private void sayHello (String content) { System.out.println(); } Hello hello = new Hello(); Consumer<String> con = hello::sayHello; Consumer<String> con1 = this ::sayHello; Supplier<Hello> supplier = Hello::new ; Hello hello1 = supplier.get(); BiConsumer<Hello, String> biCon = Hello::sayHello; sayHello.accept(hello, "hello" ); public int length () ; Function<String, Integer> length = String::length; int len = length.apply("hello world" );
介绍了lambda和函数接口,方法引用后。学会已经可以大显身手了,但是 lambda 肯定还有更高深的内容等着你,那就是
lambda 组合,lambda 本身还可以和本身组成更复杂的 lambda [依赖于默认方法]。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 Runnable Callable<V> Comparable<T> Comparator<T> Thread.UncaughtExceptionHandler @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; } } Function<Integer, Integer> square = e -> e * e; Function<Integer, Integer> dou = e -> 2 * e; int a = square.compose(dou).apply(3 ); int b = square.andThen(dou).apply(3 ); int c = dou.compose(square).apply(3 ); int d = dou.andThen(square).apply(3 ); (V v) -> apply(before.apply(v)); (T t) -> after.apply(apply(t)); int e = dou.andThen(tripe).compose(square).andThen(square).compose(tripe).andThen(dou).apply(4 );@FunctionalInterface public interface Comparator <T > { int compare (T o1, T o2) ; boolean equals (Object obj) ; ****多个 default method; } @FunctionalInterface interface Compare <T > extends Comparable { int add (T a, T b) ; } List<String> list = new ArrayList<>(Arrays.asList("he" , "b hello" , "a hello world!" , "world" , "hello" , "ha" )); list.sort(Comparator.comparing(String::length).thenComparing(Comparator.naturalOrder()));
Java8 支持 lambda之后, 9,10,11 版本都进行了扩展,虽然 lambda 效率低于匿名类,但是获得更加简洁的写法,更强的语义。Java Collection, Map, Arrays 添加了大量lambda api,大家在写代码的时候学以致用,下面一篇文章将详解Java8新增的 Optional 和 Stream。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 List<Integer> nums = Arrays.asList(1 , 1 , null , 2 , 3 , 4 , null , 5 , 6 , 7 , 8 , 9 , 10 ); int sum = nums.stream().filter(Objects::nonNull).distinct().mapToInt(Integer::intValue).sum();public class Employee { private String name; private String city; private int number; **** } List<Employee> employees = new ArrayList<>(); employees.add(new Employee("张三" , "杭州" , 100 )); employees.add(new Employee("小兰" , "嘉兴" , 85 )); employees.add(new Employee("李四" , "杭州" , 80 )); employees.add(new Employee("王二" , "兰州" , 95 )); Map<String, List<Employee>> result = employees.stream().collect(Collectors.groupingBy(Employee::getCity));