Java8的重要特性之四:默认方法、函数式接口、Lambda表达式和方法引用,之所以将这四个特性拿出来一起记录学习,因为其作用都是为了在接口定义时或者实现时进一步优化编程体验。其中Lambda表达式和方法引用可以看做是匿名内部类的进一步的抽象,合理的使用这些特性可以使编程更加高效,代码更加简洁清晰。
默认方法:
Java的接口用起来很方便,使编程更加易于规范化、格式化,但是也存在一些问题:当修改接口的方法时,所有实现该接口的类都要进行修改,这是很糟糕的体验。于是Java8中引入了默认方法,可以使接口中存在非抽象方法,目的是为了解决接口的修改与现有的实现类不兼容的问题。
关键字:default
默认语法格式如下:
1 | public interface B { |
注意:当B、C两个接口同时存在同名默认方法show(),并且同时被一个类D实现时会报出编译错误:D inherits unrelated defaults for show() from B and C
1 | public interface C{ |
解决方法:必须在实现类D中重写show()方法进行覆盖。
1 | public class D implements B,C{ |
如果希望使用接口中的某个默认方法,可以在重写的方法中使用接口名.super.方法()的方式调用。
1 | public class D implements B,C{ |
在Java8中不但允许接口内实现方法,还允许接口实现静态方法并通过接口名.静态方法来调用。但是接口的实现类和实现类对象不能直接调用接口的静态方法。
1 | public class D implements B,C{//在B接口中加入静态方法static void hello(){} |
Lambda表达式:
基本语法:(params) -> expression; 或者 (params) -> {expression; expression;…};
1 | public class test { |
1 | public class test { |
- Lambda表达式只支持函数式接口,也就是只有一个抽象方法但可以有多个default方法的接口。
- ->Lambda运算符,左边是参数列表,右边是需要执行的表达式语句。
- 当且仅当只有一个参数时可以省略(),当且仅当只有一条语句可以省略{}和return。
- 参数的类型可以不写,Javac编译时会根据上下文推断出参数的类型,即“类型推断”。
- 当有多个参数和多条语句时:
1 | public class test { |
函数式接口:
函数式接口:有且仅有只有一个抽象方法,但可以有多个非抽象方法的接口。
函数式接口可以被隐式的转换为Lambda表达式,并对现有的函数非常友好的支持Lambda表达式。
JDK 1.8 之前已有的函数式接口:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.lang.reflect.InvocationHandler
- java.beans.PropertyChangeListener
- java.awt.event.ActionListener
- javax.swing.event.ChangeListener
JDK 1.8 新增加的函数接口:
- java.util.function
java.util.function包中有很多接口,用来支持函数式编程:
序号 | 接口 | 描述 |
---|---|---|
1 | BiConsumer<T,U> | 代表了一个接受两个输入参数的操作,并且不返回任何结果 |
2 | BiFunction<T,U,R> | 代表了一个接受两个输入参数的方法,并且返回一个结果 |
3 | BinaryOperator |
代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果 |
4 | BiPredicate<T,U> | 代表了一个两个参数的boolean值方法 |
5 | BooleanSupplier | 代表了boolean值结果的提供方 |
6 | Consumer |
代表了接受一个输入参数并且无返回的操作 |
7 | DoubleBinaryOperator | 代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。 |
8 | DoubleConsumer | 代表一个接受double值参数的操作,并且不返回结果。 |
9 | DoubleFunction |
代表接受一个double值参数的方法,并且返回结果 |
10 | DoublePredicate | 代表一个拥有double值参数的boolean值方法 |
11 | DoubleSupplier | 代表一个double值结构的提供方 |
12 | DoubleToIntFunction | 接受一个double类型输入,返回一个int类型结果。 |
13 | DoubleToLongFunction | 接受一个double类型输入,返回一个long类型结果 |
14 | DoubleUnaryOperator | 接受一个参数同为类型double,返回值类型也为double 。 |
15 | Function<T,R> | 接受一个输入参数,返回一个结果。 |
16 | IntBinaryOperator | 接受两个参数同为类型int,返回值类型也为int 。 |
17 | IntConsumer | 接受一个int类型的输入参数,无返回值 。 |
18 | IntFunction |
接受一个int类型输入参数,返回一个结果 。 |
19 | IntPredicate | 接受一个int输入参数,返回一个布尔值的结果。 |
20 | IntSupplier | 无参数,返回一个int类型结果。 |
21 | IntToDoubleFunction | 接受一个int类型输入,返回一个double类型结果 。 |
22 | IntToLongFunction | 接受一个int类型输入,返回一个long类型结果。 |
23 | IntUnaryOperator | 接受一个参数同为类型int,返回值类型也为int 。 |
24 | LongBinaryOperator | 接受两个参数同为类型long,返回值类型也为long。 |
25 | LongConsumer | 接受一个long类型的输入参数,无返回值。 |
26 | LongFunction |
接受一个long类型输入参数,返回一个结果。 |
27 | LongPredicate | R接受一个long输入参数,返回一个布尔值类型结果。 |
28 | LongSupplier | 无参数,返回一个结果long类型的值。 |
29 | LongToDoubleFunction | 接受一个long类型输入,返回一个double类型结果。 |
30 | LongToIntFunction | 接受一个long类型输入,返回一个int类型结果。 |
31 | LongUnaryOperator | 接受一个参数同为类型long,返回值类型也为long。 |
32 | ObjDoubleConsumer |
接受一个object类型和一个double类型的输入参数,无返回值。 |
33 | ObjIntConsumer |
接受一个object类型和一个int类型的输入参数,无返回值。 |
34 | ObjLongConsumer |
接受一个object类型和一个long类型的输入参数,无返回值。 |
35 | Predicate |
接受一个输入参数,返回一个布尔值结果。 |
36 | Supplier |
无参数,返回一个结果。 |
37 | ToDoubleBiFunction<T,U> | 接受两个输入参数,返回一个double类型结果 |
38 | ToDoubleFunction |
接受一个输入参数,返回一个double类型结果 |
39 | ToIntBiFunction<T,U> | 接受两个输入参数,返回一个int类型结果。 |
40 | ToIntFunction |
接受一个输入参数,返回一个int类型结果。 |
41 | ToLongBiFunction<T,U> | 接受两个输入参数,返回一个long类型结果。 |
42 | ToLongFunction |
接受一个输入参数,返回一个long类型结果。 |
43 | UnaryOperator |
接受一个参数为类型T,返回值类型也为T。 |
不难发现,提供的这么多的接口摘要出来无非五种:Consumer(消费型)、Supplier(供给型)、Function(函数型)、Predicate(断言型)、Operator(工具型) 。接下来逐一举例介绍:
Consumer(消费型):
接口方法 void accept(T t):参数类型是T,无返回值
1 | //为了精简代码都使用Lambda表达式实现。 |
1 | public class test { |
Supplier(供给型):
接口方法 T get():无参数,有T类型返回值。
1 | public static void main(String[] args) { |
1 | //生成size大小的ArrayList集合并填充数据 |
Function(函数型):
接口方法R apply(T):有T类型参数,有R类型返回值
1 | public static void main(String[] args) { |
1 | public static String strHandler(String str, Function<String,String> function){ |
Predicate(断言型):
接口方法 boolean test(T t):对T类型参数进行条件筛选操作,返回boolean
1 | public static void main(String[] args) { |
1 | //返回满足条件的新集合 |
XXXOperator:
各种XXXOperator接口和Function使用非常类似。。。其中UnaryOperator就是继承了Function<T,T>。
XXXOperator可以看作是Function接口的特殊形式,即参数和返回值类型一致时的Function接口。
举几个例子:
UnaryOperator,就是一个T类型参数,有T类型返回值的情况下的Function<T,T>。
IntUnaryOperator,就是一个Int类型参数,有Int类型返回值情况下的Function<int,int>。
BinaryOperator,就是有两个T类型参数,有T类型返回值的情况下的Function<T,T,T>。
IntBinaryOperator,就是有就是有两个Int类型参数,有Int类型返回值的情况下的Function<Int,Int,Int>。
同理,以此类推。。。
方法引用:
Lambda表达式的进一步优化,将接口实现的内容封装在一个具体方法里,然后去进行方法引用作为接口的实现。增强方法的可复用性。
基本语法:CLassName::MethodName
1 | public static void main(String[] args) { |
注:sort(T[] a, Comparator c) 该方法中的接口参数Comparator是一个函数式接口。
方法引用也分几种情况:
静态方法引用: ClassName::staticMethodName
实例对象的方法引用: instanceReference::methodName
超类上的实例方法引用: super::methodName
注:通过使用super,可以引用方法的超类版本。 还可以捕获this 指针,this :: equals 等价于lambda表达式 x -> this.equals(x);
类型上的实例方法引用: ClassName::methodName
注:要区别于静态方法引用,实例方法的调用是要依赖于对象去调用的,那么这个对象从何而来呢? 例如:String::toString 等价于lambda表达式 (s) -> s.toString() 方法引用对应Lambda,Lambda的第一个参数会成为调用实例方法的对象。 有时候实例方法时泛型的,方法引用时可以在方法前加上<类型参数>,但大多数情况并不需要手动添加类型参数,编译器往往可以根据上下文推断出类型。
构造方法引用: Class::new
例子:String::new, 等价于lambda表达式 () -> new String()
数组构造方法引用: TypeName[]::new
例子:int[]::new 是一个含有一个参数的构造器引用,这个参数就是数组的长度。等价于lambda表达式 x -> new int[x]。
无论Lambda表达式还是方法引用实质上都是匿名实现了函数式接口,都是对匿名内部类的进一步抽象。