Java8 Stream 相关集合转换
简化过程:
public class TestMain {
static class StaStudent implements Person {
@Override
public void speak(String language) {
System.out.println("静态内部类" + language);
}
}
public static void main(String[] args) {
Person p = new GloStudent();
p.speak("lan");
p = new StaStudent();
p.speak("lan");
class LocStudent implements Person {
@Override
public void speak(String language) {
System.out.println("局部内部类" + language);
}
}
p = new LocStudent();
p.speak("lan");
p = new Person() {
@Override
public void speak(String language) {
System.out.println("匿名内部类" + language);
}
};
p.speak("lan");
p = (language) -> {
System.out.println("lambda 表达式---块代码1" + language);
System.out.println("lambda 表达式---块代码2" + language);
};
p.speak("lan");
//接口是函数式接口才能转换为lambda表达式
//在只有一行代码的情况下可以将块简化为行
//单个参数可以去掉参数类型,括号可选
//多个参数可以同时去掉参数类型,必须括号包裹
p = (language) -> System.out.println("lambda 表达式---单行代码" + language);
p.speak("lan");
}
}
class GloStudent implements Person {
@Override
public void speak(String language) {
System.out.println("外部类" + language);
}
}
interface Person {
void speak(String language);
}
那么如何把“一块代码”赋给 Java 变量呢?如图把右边代码,赋给左边的 Java 变量 aBlockOfCode:
当然,该写法并不简洁。为使该赋值操作更加简洁,可移除一些无用的声明,如下:
如此就成功的把“一块代码”赋给了一个变量。而“这块代码”,或者说“这个被赋给一个变量的函数”,就是一个 Lambda 表达式。但是这里有个问题,就是变量 aBlockOfCode 的类型是什么?
Java8 中所有的 Lambda 的类型都是一个接口。而 Lambda 表达式本身,也就是“那块代码”,需要是该接口的实现。
这是理解 Lambda 的关键所在,简言之,Lambda 表达式本身就是一个接口的实现
。便于理解,给 aBlockOfCode 加上一个类型:
这种只有一个函数需要被实现的接口类型,为“函数式接口”。
为避免后人在该接口中增加接口函数导致其有多个接口方法需要被实现,变成"非函数接口”,可以在上面加上一个声明@FunctionalInterface
,如此就无法在里面添加新的接口函数了:
最直观的作用就是使得代码变得异常简洁。
对比一下 Lambda 表达式和传统的 Java 对同一个接口的实现:
两种写法本质上是等价的。但是 Java8 的写法更加优雅简洁。并且由于 Lambda 可以直接赋值给一个变量,就可以直接把 Lambda 作为参数传给函数,而传统的 Java 必须有明确的接口实现的定义,初始化才行:
Java7 要求必须定义一个“污染环境”的接口实现 MyInterfaceImpl。有时,该接口实现只用到一次。相较之下 Java8 的 Lambda 就显得干净很多。
要打印出 guiltyPersons 里面所有 LastName 以“Z”开头的人的 firstName。
原生态 Lambda 写法:定义两个函数式接口,定义一个静态函数,调用静态函数并给参数赋值 Lambda 表达式。
这个代码实际上已经比较简洁了,但是还可以更简洁么?
当然可以。在 Java8 中有一个函数式接口的包java.util.function(Java Platform SE 8),里面定义了大量可能用到的函数式接口。所以,在这里压根都不需要定义 NameChecker 和 Executor 这两个函数式接口,直接用 Java8 函数式接口包里的 Predicate 和 Consumer 就可以——因为这一对的接口定义和 NameChecker/Executor 其实是一样的。
静态函数里面的 for each 其实是非常碍眼的。这里可以利用 Iterable 自带的 forEach() 来替代。forEach() 本身可以接受一个 Consumer 参数。
由于静态函数其实只是对 List 进行了一通操作,这里可以甩掉静态函数,直接使用 stream() 特性来完成。stream() 的几个方法都是接受 Predicate,Consumer 等参数的java.util.stream(Java Platform SE 8)。理解了上面的内容,stream() 就非常好理解了。
对比最开始的 Lambda 写法,这里已经非常简洁了。但是,如果要求变成 print 这个人的全部信息,及 p -> System.out.println§; 那么还可以利用 Method reference 来继续简化。所谓 Method reference 就是用已经写好的别的 Object/Class 的method来代替 Lambda expression。格式如下:
这基本上就是能写的最简洁的版本了。
假设有一个 person object,以及一个 person object 的 Optional wrapper:
Optional 如果不结合 Lambda 使用的话,并不能使原来繁琐的 null check 变的简单。
只有当 Optional 结合 Lambda 一起使用的时候,才能发挥出其真正的威力。现在就来对比一下下面四种常见的 null 处理中,Java8 的 Lambda+Optional 和传统 Java 两者之间对于 null 的处理差异。
由上述四种情况可以清楚地看到,Optional+Lambda 可以少写很多 if/else。尤其是对于情况四,传统 Java 的写法显得冗长难懂,而新的 Optional+Lambda 则清楚简洁。