探索Java8——Java8做出的改变

为什么要关心Java8

Java8所做的改变,比Java历史上任何一次改变都深远,这也是为什么学习到SpringBoot WebFlux的时候,会反过来先把Java8给学习了。

Java8提供了很多编程概念和工具。

流处理

**第一个编程概念是流处理。**流是一系列数据项,一次只生成一项,程序可以从输入流中一个一个读取数据项,然后以同样的方式将数据项写入输出流。
举个例子,在Linux系统中:
cat file1 file2 | tr "[A-Z]" "[a-z]" | sort | tail -3
cat命令会把两个文件连接起来创建一个流,tr会转换流中的字符,sort会对流中的行进行排序,而tail -3则给出流的最后三行。
在这里插入图片描述
这个过程就像是汽车组装流水线一样,每个加工站点会接受、修改汽车,然后将修改后的东西给下个站点做进一步的处理。尽管流水线是一个序列,但是从宏观角度看,数据流足够的多,每个站点的运行都是并行的。
所以Java8可以透明的把输入不相关的部分交给不同的CPU内核处理(将不同处理程度的车交由不同的站点),因为多内核的原因,这是几乎免费的并行,用不着去搞Thread了。

几乎每个Java应用都会制造和处理集合。但集合用起来并不总是那么理想。比方说,你需要从一个列表中筛选金额较高的交易,然后按货币分组。你需要写一大堆套路化的代码来实现这个数据处理命令,如下所示:

Map<Currency, List<Transaction>> transactionsByCurrencies = 
 new HashMap<>(); 
for (Transaction transaction : transactions) { 
 if(transaction.getPrice() > 1000){ 
 Currency currency = transaction.getCurrency(); 
 List<Transaction> transactionsForCurrency = 
 transactionsByCurrencies.get(currency); 
 if (transactionsForCurrency == null) { 
 transactionsForCurrency = new ArrayList<>(); 
 transactionsByCurrencies.put(currency, 
 transactionsForCurrency); 
 } 
 transactionsForCurrency.add(transaction); 
 } 
} 

我们很难一眼看出来这些代码是做什么的,因为有好几个嵌套的控制流指令。有了Stream API,你现在可以这样解决这个问题了:

import static java.util.stream.Collectors.toList; 
Map<Currency, List<Transaction>> transactionsByCurrencies = 
 transactions.stream() 
 .filter((Transaction t) -> t.getPrice() > 1000) 
 .collect(groupingBy(Transaction::getCurrency)); 

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

Java8中增加的另一个编程概念是通过API来传递代码的能力。

举个例子:
假设你有一个Apple类,它有一个getColor方法,还有一个变量inventory保存着一个Apples的列表。你可能想要选出所有的绿苹果,并返回一个列表。通常我们用筛选(filter)一词来表达这个概念。在Java 8之前,你可能会写这样一个方法filterGreenApples:

public static List<Apple> filterGreenApples(List<Apple> inventory){ 
 	List<Apple> result = new ArrayList<>(); 
 	for (Apple apple: inventory){ 
		 if ("green".equals(apple.getColor())) {
 			result.add(apple); 
		 } 
	 } 
 	return result; 
} 

但是接下来,有人可能想要选出重的苹果,比如超过150克,于是你心情沉重地写了下面这个方法,甚至用了复制粘贴:

public static List<Apple> filterGreenApples(List<Apple> inventory){ 
 	List<Apple> result = new ArrayList<>(); 
 	for (Apple apple: inventory){ 
		 if (apple.getWeight() > 150) {
 			result.add(apple); 
		 } 
	 } 
 	return result; 
} 

我们都知道软件工程中复制粘贴的危险——给一个做了更新和修正,却忘了另一个。这两段Java只有一处不同,if语句。Java 8会把条件代码作为参数传递进去,这样可以避免filter方法出现重复的代码。现在你可以写:

    public static boolean isGreenApple(Apple apple) {
        return "green".equals(apple.getColor());
    }

    public static boolean isHeavyApple(Apple apple) {
        return apple.getWeight() > 150;
    }

    public interface Predicate<T> {
        boolean test(T t);
    }
    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;
    }

要用它的话,你可以写:

filterApples(inventory, Apple::isGreenApple);
或者
filterApples(inventory, Apple::isHeavyApple);

Java 8增加了把方法(你的代码)作为参数传递给另一个方法的能力。我们把这一概念称为行为参数化。

当然,把方法作为值来传递显然很有用,但要是为类似于isHeavyApple和isGreenApple这种可能只用一两次的短方法写一堆定义有点儿烦人。

过Java 8也解决了这个问题,它引入了一套新记法(匿名函数或Lambda),让你可以写:

filterApples(inventory, (Apple a) -> "green".equals(a.getColor()) ); 
或者
filterApples(inventory, (Apple a) -> a.getWeight() > 150 ); 

前面的代码传递了方法 Apple::isGreenApple (它接受参数 Apple 并返回一个boolean)给filterApples,后者则希望接受一个Predicate参数。谓词(predicate)在数学上常常用来代表一个类似函数的东西,它接受一个参数值,并返回true或false。你在后面会看到,Java 8也会允许你Function<Apple,Boolean>——在学校学过函数却没学过谓词的读者对此可能更熟悉,但用Predicate是更标准的方式,效率也会更高一点儿,这避免了把boolean封装在Boolean里面。

并行与共享的可变数据

第三个编程概念更隐晦一点,在之前讨论的流处理能力中,说到“几乎免费的并行”,实际上是需要一些代价的:**你的行为必须能够同时对不同的输入安全地执行。一般情况下这就意味着,你写代码时不能访问共享的可变数据。**这类函数通常也被称为“无状态函数”。
但如果两个进程需要同时修改这个共享变量会怎么办?

集合处理时的套路和晦涩,以及难以利用多核。这样设计的第一个原因是,有许多反复出现的数据处理模式,类似于前一节所说的filterApples,如果在库中有这些就会很方便:根据标准筛选数据(比如较重的苹果),提取数据(例如抽取列表中每个苹果的重量字段),或给数据分组(例如,将一个数字列表分组,奇数和偶数分别列表)等。第二个原因是,这类操作常常可以并行化。

import static java.util.stream.Collectors.toList; 
List<Apple> heavyApples = 
 inventory.stream().filter((Apple a) -> a.getWeight() > 150) 
 .collect(toList()); 

使用filter分支到两个CPU上并聚合:
在这里插入图片描述
并行处理:

import static java.util.stream.Collectors.toList; 
List<Apple> heavyApples = 
 inventory.parallelStream().filter((Apple a) -> a.getWeight() > 150) 
 .collect(toList()); 

大家都说Java里面并行很难,而且和synchronized相关的玩意儿都容易出问题。那Java 8里面有什么“灵丹妙药”呢?事实上有两个。首先,库会负责分块,即把大的流分成几个小的流,以便并行处理。其次,流提供的这个几乎免费的并行,只有在传递给filter之类的库方法的方法不会互动(比方说有可变的共享对象)时才能工作。但是其实这个限制对于程序员来说挺自然的,举个例子,我们的Apple::isGreenApple就是这样。确实,虽然函数式编程中的函数的主要意思是“把函数作为一等值”,不过它也常常隐含着第二层意思,即“执行时在元素之间无互动”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值