Java 8 与 Scala
在这里必须要提到一位大牛:Martin Odersky。他是一位编译器以及编程的狂热爱好者,JDK5.0和JDK8.0的编译器便是Martin Odersky完成的。他创造了两种编程语言(Pizza & Scala),并将它们的一部分特性带入到了Java当中,推动了Java的进化与发展。
三个单词表达自己最中意的Java8特性,那就是:Lambda,Stream,Optional。
Lambda表达式
Lambda表达式简化了匿名类的方法重写的繁杂声明,并且可以根据接口的泛型自动推断Lambda表达式中的参数类型。它的引入为Java的函数式编程打下了基础。
在Java8之前,如果要用匿名类直接实现一个接口的方法,则需要这样:
Consumer<String> stringConsumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("Hello,"+s);
}
};
在引入了Lambda表达式之后,一行即可搞定:
Consumer<String> consumer = str -> System.out.println("Hello,"+str);
函数式接口
Java中的函数式编程是基于接口去实现的。Lambda语法实际上就是简化了对抽象方法的实现。可这又带来了一个问题:如果一个接口声明了多个抽象方法,那么Lambda语法应该实现哪一个呢?
如果Lambda表达式可以通过参数列表自动判断我希望重写哪个抽象方法,那一定很酷,可是目前它并不支持这么做。也就是说Lambda表达式仅可用于只声明了一个抽象函数的接口。
Java8使用注解@FunctionalInterface来对想要用于函数式编程的接口做约束:该接口的抽象方法有且只有一个,否则报出编译错误。
Java8 提供了4种核心类型的函数式接口:
函数式接口 | 参数类型 | 返回类型 | 内部方法 | 特征 |
---|---|---|---|---|
Consumer<T> | T | void | void accept(T t) | 只消费,不返回 |
Supplier<T> | void | T | T get() | 只返回,不消费 |
Function<T,R> | T | R | R apply(T t) | 提供T,返回R |
Predicate<T> | T | Boolean | Boolean test(T t) | 提供T,作检验 |
方法引用
有些情况下,某个Lambda表达式纯粹是简单地调用了其它类的方法,而没有其它多余的动作。
比如上一段的Lambda表达式如果变为了这个样子:
Consumer<String> consumer = str -> System.out.println(str);
对于接收到的str
变量,该函数除了原封不动地再将它抛给了System.out.println
方法,便再也没有额外操作了。因此,干脆连()->{}
格式也省略掉,直接声明该函数引用自System.out 的println
方法:
Consumer<String> consumer1 = System.out::println;
这种“偷懒”的引用是有条件的:被引用的方法和你要实现的抽象方法是“同调”的。即它们的参数类型,返回的数据类型完全一致,只不过具体的执行流程被“调包”为了被引用的方法。
如果引用的是个很繁琐的”套娃函数”也没有关系。只要保证最外层函数的参数列表,最终返回的数据类型与抽象函数的参数类型,返回类型保持一致也无伤大雅。
Java中没有哪个方法是脱离于Object而独立存在,因此要声明被引用的方法源自哪里。格式为
Source::method
。这里的 来源可以是类,或者实例,或者是构造器。方法可以是 静态方法,也可以是实例方法。
方法引用可以分为以下四类:
类型 | 语法 | Lambda表达式写法 | 注释 |
---|---|---|---|
静态方法引用 | Clazz::Method | arg -> Clazz.Method(arg) | Method是静态方法 |
实例方法引用 | inst::method | arg -> inst.method(arg) | method是实例方法 |
对象方法引用 | Clazz::method | (inst,arg) -> inst.method(arg) | inst是Clazz的一个实例 |
构造器引用 | Clazz::new | (args) -> new Clazz(args) | 调用的构造器取决于上下文 |
其中,对象方法引用可能稍稍费解一些。当在以下情境时,可以使用对象方法引用来简化:
Lambda参数列表中的前一个参数用于调用它的一个实例方法,而后一个参数是该实例方法的参数。
假设有一个这样的场景:一个工厂类Factory
,有一个公开的方法produce(Properties prop)
。它需要一个配置类Properties
做参数,返回一个产品Product
类实例。现在希望用Lambda表达式来描述这个方法:
Function2<Factory,Properties,Product> produce = (factory,prop)->factory.produce(prop);
实际上这个三元函数引用的是Factory
类的produce
实例方法,并希望向该方法传入Properties
对象,并返回一个Product
对象。因此这个Lambda表达式还可以改成:
Function2<Factory,Properties,Product> produce = Factory::produce;
对象方法引用可用在简化Comparator<E>
接口的声明。比较器的功能实现实际上是调用了一个对象(该对象的类实现了Comparable<E>
接口)的compareTo
方法,该方法接收外部的另一个同类对象,并通过返回正负值来实现大小比较。
Comparator<Product> comparator = Product::compareTo;
products.sort(Product::compareTo);
至于构造器引用,默认使用无参构造函数。如果这个函数式接口可提供参数列表,则编译器会使用参数列表相匹配的构造器。
//调用的是Product()
Supplier<Product> productSupplier = Product::new
//调用的是Product(Integer product_id)
Function<Integer,Product> productFunction = Product::new;