作者:古时的风筝
就在今年 Java 25周岁了,可能比在座的各位中的一些少年年龄还大,但令人遗憾的是,竟然没有我大,不禁感叹,Java 还是太小了。(难道我会说是因为我老了?)
![ad2b2e923ce9fc4f852b2bc48f3ccdec.png](https://i-blog.csdnimg.cn/blog_migrate/2ead9a05851d553d62d24923600b557f.jpeg)
而就在上个月,Java 15 的试验版悄悄发布了,但是在 Java 界一直有个神秘现象,那就是「你发你发任你发,我的最爱 Java 8」.
据 Snyk 和 The Java Magazine 联合推出发布的 2020 JVM 生态调查报告显示,在所有的 Java 版本中,仍然有 64% 的开发者使用 Java 8。另外一些开发者可能已经开始用 Java 9、Java 11、Java 13 了,当然还有一些神仙开发者还在坚持使用 JDK 1.6 和 1.7。
尽管 Java 8 发布多年,使用者众多,可神奇的是竟然有很多同学没有用过 Java 8 的新特性,比如 Lambda表达式、比如方法引用,再比如今天要说的 Stream。其实 Stream 就是以 Lambda 和方法引用为基础,封装的简单易用、函数式风格的 API。
Java 8 是在 2014 年发布的,实话说,风筝我也是在 Java 8 发布后很长一段时间才用的 Stream,因为 Java 8 发布的时候我还在 C# 的世界中挣扎,而使用 Lambda 表达式却很早了,因为 Python 中用 Lambda 很方便,没错,我写 Python 的时间要比 Java 的时间还长。
![e51f7025cca5b20a0aa6502bb9183c1f.png](https://i-blog.csdnimg.cn/blog_migrate/87d1b10268a91389edb8af697d7097e0.jpeg)
要讲 Stream ,那就不得不先说一下它的左膀右臂 Lambda 和方法引用,你用的 Stream API 其实就是函数式的编程风格,其中的「函数」就是方法引用,「式」就是 Lambda 表达式。
![47e0fd20a81cf93177dbb39e1f30bade.png](https://i-blog.csdnimg.cn/blog_migrate/6c73c95746fecd76479bf5b5ffa91e0f.jpeg)
Lambda 表达式
Lambda 表达式是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象,是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包。
在 Java 中,Lambda 表达式的格式是像下面这样
// 无参数,无返回值() -> log.info("Lambda") // 有参数,有返回值(int a, int b) -> { a+b }
其等价于
log.info("Lambda");private int plus(int a, int b){ return a+b;}
最常见的一个例子就是新建线程,有时候为了省事,会用下面的方法创建并启动一个线程,这是匿名内部类的写法,new Thread需要一个 implements 自Runnable类型的对象实例作为参数,比较好的方式是创建一个新类,这个类 implements Runnable,然后 new 出这个新类的实例作为参数传给 Thread。而匿名内部类不用找对象接收,直接当做参数。
new Thread(new Runnable() { @Override public void run() { System.out.println("快速新建并启动一个线程"); }}).start();
但是这样写是不是感觉看上去很乱、很土,而这时候,换上 Lambda 表达式就是另外一种感觉了。
new Thread(()->{ System.out.println("快速新建并启动一个线程");}).start();
怎么样,这样一改,瞬间感觉清新脱俗了不少,简洁优雅了不少。
Lambda 表达式简化了匿名内部类的形式,可以达到同样的效果,但是 Lambda 要优雅的多。虽然最终达到的目的是一样的,但其实内部的实现原理却不相同。
匿名内部类在编译之后会创建一个新的匿名内部类出来,而 Lambda 是调用 JVM invokedynamic指令实现的,并不会产生新类。
方法引用
方法引用的出现,使得我们可以将一个方法赋给一个变量或者作为参数传递给另外一个方法。::双冒号作为方法引用的符号,比如下面这两行语句,引用 Integer类的 parseInt方法。
Function s = Integer::parseInt;Integer i = s.apply("10");
或者下面这两行,引用 Integer类的 compare方法。
Comparator comparator = Integer::compare;int result = comparator.compare(100,10);
再比如,下面这两行代码,同样是引用 Integer类的 compare方法,但是返回类型却不一样,但却都能正常执行,并正确返回。
IntBinaryOperator intBinaryOperator = Integer::compare;int result = intBinaryOperator.applyAsInt(10,100);
相信有的同学看到这里恐怕是下面这个状态,完全不可理喻吗,也太随便了吧,返回给谁都能接盘。
先别激动,来来来,现在咱们就来解惑,解除蒙圈脸。
Q:什么样的方法可以被引用?
A:这么说吧,任何你有办法访问到的方法都可以被引用。
Q:返回值到底是什么类型?
A:这就问到点儿上了,上面又是 Function、又是Comparator、又是 IntBinaryOperator的,看上去好像没有规律,其实不然。
返回的类型是 Java 8 专门定义的函数式接口,这类接口用 @FunctionalInterface 注解。
比如 Function这个函数式接口的定义如下:
@FunctionalInterfacepublic interface Function { R apply(T t);}
还有很关键的一点,你的引用方法的参数个数、类型,返回值类型要和函数式接口中的方法声明一一对应才行。
比如 Integer.parseInt方法定义如下:
public static int parseInt(String s) throws NumberFormatException { return parseInt(s,10);}
首先par