Java8新变化

Java8优势

自1998年JDK1.0(Java1.0)发布以来,从Java1.1(1997年)-Java7(2011年),Java不断升级,Java8则是在2014年3月发布。

流处理

Java 8在java.util.stream中添加了一个Stream API;Stream可以看成一种迭代器。

Stream API的可以链接形成一个复杂的流水线,就像Unix命令。

可以在一个更高的抽象层次上写Java8程序,把这样的流变成那样的流(就像写数据库查询语句时的那种思路),而不是一次只处理一个项目。

Java8可以透明地把输入的不相关部分拿到几个CPU内核上去分别执行。Stream操作流水线——这是几乎免费的并行,用不着去费劲搞Thread。

用行为参数化把代码传递给方法

可以写一个compareUsingCustomerId来比较两张发票的代码,但是在Java8之前,你没法把这个方法传给另一个方法。可以创建一个Comparator对象,将之传递给sort方法,但这不但啰嗦,而且让“重复使用现有行为”的思想变得不那么清楚了。

Java8增加了把方法(代码)作为参数传递给另一个方法的能力。

并行与共享的可变数据

行为必须能够同时对不同的输入安全地执行。这就意味着,写代码时不能访问共享的可变数据。这些函数有时被称为“纯 函数”或“无副作用函数”或“无状态函数”。

Java8的流实现并行比Java现有的线程API更容易,因此,尽管可以使用synchronized来打破“不能有共享的可变数据”这一规则,但这相当于是在和整个体系作对,因为它使所有围绕这一规则做出的优化都失去意义了。

在多个处理器内核之间使用synchronized,其代价往往比预期的要大得多,因为同步迫使代码按照顺序执行,而这与并行处理的宗旨相悖。

没有共享的可变数据及将方法函数即代码传递给其他方法的能力是函数式编程范式的基石。不能有共享的可变数据的要求意味着,方法的行为就像一个数学函数,没有可见的副作用。

Java8中的主要变化反映了它开始远离常侧重改变现有值的经典面向对象思想,而向函数式编程领域转变。

Java8中的函数

Java8中新增了函数——值的一种新形式。它有助于使用流,可以进行多核处理器上的并行编程。

值是Java中的一等公民,但其他很多Java概念(如方法和类等)则是二等公民。在运行时传递方法能将方法变成一等公民。

方法和Lambda作为一等公民

方法引用

Java8的方法引用采用::语法,写下File::isHidden的时候,就创建了一个方法引用,你同样可以传递它。

Lambda——匿名函数

将函数作为值的思想

1
2
3
4
5
6
7
8
9
10
11
12
public static boolean isGreenApple(Apple apple) { 
return "green".equals(apple.getColor());
}
static List<Apple> filterApples(List<Apple> inventory, Predicate<Apple> p) {
List<Apple> result = new ArrayList<>();
for (Apple apple: inventory){
if (p.test(apple)) {
result.add(apple);
}
}
return result;
}

传递方法

1
2
filterApples(inventory, Apple::isGreenApple); 
filterApples(inventory, Apple::isHeavyApple);

谓词

传递方法Apple::isGreenApple(接受参数Apple并返回一个boolean)给filterApples,后者则希望接受一个Predicate参数。

谓词(predicate)在数学上常常用来代表一个类似函数的东西,它接受一个参数值,并返回true或false。

从传递方法到Lambda

类似于isHeavyApple和isGreenApple这种可能只用一两次的短方法写一堆定义有点儿烦人。

Java8解决了这个问题,它引入了一套新记法(匿名函数或Lambda)。

1
2
3
4
filterApples(inventory, (Apple a) -> "green".equals(a.getColor()) );
filterApples(inventory, (Apple a) -> a.getWeight() > 150 );
filterApples(inventory, (Apple a) -> a.getWeight() < 80 ||
"brown".equals(a.getColor()) );

甚至都不需要为只用一次的方法写定义;代码更干净、更清晰,因为用不着去找自己到底传递了什么代码。

但要是Lambda的长度多于几行(它的行为也不是一目了然)的话,那还是应该用方法引用来指向一个有描述性名称的方法,而不是使用匿名的Lambda,应该以代码的清晰度为准绳。

Java8流

用for-each循环一个个去迭代元素,然后再处理元素。我们把这种数据迭代的方法称为外部迭代。

Stream API,根本用不着操心循环的事情。数据处理完全是在库内部进行的,我们把这种思想叫作内部迭代。Java8提供了新的编程风格,可更好地利用多核计算机。

Java8默认方法

直接对List调用sort方法,它是用Java8 List接口中如下所示的默认方法实现的

1
2
3
default void sort(Comparator<? super E> c) { 
Collections.sort(this, c);
}

这意味着List的任何实体类都不需要显式实现sort,而在以前的Java版本中,除非提供了sort的实现,否则这些实体类在重新编译时都会失败。

Java8来自函数式编程的其他好思想

Java中从函数式编程中引入的两个核心思想:将方法和Lambda作为一等值,以及在没有可变共享状态时,函数或方法可以有效、安全地并行执行。

Optional

在Java 8里有一个Optional类,如果你能一致地使用它的话,就可以帮助你避免出现NullPointer异常。它是一个容器对象,可以包含,也可以不包含一个值。

模式匹配

模式匹配可以看作switch的扩展形式,可以同时将一个数据类型分解成元素。

在Java中,你可以在这里写一个if-then-else语句或一个switch语句。其他语言表明,对于更复杂的数据类型,模式匹配可以比if-then-else更简明地表达编程思想。

给定一个数据类型Expr代表表达式,在Scala里可以写以下代码,把Expr分解给它的各个部分,然后返回另一个Expr。

1
2
3
4
5
6
def simplifyExpression(expr: Expr): Expr = expr match { 
case BinOp("+", e, Number(0)) => e
case BinOp("*", e, Number(1)) => e
case BinOp("/", e, Number(1)) => e
case _ => expr
}

这里,Scala的语法expr match就对应于Java中的switch (expr)。

Java中的switch语句限于原始类型值和Strings。函数式语言倾向于允许switch用在更多的数据类型上,包括允许模式匹配(在Scala代码中是通过match操作实现的)。

模式匹配的一个优点是编译器可以报告常见错误。

------ 本文结束------

本文标题:Java8新变化

文章作者:Perkins

发布时间:2019年10月24日

原始链接:https://perkins4j2.github.io/posts/3769/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。