当前软件行业的气候正在不断地变化。数据量的爆炸式增长,导致程序员在开发时不得不去面对存在的各种效率问题,并希望利用多核计算机或计算集群来有效地处理。这意味着需要使用并行处理,而曾经的java对于并行的支持并不好,这使得其不得不进行进化,以适应当前的行业气候。
Java 8中开发出并行和编写更简洁通用代码的功能,下面我们一起学习。
一、java8有哪些变化
1.1 流处理
Java 8在 java.util.stream 中添加了一个Stream API。通过这个流处理有两个较为直观的好处:
1)把这样的流变成那样的流。
什么意思?简单举个小例子,在Stream提供了很多方法,此处以mapToInt举例:
IntStream mapToInt(ToIntFunction<? super T> mapper);
用法:
public static void main(String[] args) {
// 有如下的字符串列表
List<String> list = Arrays.asList("adas", "ada", "gfg", "asdasd", "adsasd");
// 打印出每个字符串的长度
list.stream().mapToInt(s->s.length()).forEach(l-> System.out.println(l));
// 经过Lambda替换方法后,尽量的简化代码
list.stream().mapToInt(String::length).forEach(System.out::println);
}
结果:
4
3
3
6
6
在上面的代码我们看到只通过一行代码就可以完成操作,最大化的简化了代码。比传统的for循环更容易查看。
2)集合处理。
几乎每个Java应用都会制造和处理集合。但集合用起来并不总是那么理想。比方说,你需要从一个列表中筛选金额较高的交易,然后按货币分组。你需要写一大堆套路化的代码来实现这个数据处理命令。
通过Stream API 可以简单的完成上面的步骤,这里举个例子:
Map<Currency, List<Transaction>> transactionsByCurrencies =
transactions.stream()
.filter((Transaction t) -> t.getPrice() > 1000)//筛选金额较高的
.collect(groupingBy(Transaction::getCurrency));//按照金额分组
相比于使用集合Collection,使用Stream API我们能获得两点直观的优点:
1)不需要自己去通过Collection API去迭代处理复杂的过程(外部迭代),使用流式处理的内部迭代,不需要操心循环的事情。
2)如果数据量非常庞大,那么使用集合的方式,处理将会非常慢;引入多线程将会是原本就复杂的集合迭代变得更加困难,使用Steam API可以较好的解决这一问题,看3)中的介绍。
3)在不同cpu上执行Stream操作,不需要Thread。
Stream提供了并行操作,通过下面的例子简单看下:
public static void main(String[] args) {
// 有如下的字符串列表
List<String> list = Arrays.asList("adas", "ada", "gfg", "asdasd", "adsasd");
// 打印出每个字符串的长度
//list.stream().mapToInt(s->s.length()).forEach(l-> System.out.println(l));
// 经过Lambda替换方法后,尽量的简化代码
long startTime = System.currentTimeMillis();
list.stream().mapToInt(String::length).forEach(l -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(l);
});
System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
}
结果:
4
3
3
6
6
耗时:5104
这时我们添加上并行操作parallel,代码如下:
public static void main(String[] args) {
// 有如下的字符串列表
List<String> list = Arrays.asList("adas", "ada", "gfg", "asdasd", "adsasd");
// 打印出每个字符串的长度
//list.stream().mapToInt(s->s.length()).forEach(l-> System.out.println(l));
// 经过Lambda替换方法后,尽量的简化代码
long startTime = System.currentTimeMillis();
list.stream().mapToInt(String::length).parallel().forEach(l -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(l);
});
System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
}
结果:
3
6
4
3
6
耗时:1096
总体耗时基本是五分之一,或者可以使用parallelStream():
list.parallelStream().mapToInt(String::length).forEach....
关于具体的内容后面会详细讲解。
1.2 用行为参数化把代码(方法)传递给方法
Java 8增加了把方法(你的代码)作为参数传递给另一个方法的能力。我们把这一概念称为行为参数化。
下面会简单讲解时什么意思。
1.2.1 函数
编程语言中的函数一词通常是指方法,尤其是静态方法。
Java 8中新增了函数:值的一种新形式。
编程语言的本质就是操作值。按照传统的编程语言历史,我们以java举例:例如int,long,double这些可以直接被作为参数传递的值,都是一等值(一等公民);而还有很多涉及到结构的内容,例如方法和类等,不能作为参数传递,这些被称为二等公民。
java8中增加了新的功能,将二等公民添加到运行时传递,则二等公民就成为了一等公民。
1.2.1.1 方法和 Lambda 作为一等公民
Java 8的设计者决定允许方法作为值,让编程更轻松。此外,让方法作为值也构成了其他若干Java 8功能(如 Stream )的基础。
我们介绍的Java 8的第一个新功能是方法引用。
举个例子,你想要筛选一个目录中的所有隐藏文件。 File类里面有一个叫作 isHidden 的方法。我们可以把它看作一个函数,接受一个 File ,返回一个布尔值。但要用它做筛选,你需要把它包在一个 FileFilter 对象里,然后传递给 File.listFiles方法,如下所示:
public void test(){
File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.isHidden();
}
});
}
在java8当中我们可以使用如下的方式:
public void test(){
File[] hiddenFiles = new File(".").listFiles(File::isHidden);
}
你已经有了函数 isHidden ,因此只需用Java 8的方法引用 :: 语法(即“把这个方法作为值”)将其传给 listFiles 方法,我们也开始用函数代表方法了。
当使用File::isHidden时,其实创建了一个方法引用,与对象引用类似,我们就可以将其作为一等公民传递。
除了允许(命名)函数成为一等值外,Java 8还体现了更广义的将函数作为值的思想,包括Lambda(匿名函数)。直白说,将匿名函数作为一等值。
将匿名函数传递有什么好处呢?写代码的时候相信都会遇到一种情况,当你只需要一个简单的运算的时候,还需要去重新定义一个类或者方法吗?如果没有方便的类或者方法可用的话,lambda就能帮助你使语法更简洁。
1.2.1.2 使用案例
假设有一个手机仓库,里面放了很多品牌的手机,包括苹果、华为等。现在我们需要筛选出华为手机有多少。那么需要些如下的代码:
public List<Phone> brandFilter(){
List<Phone> phones = new ArrayList<>();
List<Phone> results = new ArrayList<>();
for (Phone phone : phones) {
if ("华为".equals(phone.getBrand())){
results.add(phone);
}
}
return results;
}
如果又需要筛选黑色的手机,那么还需要写下面的方法:
public List<Phone> colorFilter(){
List<Phone> phones = new ArrayList<>();
List<Phone> results = new ArrayList<>();
for (Phone phone : phones) {
if ("黑色".equals(phone.getColor())){
results.add(phone);
}
}
return results;
}
这样一来重复代码就会很多了,如果我们使用java8,可以像下面这样写:
package com.cloud.bssp.java8.stream;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
/**
* @description: 挑选手机
* @author:weirx
* @date:2021/10/15 16:02
* @version:3.0
*/
public class ChoicePhone {
public static void main(String[] args) {
List<Phone> list = new ArrayList<>();
list.add(new Phone("苹果", "黑色"));
list.add(new Phone("华为", "黑色"));
List<Phone> blackPhones = phoneFilter(list, ChoicePhone::isBlack);
System.out.println(blackPhones.toString());
List<Phone> huaweiPhones = phoneFilter(list, ChoicePhone::isHuawei);
System.out.println(huaweiPhones.toString());
}
@Data
static class Phone {
private String brand;
public Phone(String brand, String color) {
this.brand = brand;
this.color = color;
}
private String color;
}
/**
* 筛选华为方法
*/
public static boolean isHuawei(Phone phone) {
return "华为".equals(phone.getBrand());
}
/**
* 筛选黑色方法
*/
public static boolean isBlack(Phone phone) {
return "黑色".equals(phone.getColor());
}
/**
* 手机过滤方法,参数是手机list和Predicate<T>函数
*/
public static List<Phone> phoneFilter(List<Phone> phones, Predicate<Phone> p) {
List<Phone> results = new ArrayList<>();
for (Phone phone : phones) {
if (p.test(phone)) {
results.add(phone);
}
}
return results;
}
}
前面的代码传递了方法 Apple::isGreenApple (它接受参数 Apple 并返回一个boolean )给 filterApples ,后者则希望接受一个 Predicate<Apple> 参数。
Java 8也会允许你写 Function<Apple,Boolean>。
关于上面的代码请同学们细细体会啊,一遍看不懂就多看几遍。
前面就简单介绍了lambda的作用,像我们前面的例子中,只是一个判断的方法实在没有必要单独提供一个方法去维护,所以我们可以使用lambda的方式进行优化,就不需要单独定义判断方法isBlack和isHuawei了,使代码更简洁,如下所示:
List<Phone> blackPhones = phoneFilter(list, (Phone p) -> p.getColor().equals("黑色"));
List<Phone> huaweiPhones = phoneFilter(list, (Phone p) -> p.getBrand().equals("华为"));
List<Phone> blackPhones = phoneFilter(list, (Phone p) ->
p.getColor().equals("黑色") || p.getBrand().equals("华为")
);
1.3 默认方法
在java8之前的版本当中,接口被一个类实现,必须要求这个类实现该接口的全部方法,如果一个接口增加一个方法,那么所有的实现类都需要增加这个方法的实现。那么如何解决这个问题呢?
既然不想让实现类自己实现这个方法,那么只能由接口自己来实现了。这就给开发者提供了一个扩充接口的方式,而不会破坏现有的代码。在java8中使用关键字default来表示这个关键点。
比如在java8当中,我们可以直接调用List接口中的sort方法:
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
这意味着 List 的任何实体类都不需要显式实现 sort。
本篇主要针对java的变化有个简单的认识,后续文章会逐渐深入到细节。
如果对您有帮助,给点个赞吧,感谢!!