Java8新特性之Lambda表达式总结

函数式接口

Lambda不是所有方法都能使用的,得满足一定的条件,比如得满足函数式接口。
小小牛博客

对于只有一个抽象方法的接口,需要这种借口的对象时,就可以提供一个Lambda表达式,这种接口称为函数式接口,也就是只有一个抽象方法的接口(不包括继承的)

函数式接口其实本质上还是一个接口,但是它是一种特殊的接口:SAM类型的接口(Single Abstract Method)。定义了这种类型的接口,使得以其为参数的方法,可以在调用时,使用一个lambda表达式作为参数。从另一个方面说,一旦我们调用某方法,可以传入lambda表达式作为参数,则这个方法的参数类型,必定是一个函数式的接口,(这个类型可以使用@FunctionalInterface进行修饰,好处是,如果无意中增加了方法或者不符合lambda接口规范,编译器会提示错误,另外javadoc中会指出是一个函数式接口,推荐使用注解

  从SAM原则上讲,这个接口中, 只能有一个函数需要被实现 ,但是也可以有如下例外:

    1. 默认方法与静态方法并不影响函数式接口的契约,可以任意使用,即

      函数式接口中可以有静态方法,一个或者多个静态方法不会影响SAM接口成为函数式接口,并且静态方法可以提供方法实现

      可以由 default 修饰的默认方法方法,这个关键字是Java8中新增的,为的目的就是使得某一些接口,原则上只有一个方法被实现,但是由于历史原因,不得不加入一些方法来兼容整个JDK中的API,所以就需要使用default关键字来定义这样的方法

Lambda语法

一例胜千言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 // =====Lambda
//这里省略list的构造
List<String> names = ...;
Collections.sort(names, (o1, o2) -> o1.compareTo(o2));

//===原始形态

//这里省略list的构造
List<String> names = ...;
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});

完整一般语法:

1
2
3
4
5
6
	(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
statment1;
statment2;
//.............
return statmentM;
}

  1. 参数类型省略–绝大多数情况,编译器都可以从上下文环境中推断出lambda表达式的参数类型。这样lambda表达式就变成了:
1
2
3
4
5
6
(param1,param2, ..., paramN) -> {
statment1;
statment2;
//.............
return statmentM;
}
  1. 当lambda表达式的参数个数只有一个,可以省略小括号。lambda表达式简写为:
1
2
3
4
5
6
param1 -> {
statment1;
statment2;
//.............
return statmentM;
}
  1. 当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的分号。
    1
    param1 -> statme

方法的引用

个人理解:上面的Lambda有点像是实现匿名内部类的一个简化版本,我们需要传入代码以实现函数式接口内的那个抽象方法,方法引用有点像是引用其他已经实现好的方法。我们就不用再实现了(当然得看引用的方法满不满足需求)

方法引用:

objectName::instanceMethod (对象.方法名)
ClassName::staticMethod (类名.静态方法)
ClassName::instanceMethod

前两种方式类似,等同于把lambda表达式的参数直接当成instanceMethod|staticMethod的参数来调用。比如System.out::println等同于x->System.out.println(x);Math::max等同于(x, y)->Math.max(x,y)。

最后一种方式(类名+非静态方法),等同于把lambda表达式的第一个参数当成instanceMethod的目标对象,其他剩余参数当成该方法的参数。比如String::toLowerCase等同于x->x.toLowerCase()。

构造器引用

构造器引用语法如下:构造器引用和方法引用有点类似,只不过方法名为new,如:ClassName::new,把lambda表达式的参数当成ClassName构造器的参数 。例如BigDecimal::new等同于x->new BigDecimal(x)。

可以用数组类型建立构造器引用:
int[] :: new
他有一个参数即数组长度
等价于Lambda : x -> new int[x]

变量的作用域

Lambda表达式也可访问外部的变量(参考内部类)

lambda表达式的三个重要组成部分:

  • 输入参数
  • 可执行语句
  • 自由变量的值,这是指非参数而且不在代码中定义的变量
1
2
3
4
String[] array = {"a", "b", "c"};
for(Integer i : Lists.newArrayList(1,2,3)){
Stream.of(array).map(item -> Strings.padEnd(item, i, '@')).forEach(System.out::println);
}

上面的这个例子中,map中的lambda表达式访问外部变量Integer i。

不过lambda表达式访问外部变量有一个非常重要的限制:变量不可变(java是值传递,也就是说基本类型值不能变,只是引用不可变,而不是真正的不可变)可以参考内部类引用外部变量也是必须用final声明,不过在Lambda中编译器会隐式的当做final处理,不加fina也可以

1
2
3
4
5

String[] array = {"a", "b", "c"};
for(int i = 1; i<4; i++){
Stream.of(array).map(item -> Strings.padEnd(item, i, '@')).forEach(System.out::println);
}

上面这个要报错

Lambda中的this

在lambda中,this不是指向lambda表达式产生的那个SAM对象,而是声明它的外部对象。

处理(接收)Lambda表达式

上面我们已经了解和使用了Lambda表达式,下面讨论下怎么接收处理Lambda表达式

使用Lambda的重点是延迟执行,像需要多次运行的场景就适合使用,jdk提供了很多常用的函数式接口,所以在自定义方法时可以使用这些函数式接口,不用自己再创建接口。还有一些基本类型的函数式接口,减少int double等的自动开装箱。