Lambda在集合中的使用
列表的遍历
提起对于集合的遍历,恐怕下面的这种方式已经是一种思维定式了吧:
final List<String> friends = Arrays.asList("Brian", "Nate", "Neal", "Raju", "Sara", "Scott");
for(int i = 0; i < friends.size(); i++) {
System.out.println(friends.get(i));
}
但是仔细想想,以上的代码似乎出现了过多的细节,比如循环变量i的出现。在做简单的遍历操作时,循环变量实际上是不必要的,只有在对某个特定位置的元素执行某个特殊操作时,循环变量的使用才有意义。所以,在Java中引入了增强的for循环,在这种循环方式中,循环变量是不必要的:
for(String name : friends) {
System.out.println(name);
}
这种方式,在实现细节上使用的是iterator接口和它的hasNext(),next()方法。
无论使用哪种for循环,它们仍然使用了外部遍历器(External Iterator)。即在for循环中,你总是有办法通过诸如break,continue等方式来控制遍历的过程。
与外部遍历器相对的,是内部遍历器(Internal Iterator)。在Java 8中,Iterable接口被增强了,现在该接口拥有一个forEach方法用来实现内部遍历器。forEach方法会接受一个Consumer接口类型作为参数,该接口是一个函数式接口(Functional Interface),它是内部遍历器的实现方式。关于函数式接口,可以参考上一篇文章。
friends.forEach(new Consumer<String>() {
public void accept(final String name) {
System.out.println(name);
}
});
很显然,上述代码中使用的匿名类在Java 8中并不是最好的方案,在这种场景下Lambda表达式是更好的选择:
friends.forEach((final String name) -> System.out.println(name));
forEach方法本身是一个高阶函数,因为它接受了一个Lambda表达式作为其参数,而Lambda表达式在本质上则是一个函数。在Lambda表达式的左边,声明了一个String类型的变量name,它代表了集合中的元素。而箭头右边的代码则表达了对于该元素应该执行何种操作。forEach之所以被称为内部遍历器,原因在于一旦它开始执行了,那么遍历操作就不能够被轻易中断。
同时,借助Java编译器的类型推导(Type Inference)特性,Lambda表达式能够被进一步简化如下:
friends.forEach((name) -> System.out.println(name));
此时,编译器能够通过运行时的上下文知道这个name变量的类型是String。
另外,当Lambda表达式左端只接受一个变量的时候,括号也是可以省略的:
friends.forEach(name -> System.out.println(name));
但是用类型推导有一个不好的地方,就是参数不会自动被final修饰。因此,在Lambda表达式右端,是可以对参数进行修改的,然而这种行为是不被倡导的。
上面的代码已经足够简洁了,但是还有更简洁的方法,那就是使用方法引用:
friends.forEach(System.out::println);
使用这种方式甚至不需要写出Lambda表达式的左端参数部分。关于方法引用的详细情况,会在以后进行介绍。
与使用外部遍历不同,使用内部遍历的好处在于:
- 放弃使用显式的for循环,因其与生俱来的顺序执行的特性会阻碍并行化。换言之,使用内部遍历时,程序的并行程度能够很容易地被提高。
- 声明式的代码比命令式的代码更简洁,更具有可读性,更优雅。
列表的变换
将一个集合通过某种计算得到另一个集合是一种常用的操作,也是Lambda表达式的用武之地。 比如,以将一个名字集合转换为首字母大写了的名字集合为例。
为了不改变原集合,最“自然”的方式如下:
final List<String> uppercaseNames = new ArrayList<String>();
for(String name : friends) {
uppercaseNames.add(name.toUpperCase());
}
以上代码使用了外部遍历器,即for循环来完成集合操作。而将命令式代码转变为声明式代码(也就是函数式)的首要任务就是观察遍历的使用方式,尽可能地将外部遍历更改为内部遍历:
final List<String> uppercaseNames = new ArrayList<String>();
friends.forEach(name -> uppercaseNames.add(name.toUpperCase<