非Lambda的方式
举这么一个例子,假设我想要遍历一个对象的列表。不过由于我的业务需求,我还需要取得列表项的值和索引。如果用现在版本的Java来做的话,我需要把实际的逻辑和索引放在一起进行处理:
- List list = Arrays.asList("A", "B", "C");
- for (int index = 0; index < list.size(); index++) { String value = list.get(index); String output = String.format("%d -> %s", index, value);
- System.out.println(output);
- }
这样会输出
- 0 -> A
- 1 -> B
- 2 -> C
这样其实也并不坏,但是我这几行代码里做了两件事:控制列表的迭代以及进行了一些(简单)的业务逻辑处理。不过如果使用Lambda表达式的话,它可以帮助我把这两者分开进行处理。
eachWithIndex方法签名
因此,我想实现一个eachWithIndex方法,它可以这样被调用:
- List list = Arrays.asList("A", "B", "C");
- eachWithIndex(list,
- (value, index) -> {
- String output = String.format("%d -> %s", index, value);
- System.out.println(output);
- }
- );
这个方法接收两个参数。第一个参数是要处理的列表,第二个参数是一个lambda表达式或者闭包,它表示处理每个列表项的方法。你可以在第3行看到,这个lambda表达式接受两个参数:当前值和当前索引。这两个参数都没有类型声明。Java 8 的编译器会自动推导出参数的类型。在参数的后面,是一个->符号以及处理每个列表项的代码块。
注意:你需要在一个文本编辑器里编写这个方法或者你需要忽略IDE提示的错误信息。
实现eachWithIndex方法
为了使用Java 8 的lambda,你需要声明一个功能接口。功能接口是一种特殊的接口,它只有一个方法——这个方法会被lambda表达式实现。在这个示例里,我需要声明一个接收一个元素和索引并且没有返回值的接口。因此,我定义了如下的接口:
- public static interface ItemWithIndexVisitor<E> {
- public void visit(E item, int index);
- }
通过这个接口,我现在可以实现eachWithIndex方法。
- public static <E> void eachWithIndex(List<E> list, ItemWithIndexVisitor<E> visitor) {
- for (int i = 0; i < list.size(); i++) {
- visitor.visit(list.get(i), i);
- }
- }
这个方法使用了泛型参数<E>,因此传入到visit方法里的元素类型会根据列表的类型进行推导。
使用功能接口的一个好处是,Java里已经有了很多的功能接口。想想关于 java.util.concurrent.Callableinterface的例子。它可以被当作lambda表达式来使用,并且不需要修改 Callable的调用者的代码。这样使得大部分的JDK和框架默认都能够支持lambda表达式。
使用方法引用
另外一个来自于Lambda项目的很有用的东西是方法引用。它是一种复用已有的方法并且把它们打包成一个功能接口对象的方式。假设我们有如下的代码:
- public static <E> void printItem(E value, int index) {
- String output = String.format("%d -> %s", index, value);
- System.out.println(output);
- }
并且我想在eachWithIndex方法里调用它,那么我就可以在我的方法调用里使用::符号:
- eachWithIndex(list, LambdaIntro::printItem);
看起来很不错,并且也很简洁,对吗?
总结
这样就可以使得我的第一个lambda表达式例子能够运行。我实在是太兴奋了。