Java8的最大变化是引入了Lambda表达式,Lambda表达式更像是一个匿名方法,它没有名称,但有参数列表,函数主体,返回类型,可能还会有一个可以抛出异常的列表,增加Lambda表达式是为了让开发者能够编写处理批量数据的并行类库。面向对象编程是对数据进行抽象,函数式编程是对行为抽象。Lambda表达式新增了操作符 “‐>”。
一.Lambda表达式语法举例
1.()->{} 等价于 public void run(){}
2.()->”red” 没有参数,返回String表达式
3.()->{return “red”;} 没有参数,显示返回String
4.(Integer i)->{return “red”+i;} 有参数
5.(String s)->”red” 等价于 (String s)->{return “red”;}
6.(Listlist)->list.isEmpty() 布尔表达式
对应的函数式接口:Predicate<List>
7.()->new Apple(3) 创建对象
对应的函数式接口:Supplier
8.(Apple a)->{system.out.print(a.getColor());} 消费一个对象
对应的函数式接口:Consumer
9.(String s)->s.length() 从一个对象中选取
对应的函数式接口:Function<String,Integer>
10.(int a,int b)->a+b 组合两个值
对应的函数式接口:IntBinaryOperator
11.(Apple a,Apple b)->a.getWeight().compareTo(b.getWeight()) 比较两个对象
对应的函数式接口:Comparator
12.
二.函数式接口
函数式接口就是只定义一个抽象方法的接口,用作Lambda表达式的类型,只有在接受函数式接口的地方才可以使用Lambda表达式。函数接口可以接受两个参数,并返回一个值,还可以使用泛型。接口现在还可以拥有默认方法,即在类没有对方法进行实现时,其主体对方法提供默认实现方法。哪怕有很多默认方法,只要接口只定义了一个抽象方法,它仍然是一个函数式接口。上面举例中列出了一些函数式接口。在jdk 1.8的java.util.function包里提供了一组核心函数接口。
此外,Java 8 还内置了四大核心函数式接口:
其他的一些接口,比如为了避免装箱操作,对Predicate等通用函数式接口的原始类型的特化,IntPredicate,IntToLongFunction等,这些通用函数式接口都有几个可以用来结合Lambda表达式的默认方法:
我们可以在任意函数式接口上使用 @FunctionalInterface 注解来检查它是否是一个函数接口,如果接口不符合函数接口的定义规范,编译会报错。任何函数式接口都不允许抛出受检异常(checked exception),如果需要Lambda表达式抛出异常,有两个方法:定义一个自己的函数式接口,并声明受检异常;把Lambda包在一个try/catch块中。
三.类型检查,类型推断
Javac可以根据程序的上下文在后台推断出参数的类型,所以Lambda表达式中的参数可以不用显示的指定类型(当然也可以指定类型)。这跟null类似,只有将null赋给一个变量,才能知道它的类型。
首先看一下Java 7中引入的目标类型推断,即菱形操作符。
在Java 7 中变量list使用了菱形操作符,不用明确声明泛型类型,编译器就可以自己推断出来,而Lambda表达式中的类型推断则是对它的扩展,可以省略表达式中的所有参数类型,但程序依然要经过类型检查来保证运行的安全性,因为编译器可以根据Lambda表达式的上下文信息推断出正确的参数类型。Lambda表达式可以从赋值的上下文,方法调用的上下文(参数和返回值)以及类型转换的上下文中获得目标类型,可以利用目标类型来检查一个Lambda表达式是否可以用于某个特定的上下文,还可以推断Lambda参数的类型。
这个例子中,因为目标类型Object不是一个函数式接口,所以报错,改为Runnable编译就会通过。
1.Predicate接口
从java.util.function包中查看Predicate接口的源码,发现它是只有一个泛型类型的参数,Lambda表达式实现了Predicate接口,因此x被推断为Integer类型,编译器还可以检查Lambda表达式的返回值是不是boolean类型。但是如果去掉泛型Integer,类型推断系统将无法推断出参数类型,会出现编译报错。
2.BinaryOperator接口
该接口接受两个参数,返回一个值。
四.限制
匿名内部类是为了将代码作为数据传递,但是写法不够简便,可读性差。需要引用它所在方法里的变量时,该变量要声明为final,而在Java8中,既可以引用final声明的变量,也可以引用非final声明的变量,但是非final声明的变量只能赋值一次,否则编译器会报错。
局部变量的限制:实例变量存储在堆中,局部变量保存在栈上,如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程,可能会在分配该变量的线程将这个变量回收之后,去访问该变量。因此,java在访问自由局部变量时,实际上是在访问它的副本,而不是原始变量。如果局部变量仅赋值一次就没什么区别了;这一限制不鼓励你使用改变外部变量的典型命令式编程模式,这种模式会阻碍并行处理。
五.方法引用和构造器引用
-
对象 ::实例方法
对象和方法都可以自定义。
-
类::静态方法
下面两个代码是等价的:
-
类::实例方法
下面两个代码是等价的:
4.构造方法引用
六.实战
1.传递代码
2.匿名内部类
3.Lambda表达式
4.使用方法引用list.sort(comparing(Banana::getWeight));