长大后想做什么?做回小孩!

0%

Java8特性(一)

默认方法&Lambda表达式&函数式接口&方法引用

Java8的重要特性之四:默认方法、函数式接口、Lambda表达式和方法引用,之所以将这四个特性拿出来一起记录学习,因为其作用都是为了在接口定义时或者实现时进一步优化编程体验。其中Lambda表达式和方法引用可以看做是匿名内部类的进一步的抽象,合理的使用这些特性可以使编程更加高效,代码更加简洁清晰。

默认方法:

Java的接口用起来很方便,使编程更加易于规范化、格式化,但是也存在一些问题:当修改接口的方法时,所有实现该接口的类都要进行修改,这是很糟糕的体验。于是Java8中引入了默认方法,可以使接口中存在非抽象方法,目的是为了解决接口的修改与现有的实现类不兼容的问题。

关键字:default

默认语法格式如下:

1
2
3
4
5
public interface B {
public default void show(){
System.out.println("I'm B...");
}
}

注意:当B、C两个接口同时存在同名默认方法show(),并且同时被一个类D实现时会报出编译错误:D inherits unrelated defaults for show() from B and C

1
2
3
4
5
public interface C{
public default void show(){
System.out.println("I'm C...");
}
}

解决方法:必须在实现类D中重写show()方法进行覆盖。

1
2
3
4
5
public class D implements B,C{
public void show(){
System.out.println("I'm D...");
}
}

如果希望使用接口中的某个默认方法,可以在重写的方法中使用接口名.super.方法()的方式调用。

1
2
3
4
5
public class D implements B,C{
public void show(){
B.super.show();
}
}

在Java8中不但允许接口内实现方法,还允许接口实现静态方法并通过接口名.静态方法来调用。但是接口的实现类和实现类对象不能直接调用接口的静态方法。

1
2
3
4
5
6
7
8
9
10
public class D implements B,C{//在B接口中加入静态方法static  void hello(){}
public void show(){
B.super.show();
B.hello();
C.super.show();
System.out.println("I'm D...");
}//运行结果: I'm B...
} // HelloWorld!
// I'm C...
// I'm D...

Lambda表达式:

基本语法:(params) -> expression; 或者 (params) -> {expression; expression;…};

1
2
3
4
5
6
7
8
9
10
11
12
13
public class test {
public static void main(String[] args) {
new Runnable(){//匿名内部类实现
@Override
public void run(){
System.out.println("run...");
}
}.run();
//改用Lambda表达式
Runnable runnable = ()-> System.out.println("run...");
runnable.run();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class test {
public static void main(String[] args) {
String[] atp = {"Rafael Nadal", "Novak Djokovic",
"Stanislas Wawrinka",
"David Ferrer","Roger Federer",
"Andy Murray","Tomas Berdych",
"Juan Martin Del Potro"};
List<String> players = Arrays.asList(atp);
//Java5的增强for
for (String player : players) {
System.out.println(player);
}
//Java8的Lambda表达式
players.forEach((player)-> System.out.println(player));
}
}
  1. Lambda表达式只支持函数式接口,也就是只有一个抽象方法但可以有多个default方法的接口。
  2. ->Lambda运算符,左边是参数列表,右边是需要执行的表达式语句。
  3. 当且仅当只有一个参数时可以省略(),当且仅当只有一条语句可以省略{}和return
  4. 参数的类型可以不写,Javac编译时会根据上下文推断出参数的类型,即“类型推断”。
  5. 当有多个参数和多条语句时:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class test {
public static void main(String[] args) {
//匿名内部类
Comparator<Integer> comparator =new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println("函数式接口...");
return Integer.compare(o1,o2);
}
};
//Lambda表达式
Comparator<Integer> comparator1 = (x,y) -> {
System.out.println("函数式接口...");
return Integer.compare(x,y);
};
}
}

函数式接口:

函数式接口:有且仅有只有一个抽象方法,但可以有多个非抽象方法的接口。

函数式接口可以被隐式的转换为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
2
3
4
5
6
7
8
9
10
11
12
//为了精简代码都使用Lambda表达式实现。
public static void main(String[] args) {
//接受一个T类型参数可以泛型指定,无返回值
Consumer<String> Consumer = (name) -> System.out.println(name);
Consumer.accept("张三");
//接受T类型、U类型两个参数可以分别用泛型指定,无返回值
BiConsumer<String,Integer> biConsumer = (name,age)-> System.out.println(name+age);
biConsumer.accept("zhangsan",18);
//接受Double类型的参数已指定,无返回值
DoubleConsumer doubleConsumer = (money)-> System.out.println(money);
doubleConsumer.accept(24.0);
}
1
2
3
4
5
6
7
8
9
public class test {
public static void happy(double money, Consumer<Double> consumer){
consumer.accept(money);
}
public static void main(String[] args) {
//就像匿名内部类做参数一样
happy(1000,(e)-> System.out.println("happy消费了"+ e +"元"));
}
}

Supplier(供给型):

接口方法 T get():无参数,有T类型返回值。

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
//无参数,返回String类型返回值,因为只有一条语句所以省略return
Supplier<String> supplier = ()->"I'm Supplier";
System.out.println(supplier.get());
//无参数,返回Boolean类型返回值
BooleanSupplier booleanSupplier = ()->{
if (2>1)return true;
return false;
};
System.out.println(booleanSupplier.getAsBoolean());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//生成size大小的ArrayList集合并填充数据
public static List<Integer> getList(int size, Supplier<Integer> supplier){
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i <size ; i++) {
Integer num = supplier.get();
list.add(num);
}
return list;
}
public static void main(String[] args) {
List<Integer> list = getList(10, ()->(int)(Math.random()*20));
for (Integer integer : list) {
System.out.println(integer);
}
}

Function(函数型):

接口方法R apply(T):有T类型参数,有R类型返回值

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
//有T类型参数,有R类型返回值
Function<String, String> function = (str) -> "I'm " + str;
System.out.println(function.apply("Function"));
//接受两个参数T、U类型,有R类型返回值
BiFunction<String,String,String> stringStringStringBiFunction = (str1,str2)->str1+str2;
System.out.println(stringStringStringBiFunction.apply("I''m","BiFunction"));
//接受Long类型参数,返回Double类型值
LongToDoubleFunction longToDoubleFunction = (tlong)->(double) (tlong-10);
System.out.println(longToDoubleFunction.applyAsDouble(123456789));
}
1
2
3
4
5
6
7
public static String strHandler(String str, Function<String,String> function){
return function.apply(str);
}
public static void main(String[] args) {
//去除首尾空格
String s = strHandler("\t\t123456 789", (str) -> (str + "->no blank space ").trim());
}

Predicate(断言型):

接口方法 boolean test(T t):对T类型参数进行条件筛选操作,返回boolean

1
2
3
4
5
6
7
8
public static void main(String[] args) {
//一个Integer类型参数,有boolean类型返回值
Predicate<Integer> predicate =(num)->num>10;
System.out.println(predicate.test(11));
//两个参数,有boolean类型返回值
BiPredicate<Double,Double> biPredicate = (num1,num2)->num1>num2;
System.out.println(biPredicate.test((double)12,13.0));
}
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
//返回满足条件的新集合
public static List<String> getNewList(List<String> strList, Predicate<String> predicate){
List<String> newlist = new ArrayList<>();
for (String s : strList) {
if (predicate.test(s))
newlist.add(s);
}
return newlist;
}
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("d34");
list.add("d8wq9");
list.add("c9");
list.add("rnglj");
//返回s.length()>3的元素的新集合
List<String> newList = getNewList(list,(s)->s.length()>3);
for (String s : newList) {
System.out.println(s);
}
//返回以9结尾元素的新集合
List<String> newList2 = getNewList(list,(s)->s.endsWith("9"));
for (String s : newList2) {
System.out.println(s);
}
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
String[] strings = {"c","D","e","de","qw","Aeq"};
//匿名内部类方式实现接口
Arrays.sort(strings, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareToIgnoreCase(o2);
}
});
//Lambda表达式中只是调用了一个现有的方法
Arrays.sort(strings,(s1,s2)->s1.compareToIgnoreCase(s2));
//可以用方法引用的方式实现匿名内部类
Arrays.sort(strings,String::compareToIgnoreCase);
}

sort(T[] a, Comparator c) 该方法中的接口参数Comparator是一个函数式接口。

方法引用也分几种情况:

  1. 静态方法引用: ClassName::staticMethodName

  2. 实例对象的方法引用: instanceReference::methodName

  3. 超类上的实例方法引用: super::methodName

    注:通过使用super,可以引用方法的超类版本。 还可以捕获this 指针,this :: equals 等价于lambda表达式 x -> this.equals(x);

  4. 类型上的实例方法引用: ClassName::methodName

注:要区别于静态方法引用,实例方法的调用是要依赖于对象去调用的,那么这个对象从何而来呢? 例如:String::toString 等价于lambda表达式 (s) -> s.toString() 方法引用对应Lambda,Lambda的第一个参数会成为调用实例方法的对象。 有时候实例方法时泛型的,方法引用时可以在方法前加上<类型参数>,但大多数情况并不需要手动添加类型参数,编译器往往可以根据上下文推断出类型。

  1. 构造方法引用: Class::new

    例子:String::new, 等价于lambda表达式 () -> new String()

  2. 数组构造方法引用: TypeName[]::new

    例子:int[]::new 是一个含有一个参数的构造器引用,这个参数就是数组的长度。等价于lambda表达式 x -> new int[x]。

无论Lambda表达式还是方法引用实质上都是匿名实现了函数式接口,都是对匿名内部类的进一步抽象。