Lambda表达式(也称为闭包)是整个Java 8发行版中最受期待的在Java语言层面上的改变,Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中),或者把代码看成数据。
一个lambda可以由用逗号分隔的参数列表、–>符号与函数体三部分表示。
lambda表达式的一般语法:
(Type1 param1, Type2 param2, ..., TypeN paramN) -> { statment1; statment2; //............. return statmentM; } |
List<String> names = Arrays.asList("d", "b", "c", "a");
Collections.sort(names, (String o1, String o2) -> {
System.out.print(o1);
System.out.print(o2);
return o1.compareTo(o2);
});
System.out.println("\n" + names);
简化版:
1.参数类型省略–绝大多数情况,编译器都可以从上下文环境中推断出lambda表达式的参数类型。这样lambda表达式就变成了:
(param1,param2, ..., paramN) -> { statment1; statment2; //............. return statmentM; } |
Collections.sort(names, (o1, o2) -> {
System.out.print(o1);
System.out.print(o2);
return o1.compareTo(o2);
});
System.out.println("\n" + names);
2. 当lambda表达式的参数个数只有一个,可以省略小括号。lambda表达式简写为:
param1 -> {
|
Arrays.asList( "a", "b", "d" ).forEach( e -> {
System.out.println( e ) ;
});
3. 当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的分号。lambda表达式简化为:
|
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
Collections.sort(names, (o1, o2) -> o1.compareTo(o2));
- Lambda表达式一个常见的用法是取代(某些)匿名内部类
|
有Lambda表达式之后,则可以这样写:
|
详见: 函数式接口
- Lambda表达式的另一个重要用法,是和Stream一起使用
Stream就是一组元素的序列,支持对这些元素进行各种操作,而这些操作是通过Lambda表达式指定的。可以把Stream看作Java Collection的一种视图,就像迭代器是容器的一种视图那样(但Stream不会修改容器中的内容)。Stream仅仅代表着数据流,并没有数据结构,所以他遍历完一次之后便再也无法遍历(这点在编程时候需要注意,不像Collection,遍历多少次里面都还有数据),它的来源可以是Collection、array、io等等。
可以把Stream当成一个高级版本的Iterator。原始版本的Iterator,用户只能一个一个的遍历元素并对其执行某些操作;高级版本的Stream,用户只要给出需要对其包含的元素执行什么操作,比如“过滤掉长度大于10的字符串”、“获取每个字符串的首字母”等,具体这些操作如何应用到每个元素上,就给Stream就好了!
Stream和Collection不同之处:
· 不存储数据。 流不是一个存储元素的数据结构。 它只是传递源(source)的数据。
· 功能性的(Functional in nature)。在流上操作只是产生一个结果,不会修改源。 例如filter只是生成一个筛选后的stream,不会删除源里的元素。
· 延迟搜索。 许多流操作, 如filter, map等,都是延迟执行。 中间操作总是lazy的。
· Stream可能是无界的。 而集合总是有界的(元素数量是有限大小)。 短路操作如limit(n), findFirst()可以在有限的时间内完成在无界的stream
· 可消费的(Consumable)。 不是太好翻译,意思流的元素在流的声明周期内只能访问一次。 再次访问只能再重新从源中生成一个Stream
下面例子展示了Stream的常见用法。
例1
假设需要从一个字符串列表中选出以数字开头的字符串并输出,Java 7之前需要这样写:
List<String> list = Arrays.asList("1one", "two", "three", "4four");
for (String str : list) {
if (Character.isDigit(str.charAt(0))) {
System.out.println(str);
}
}
而Java 8就可以这样写:
List<String> list = Arrays.asList("1one", "two", "three", "4four");
list.stream()// 1.得到容器的Steam
.filter(str -> Character.isDigit(str.charAt(0)))// 2.选出以数字开头的字符串
.forEach(str -> System.out.println(str));// 3.输出字符串
上述代码首先
1. 调用List.stream()
方法得到容器的Stream,
2. 然后调用filter()
方法过滤出以数字开头的字符串,
3. 最后调用forEach()
方法输出结果。
使用Stream有两个明显的好处:
1. 减少了模板代码,只用Lambda表达式指明所需操作,代码语义更加明确、便于阅读。
2. 将外部迭代改成了Stream的内部迭代,方便了JVM本身对迭代过程做优化(比如可以并行迭代)
例2
假设需要从一个字符串列表中,选出所有不以数字开头的字符串,将其转换成大写形式,并把结果放到新的集合当中。Java 8书写的代码如下:
List<String> list = Arrays.asList("1one", "two", "three", "4four");
Set<String> newList =
list.stream()// 1.得到容器的Stream
.filter(str -> !Character.isDigit(str.charAt(0)))// 2.选出不以数字开头的字符串
.map(String::toUpperCase)// 3.转换成大写形式
//.map(str -> str.toUpperCase())
.collect(Collectors.toSet());// 4.生成结果集
System.out.println(newList);
上述代码首先
1. 调用List.stream()
方法得到容器的Stream,
2. 然后调用filter()
方法选出不以数字开头的字符串,
3. 之后调用map()
方法将字符串转换成大写形式,
4. 最后调用collect()
方法将结果转换成Set
。这个例子还向我们展示了方法引用(method references
)以及收集器(Collector
)的用法。
通过这个例子我们看到了Stream链式操作,即多个操作可以连成一串。不用担心这会导致对容器的多次迭代,因为不是每个Stream的操作都会立即执行。Stream的操作分成两类,一类是中间操作(intermediate operations
),另一类是最终操作(terminal operation
),只有最终操作才会导致真正的代码执行,中间操作只会做一些标记,表示需要对Stream进行某种操作。这意味着可以在Stream上通过关联多种操作,但最终只需要一次迭代。
详见: Stream API