本文是慕课网大牧莫邪老师的视频教程一课掌握Lambda表达式语法及应用的学习笔记。如果觉得内容对你有用,可以购买老师的课程支持一下,课程价格1元,十分良心了。
1. 课程介绍
2. 为什么引入Lambda表达式
2.1 什么是Lambda表达式
Lambda表达式也称箭头函数、匿名函数、闭包
Lambda表达式体现的是轻量级函数式编程思想
-> 符号是Lambda表达式核心操作符号,符号左侧是操作参数,符号右侧是操作表达式
Lambda表达式是 jdk1.8 提供的新特性
2.2 Model Code as Data(MCAD模式)
Model Code as Data,编码即数据,尽可能轻量级的将代码封装为数据
解决方案:接口&实现类(匿名内部类)
存在问题:语法冗余、thIs关键字、变量捕获、数据控制等
传统模式下,新线程的创建:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("threading ..." + Thread.currentThread().getName());
}
}).start();
使用jdk8新特性,lambda表达式优化线程模型
new Thread(() -> {
System.out.println("threading ..." + Thread.currentThread().getName());
}).start();
2.3 项目问题:功能接口的设计及优化
需求环境:线程类的创建
解决方案:匿名内部类的实现
解决方法:lambda表达式实现
2.4 为什么要使用lambda表达式
它不是解决未知问题的新技术
对现有解决方案的语义化优化
需要根据实际需求考虑性能问题
3. Lambda表达式的基础知识
3.1 函数式接口概述和定义
函数式接口,就是java类型系统中的接口
函数式接口,是只包含一个接口方法的特殊接口
语义化检测注解:@FunctionalInterface
可以像定义普通接口一样定义函数式接口,并且接口内只有一个抽象方法:
@FunctionalInterface
public interface IUserCredential {
/**
* 通过用户账号,验证用户身份信息的接口
* @param username 要验证的用户账号
* @return 返回身份信息[系统管理员、用户管理员、普通用户]
*/
String verifyUser(String username);
// 添加这个方法后,会报错
// boolean test();
由于接口添加了@FunctionalInterface注解,表明是一个函数式接口,内部只能有一个抽象方法,如果再添加一个抽象方法boolean test(),就会报错:Multiple non-overriding abstract methods found in interface com.imooc.IUserCredential
注意:@FunctionalInterface 注解也可以不加,函数式接口只需要满足以下两个条件即可:
定义一个接口
接口中只有一个抽象方法
3.2 默认方法和静态方法
1. 接口的默认方法:default关键字修饰
@FunctionalInterface
public interface IUserCredential {
/**
* 通过用户账号,验证用户身份信息的接口
* @param username 要验证的用户账号
* @return 返回身份信息[系统管理员、用户管理员、普通用户]
*/
String verifyUser(String username);
/**
* 接口的默认方法
*/
default String getCredential(String username) {
// 模拟方法
if ("admin".equals(username)) {
return "admin + 系统管理员用户";
} else if("manager".equals(username)){
return "manager + 用户管理员用户";
} else {
return "commons + 普通会员用户";
}
}
}
接口的默认方法使用default关键字修饰,调用时,需要用接口的实例调用:
IUserCredential instance = new xxx(); // 实例化接口中的实现类
instance.getCredential("admin");
2. 接口的静态方法:static关键字修饰
@FunctionalInterface
public interface IUserCredential {
/**
* 通过用户账号,验证用户身份信息的接口
* @param username 要验证的用户账号
* @return 返回身份信息[系统管理员、用户管理员、普通用户]
*/
String verifyUser(String username);
/**
* 接口的静态方法
*/
static String getCredential(String username) {
// 模拟方法
if ("admin".equals(username)) {
return "admin + 系统管理员用户";
} else if("manager".equals(username)){
return "manager + 用户管理员用户";
} else {
return "commons + 普通会员用户";
}
}
}
同普通类的静态方法一样,接口的静态方法在调用时,直接调用即可,不需要实例化接口实例:
// 直接使用 接口名.方法名 调用
IUserCredential.getCredential("admin");
3. 来自Object继承的方法
由于接口或类都是Object的子类,如果我们在接口中增加一个由Object类继承过来的抽象方法,接口依然不会报错:
@FunctionalInterface
public interface IUserCredential {
/**
* 通过用户账号,验证用户身份信息的接口
* @param username 要验证的用户账号
* @return 返回身份信息[系统管理员、用户管理员、普通用户]
*/
String verifyUser(String username);
/**
* 这里的toString()方法是Object类继承的,添加后并不会报错
* @return
*/
@Override
String toString();
}
以上接口虽然有两个抽象类,但由于toString()方法是从Object类继承的,因此并不会报错,该接口依然是一个函数式接口。
3.3 Lambda表达式和函数式接口的关系
在jdk1.8之前,我们使用匿名内部类,实现接口的抽象方法:
IUserCredential ic2 = new IUserCredential() {
@Override
public String verifyUser(String username) {
return "admin".equals(username)?"管理员":"会员";
}
};
在jdk1.8,使用lambda表达式,针对函数式接口的简单实现
IUserCredential ic3 = (String username) -> {
return "admin".equals(username) ? "lbd管理员" : "lbd会员";
};
lambda表达式 是 函数式接口的一种实现.
3.4 jdk中常见的函数式接口
在java.util.function提供了大量的函数式接口:
Predicate 接收参数T对象,返回一个boolean类型结果
@FunctionalInterface
public interface Predicate {
boolean test(T t);
// 省略静态方法和默认方法
}
Consumer 接收参数T对象,没有返回值
@FunctionalInterface
public interface Consumer {
void accept(T t);
// 省略静态方法和默认方法
}
Function 接收参数T对象,返回R对象
@FunctionalInterface
public interface Function {
R apply(T t);
// 省略静态方法和默认方法
}
Supplier 不接受任何参数,直接通过get()获取指定类型的对象
@FunctionalInterface
public interface Supplier {
T get();
}
UnaryOperator 接口参数T对象,执行业务处理后,返回更新后的T对象
@FunctionalInterface
public interface UnaryOperator extends Function {
static UnaryOperator identity() {
return t -> t;
}
}
BinaryOperator 接口接收两个T对象,执行业务处理后,返回一个T对象
public interface BiFunction {
R apply(T t, U u);
// 省略静态方法和默认方法
}
@FunctionalInterface
public interface BinaryOperator extends BiFunction {
// 省略静态方法和默认方法
}
示例:
// 1. Predicate 接收参数T对象,返回一个boolean类型结果
Predicate pre = (String username) -> {
return "admin".equals(username);
};
System.out.println(pre.test("manager"));
// 2. Consumer 接收参数T对象,没有返回值
Consumer con = (String message) -> {
System.out.println("要发送的消息:" + message);
System.out.println("消息发送完成");
};
con.accept("hello 慕课网的学员们..");
// 3. Function 接收参数T对象,返回R对象
Function fun = (String gender) -> {
return "male".equals(gender) ? 1 : 0;
};
System.out.println(fun.apply("male"));
// 4. Supplier 不接受任何参数,直接通过get()获取指定类型的对象
Supplier sup = () -> {
return UUID.randomUUID().toString();
};
System.out.println(sup.get());
// 5. UnaryOperator 接口参数T对象,执行业务处理后,返回更新后的T对象
UnaryOperator uo = (String img)-> {
img += "[100x200]";
return img;
};
System.out.println(uo.apply("原图--"));
// 6. BinaryOperator 接口接收两个T对象,执行业务处理后,返回一个T对象
BinaryOperator bo = (Integer i1, Integer i2) -> {
return i1 > i2? i1: i2;
};
System.out.println(bo.apply(12, 13));
3.5 Lambda表达式基本语法
声明:就是和lambda表达式绑定的接口类型
参数:包含在一对圆括号中,和绑定的接口中的抽象方法中的参数个数及顺序一致。
操作符:->
执行代码块:包含在一对大括号中,出现在操作符号的右侧
示例如下:
首先定义3个接口:
// 1. 没有参数,没有返回值的lambda表达式绑定的接口
interface ILambda1{
void test();
}
// 2. 带有参数,没有返回值的lambda表达式
interface ILambda2{
void test(String name, int age);
}
// 3. 带有参数,带有返回值的lambda表达式
interface ILambda3 {
int test(int x, int y);
}
编写Lambda表达式示例:
// 1. [接口声明] = (参数) -> {执行代码块};
ILambda1 i1 = () -> {
System.out.println("hello imooc!");
System.out.println("welcome to imooc!");
};
i1.test();
// 2. lambda表达式的返回值,如果代码块只有一行,并且没有大括号,不用写return关键字,单行代码的执行结果,会自动返回
ILambda1 i2 = () -> System.out.println("hello imooc");
i2.test();
// 3. 带有多个参数的Lambda表达式
ILambda2 i21 = (String n, int a) -> {
System.out.println(n + "say: my year's old is " + a);
};
i21.test("jerry", 18);
// 4. 不写参数类型,由jvm自动推导
ILambda2 i22 = (n, a) -> {
System.out.println(n + " 说:我今年" + a + "岁了.");
};
i22.test("tom", 22);
// 5. 带有返回值的Lambda表达式
ILambda3 i3 = (x, y) -> {
int z = x + y;
return z;
};
System.out.println(i3.test(11, 22));
// 6. 只有一行时,可以省略大括号和return字段
ILambda3 i31 = (x, y) -> x + y;
System.out.println(i31.test(100, 200));
小结:
lambda表达式,必须和接口进行绑定。
lambda表达式的参数,可以附带0个到n个参数,括号中的参数类型可以不用指定,jvm在运行时,会自动根据绑定的抽象方法中参数进行推导。
lambda表达式的返回值,如果代码块只有一行,并且没有大括号,不用写return关键字,单行代码的执行结果,会自动返回。如果添加了大括号,或者有多行代码,必须通过return关键字返回执行结果。
3.6 变量捕获——变量的访问操作
1. 匿名内部类型中对于变量的访问
public void testInnerClass() {
String s2 = "局部变量";
new Thread(new Runnable() {
String s3 = "内部变量";
@Override
public void run() {
// 访问全局变量
// System.out.println(this.s1); // 无法访问s1,这里的this关键字表示是当前内部类型的对象
System.out.println(s1);
System.out.println(s2); // 局部变量的访问
// s2 = "hello"; // 不能对局部变量进行数据的修改[final]
System.out.println(s3);
System.out.println(this.s3);
}
}).start();
}
在匿名内部类中,
this关键字表示的是当前内部类型的对象
局部变量的访问时,不能对局部变量进行数据的修改(默认为final类型)
2. lambda表达式变量捕获
public void testLambda() {
String s2 = "局部变量lambda";
new Thread(() -> {
String s3 = "内部变量lambda";
// 访问全局变量
System.out.println(this.s1);// this关键字,表示的就是所属方法所在类型的对象
// 访问局部变量
System.out.println(s2);
// s2 = "hello";// 不能进行数据修改,默认推导变量的修饰符:final
System.out.println(s3);
s3 = "labmda 内部变量直接修改";
System.out.println(s3);
}).start();
}
在Lambda表达式中,
this关键字,表示的就是所属方法所在类型的对象
修改局部时,同样会报错:默认推导变量的修饰符为final
3.7 Lambda表达式类型检查
首先定义个函数式接口:
@FunctionalInterface
interface MyInterface {
R strategy (T t, R r);
}
定义方法:
public static void test(MyInterface inter) {
List list = inter.strategy("hello", new ArrayList());
System.out.println(list);
}
匿名内部类调用:
test(new MyInterface() {
@Override
public List strategy(String s, List list) {
list.add(s);
return list;
}
});
Lambda表达式调用:
test((x, y) -> {
y.add(x);
return y;
});
Lambda表达式方法推导:
(x,y)->{..} --> test(param) --> param==MyInterface --> lambda表达式-> MyInterface类型
这个就是对于lambda表达式的类型检查,MyInterface接口就是lambda表达式的目标类型(target typing)
Lambda表达式方法参数推导:
(x,y)->{..} --> MyInterface.strategy(T r, R r)--> MyInterface inter
--> T==String R==List --> lambda--> (x, y) == strategy(T t , R r)--> x==T==String y==R==List
lambda表达式参数的类型检查
3.8 方法重载和Lambda表达式
先定义两个函数式接口:
interface Param1 {
void outInfo(String info);
}
interface Param2 {
void outInfo(String info);
}
再定义两个重载方法:
// 定义重载的方法
public void lambdaMethod(Param1 param) {
param.outInfo("hello param1 imooc!");
}
public void lambdaMethod(Param2 param) {
param.outInfo("hello param2 imooc");
}
使用匿名内部类调用:
app.lambdaMethod(new Param1() {
@Override
public void outInfo(String info) {
System.out.println(info);
}
});
app.lambdaMethod(new Param2() {
@Override
public void outInfo(String info) {
System.out.println("------");
System.out.println(info);
}
});
这里能正常运行,但是在使用Lambda表达式时,会有问题:
app.lambdaMethod( (String info) -> {
System.out.println(info);
});
异常信息:
Ambiguous method call. Both
lambdaMethod(Param1) in Test and
lambdaMethod(Param2) in Test match
jvm对Lambda表达式调用的方法推导如下:
lambda表达式存在类型检查-> 自动推导lambda表达式的目标类型
lambdaMethod() -> 方法 -> 重载方法
-> Param1 函数式接口
-> Param2 函数式接口
调用方法-> 传递Lambda表达式-> 自动推导->
-> Param1 | Param2
因此,在调用时,需要人为地告诉jvm我们要调用的方法参数是啥:
app.lambdaMethod((Param1) (String info) -> {
System.out.println(info);
});
3.9 深入理解lambda表达式
Lambda表达式在jvm谨慎解析在私有静态方法和匿名内部类型,通过实现接口的匿名内部类型中接口方法调用静态实现方法,完成Lambda表达式的执行。
先准备一个App.java类:
// 函数式接口
interface IMarkUp {
void markUp(String msg);
}
public class App {
public static void main(String [] args) {
IMarkUp mu = (message) -> System.out.println(message);
mu.markUp("lambda!");
}
}
使用javac 编译下,再使用javap查看:
$ javac App.java
$ javap -p App.class
Compiled from "App.java"
public class App {
public App();
public static void main(java.lang.String[]);
private static void lambda$main$0(java.lang.String);
}
可以看到,自动生成了私有静态方法private static void lambda$main$0(java.lang.String),Lambda在实际运行时,也是生成了一个私有静态方法:
private static void lambda$main$0(String message) {
System.out.println(message);
}
为了查看编译过程,我们使用参数-Djdk.internal.lambda.dumpProxyClasses重新处理:
$ java -Djdk.internal.lambda.dumpProxyClasses App
lambda!
运行后,发现多生成了一个类:App$$Lambda$1.class,使用javap -p App$$Lambda$1查看类的信息:
$ javap -p App$$Lambda$1
final class App$$Lambda$1 implements IMarkUp {
private App$$Lambda$1();
public void markUp(java.lang.String);
}
这里的markUp(java.lang.String)方法实际调用的是 lambda$main$0:
public void markUp(String message) {
App.lambda$main$0(message);
}
总结下lambda表达式的底层执行过程:
在编译时,会自动生成私有静态方法 private static void lambda$main$0(java.lang.String)
在编译时,会自动生成实现类:final class App$$Lambda$1 implements IMarkUp
调用mu.markUp("lambda!"),实际上进行的操作是new App$$Lambda$1().markUp("lambda!")
代码如下:
interface IMarkUp {
void markUp(String msg);
}
public class App {
public static void main(String [] args) {
IMarkUp mu = (message) -> System.out.println(message);
mu.markUp("lambda!");
// 3. 实际调用: new App$$Lambda$1().markUp("lambda!");
}
// 1. 自动生成的私有静态方法
/*
private static void lambda$main$0(String message) {
System.out.println(message);
}
*/
// 2. 自动生成的内部类
/*
final class App$$Lambda$1 implements IMarkUp {
private App$$Lambda$1() {
}
public void markUp(String message) {
App.lambda$main$0(message);
}
}
*/
}
4. Lambda表达式在集合中的运用
4.1 方法引用
方法引用是结合Lambda表达式的一种语法特性,主要分为静态方法引用、实例方法引用和构造方法引用。
先准备一个POJO:
@Data
@AllArgsConstructor
@NoArgsConstructor
class Person {
private String name;
private String gender;
private int age;
// 静态方法引用
public static int compareByAge(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
}
再准备一些数据:
List list = new ArrayList();
list.add(new Person("shuke", "男", 29));
list.add(new Person("tom", "男", 16));
list.add(new Person("jerry", "男", 20));
list.add(new Person("beita", "女", 30));
1. 静态方法引用
匿名内部类实现排序:
Collections.sort(list, new Comparator() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
});
lambda表达式实现排序
Collections.sort(list, (p1, p2) -> p1.getAge() - p2.getAge());
方法引用实现排序
// 使用::操作符引用Person类的静态方法compareByAge()
Collections.sort(list, Person::compareByAge);
2. 实例方法引用
添加一个类,准备实例方法:
class PersonUtil {
// 实例方法引用
public int comprareByName(Person p1, Person p2) {
return p1.getName().hashCode() - p2.getName().hashCode();
}
}
实例方法引用:
PersonUtil pu = new PersonUtil();
Collections.sort(list, pu::comprareByName);
3. 构造方法引用
准备一个函数式接口:
interface IPerson {
Person getPerson(String name, String gender, int age);
}
使用方式如下:
/*
// 匿名内部类方式
IPerson p1 = new IPerson() {
@Override
public Person getPerson(String name, String gender, int age) {
return new Person(name, gender, age);
}
}
// lambda表达式方式
IPerson p1 = (name, gender, age) -> new Person(name, gender, age);
*/
// 绑定构造方法,实际调用的构造方法是 Person(String, String, int)
IPerson p1 = Person::new;
// 调用接口方法
Person person = p1.getPerson("tom", "男", 18);
4.2 Stream概述
首先准备数据:
List list = new ArrayList();
list.add("tom");
list.add("jerry");
list.add("shuke");
list.add("beita");
list.add("damu");
现在有这样的处理要求:找出升序大于5的有效账号
第一种方式:增强for遍历
List lista = new ArrayList();
for (String s : list) {
if (s.length() > 3) {
lista.add(s);
}
}
System.out.println(lista);
第二种方式:Iterator遍历
List listb = new ArrayList<>();
Iterator it = list.iterator();
while(it.hasNext()) {
String s = it.next();
if(s.length() > 3) {
listb.add(s);
}
}
System.out.println(listb);
第三种方式:使用 stream 实现
List listc = list.stream().filter(s->s.length()>3).collect(Collectors.toList());
System.out.println(listc);
4.3 Stream常见操作API介绍
4.3.1 聚合操作
4.3.2 stream的处理流程
数据源
数据转换
获取结果
4.3.3 获取Stream对象
从集合或者数组中获取[**]
Collection.stream(),如accounts.stream()
Collection.parallelStream()
Arrays.stream(T t)
BufferReader
BufferReader.lines()-> stream()
静态工厂
java.util.stream.IntStream.range()..
java.nio.file.Files.walk()..
自定构建
java.util.Spliterator
更多的方式..
Random.ints()
Pattern.splitAsStream()..
4.3.4 中间操作API{intermediate}
操作结果是一个Stream,中间操作可以有一个或者多个连续的中间操作,需要注意的是,中间操作只记录操作方式,不做具体执行,直到结束操作发生时,才做数据的最终执行。
中间操作:就是业务逻辑处理。
中间操作过程:
无状态:数据处理时,不受前置中间操作的影响,如:map/filter/peek/parallel/sequential/unordered
有状态:数据处理时,受到前置中间操作的影响,如:distinct/sorted/limit/skip
4.3.5 终结操作|结束操作{Terminal}
需要注意的是,一个Stream对象,只能有一个Terminal操作,这个操作一旦发生,就会真实处理数据,生成对应的处理结果。
终结操作又可区分为非短路操作和短路操作,
非短路操作:当前的Stream对象必须处理完集合中所有 数据,才能得到处理结果,如:forEach/forEachOrdered/toArray/reduce/collect/min/max/count/iterator
短路操作:当前的Stream对象在处理过程中,一旦满足某个条件,就可以得到结果,如:anyMatch/allMatch/noneMatch/findFirst/findAny等,Short-circuiting,无限大的Stream-> 有限大的Stream。
4.4 Stream操作集合数中的数据-上
4.4.1 获取stream
多个数据
Stream stream = Stream.of("admin", "tom", "damu");
数组
String [] strArrays = new String[] {"xueqi", "biyao"};
Stream stream2 = Arrays.stream(strArrays);
列表
List list = new ArrayList<>();
list.add("少林");
list.add("武当");
list.add("青城");
list.add("崆峒");
list.add("峨眉");
Stream stream3 = list.stream();
集合
Set set = new HashSet<>();
set.add("少林罗汉拳");
set.add("武当长拳");
set.add("青城剑法");
Stream stream4 = set.stream();
Map
Map map = new HashMap<>();
map.put("tom", 1000);
map.put("jerry", 1200);
map.put("shuke", 1000);
Stream stream5 = map.entrySet().stream();
4.4.2 Stream对象对于基本数据类型的功能封装
// int / long / double
IntStream.of(new int[] {10, 20, 30}).forEach(System.out::println);
// range方法:[1, 5),左闭右开
IntStream.range(1, 5).forEach(System.out::println);
// rangeClosed:[1, 5],左半右闭
IntStream.rangeClosed(1, 5).forEach(System.out::println);
4.4.3 Stream对象 --> 转换得到指定的数据类型
// 数组
Object [] objx = stream.toArray(String[]::new);
// 字符串
String str = stream.collect(Collectors.joining()).toString();
// 列表
List listx = (List) stream.collect(Collectors.toList());
// 集合
Set setx = (Set) stream.collect(Collectors.toSet());
// Map
Map mapx = (Map) stream.collect(Collectors.toMap(x->x, y->"value:"+y));
4.5 Stream操作集合数中的数据-下
1. Stream中间操作
List accountList = new ArrayList<>();
accountList.add("xongjiang");
accountList.add("lujunyi");
accountList.add("wuyong");
accountList.add("linchong");
accountList.add("luzhishen");
accountList.add("likui");
accountList.add("wusong");
// map() 中间操作,map()方法接收一个Functional接口
accountList = accountList.stream().map(x->"梁山好汉:" + x).collect(Collectors.toList());
// filter() 添加过滤条件,过滤符合条件的用户
accountList = accountList.stream().filter(x-> x.length() > 5).collect(Collectors.toList());
// forEach 增强型循环
accountList.forEach(x-> System.out.println("forEach->" + x));
// peek() 中间操作,迭代数据完成数据的依次处理过程
accountList.stream()
.peek(x -> System.out.println("peek 1: " + x))
.peek(x -> System.out.println("peek 2:" + x))
.forEach(System.out::println);
2. Stream中对于数字运算的支持
List intList = new ArrayList<>();
intList.add(20);
intList.add(19);
intList.add(7);
intList.add(8);
intList.add(86);
intList.add(11);
intList.add(3);
intList.add(20);
// skip() 中间操作,有状态,跳过部分数据
intList.stream().skip(3).forEach(System.out::println);
// limit() 中间操作,有状态,限制输出数据量
intList.stream().skip(3).limit(2).forEach(System.out::println);
// distinct() 中间操作,有状态,剔除重复的数据
intList.stream().distinct().forEach(System.out::println);
// sorted() 中间操作,有状态,排序
// max() 获取最大值
Optional optional = intList.stream().max((x, y)-> x-y);
System.out.println(optional.get());
// min() 获取最小值
// reduce() 合并处理数据
Optional optional2 = intList.stream().reduce((sum, x)-> sum + x);
System.out.println(optional2.get());
5. Lambda表达式在实际生产中的应用
5.1 Lambda表达式重构项目
可以使用Lambda表达式简化项目中的代码。
5.2 Lambda表达式和Stream性能问题
我们主要从两个方面进行性能比较:基本数据类型与复杂数据类型。
5.2.1 基本数据类型的性能比较
package java8;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Random;
/**
* {这里添加描述}
*
* @author funcy
* @date 2020-01-18 8:57 下午
*/
public class Test02 {
public static void main(String[] args) {
Random random = new Random();
// 1. 基本数据类型:整数
List integerList = new ArrayList();
for (int i = 0; i < 1000000; i++) {
integerList.add(random.nextInt(Integer.MAX_VALUE));
}
// 1) stream
testStream(integerList);
// 2) parallelStream
testParallelStream(integerList);
// 3) 普通for
testForLoop(integerList);
// 4) 增强型for
testStrongForLoop(integerList);
// 5) 迭代器
testIterator(integerList);
}
public static void testStream(List list) {
long start = System.currentTimeMillis();
Optional optional = list.stream().max(Integer::compare);
System.out.println(optional.get());
long end = System.currentTimeMillis();
System.out.println("testStream:" + (end - start) + "ms");
}
public static void testParallelStream(List list) {
long start = System.currentTimeMillis();
Optional optional = list.parallelStream().max(Integer::compare);
System.out.println(optional.get());
long end = System.currentTimeMillis();
System.out.println("testParallelStream:" + (end - start) + "ms");
}
public static void testForLoop(List list) {
long start = System.currentTimeMillis();
int max = Integer.MIN_VALUE;
for (int i = 0; i < list.size(); i++) {
int current = list.get(i);
if (current > max) {
max = current;
}
}
System.out.println(max);
long end = System.currentTimeMillis();
System.out.println("testForLoop:" + (end - start) + "ms");
}
public static void testStrongForLoop(List list) {
long start = System.currentTimeMillis();
int max = Integer.MIN_VALUE;
for (Integer integer : list) {
if (integer > max) {
max = integer;
}
}
System.out.println(max);
long end = System.currentTimeMillis();
System.out.println("testStrongForLoop:" + (end - start) + "ms");
}
public static void testIterator(List list) {
long start = System.currentTimeMillis();
Iterator it = list.iterator();
int max = it.next();
while (it.hasNext()) {
int current = it.next();
if (current > max) {
max = current;
}
}
System.out.println(max);
long end = System.currentTimeMillis();
System.out.println("testIterator:" + (end - start) + "ms");
}
}
运行结果如下:
2147480897
testStream:88ms
2147480897
testParallelStream:28ms
2147480897
testForLoop:9ms
2147480897
testStrongForLoop:11ms
2147480897
testIterator:15ms
5.2.2 复杂数据类型的性能
package java8;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Random;
/**
* {这里添加描述}
*
* @author funcy
* @date 2020-01-18 9:11 下午
*/
public class Test03 {
public static void main(String[] args) {
Random random = new Random();
List productList = new ArrayList<>();
for(int i = 0; i < 1000000; i++) {
productList.add(new Product("pro" + i, i, random.nextInt(Integer.MAX_VALUE)));
}
// 调用执行
testProductStream(productList);
testProductParallelStream(productList);
testProductForloop(productList);
testProductStrongForloop(productList);
testProductIterator(productList);
}
public static void testProductStream(List list) {
long start = System.currentTimeMillis();
Optional optional = list.stream().max((p1, p2)-> p1.hot - p2.hot);
System.out.println(optional.get());
long end = System.currentTimeMillis();
System.out.println("testProductStream:" + (end - start) + "ms");
}
public static void testProductParallelStream(List list) {
long start = System.currentTimeMillis();
Optional optional = list.stream().max((p1, p2)-> p1.hot - p2.hot);
System.out.println(optional.get());
long end = System.currentTimeMillis();
System.out.println("testProductParallelStream:" + (end - start) + "ms");
}
public static void testProductForloop(List list) {
long start = System.currentTimeMillis();
Product maxHot = list.get(0);
for(int i = 0; i < list.size(); i++) {
Product current = list.get(i);
if (current.hot > maxHot.hot) {
maxHot = current;
}
}
System.out.println(maxHot);
long end = System.currentTimeMillis();
System.out.println("testProductForloop:" + (end - start) + "ms");
}
public static void testProductStrongForloop(List list) {
long start = System.currentTimeMillis();
Product maxHot = list.get(0);
for (Product product : list) {
if(product.hot > maxHot.hot) {
maxHot = product;
}
}
System.out.println(maxHot);
long end = System.currentTimeMillis();
System.out.println("testProductStrongForloop:" + (end - start) + "ms");
}
public static void testProductIterator(List list) {
long start = System.currentTimeMillis();
Iterator it = list.iterator();
Product maxHot = it.next();
while(it.hasNext()) {
Product current = it.next();
if (current.hot > maxHot.hot) {
maxHot = current;
}
}
System.out.println(maxHot);
long end = System.currentTimeMillis();
System.out.println("testProductIterator:" + (end - start) + "ms");
}
}
class Product {
String name; // 名称
Integer stock; // 库存
Integer hot; // 热度
public Product(String name, Integer stock, Integer hot) {
this.name = name;
this.stock = stock;
this.hot = hot;
}
}
运行结果:
java8.Product@5f184fc6
testProductStream:63ms
java8.Product@5f184fc6
testProductParallelStream:15ms
java8.Product@5f184fc6
testProductForloop:16ms
java8.Product@5f184fc6
testProductStrongForloop:16ms
java8.Product@5f184fc6
testProductIterator:17ms
5.2.3 结论
jvm相关人员也对stream进行了一系列测,结果如下:
可以看到,随着核心数增加,并行Stream带来的性能提升是非常明显的。
最终,我们可以得到这样一个结论:对于简单数据的迭代处理,可以直接通过外部迭代进行操作,如果在性能上有一定的要求,可以使用并行stream进行操作;对于复杂对象的处理操作,stream的串行操作在性能上已经和普通的迭代相差无几,甚至超过了普通的迭代方式,完全可以用简洁的stream的语法来替换普通的迭代操作,如果在性能上有要求,可以直接选择并行stream操作以提升性能,并行stream在多核条件下,更能发挥其性能优势。
5.3 线程安全问题
这一节我们来看看并行stream(parallelStream)的线程安全:
package java8;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* {这里添加描述}
*
* @author funcy
* @date 2020-01-18 9:30 下午
*/
public class Test04 {
public static void main(String[] args) {
// 整数列表
List lists = new ArrayList();
// 增加数据
for (int i = 0; i < 1000; i++){
lists.add(i);
}
// 串行Stream
List list2 = new ArrayList<>();
lists.stream().forEach(x->list2.add(x));
System.out.println(lists.size());
System.out.println(list2.size());
// 并行Stream
List list3 = new ArrayList<>();
lists.parallelStream().forEach(x-> list3.add(x));
System.out.println(list3.size());
// stream的collect操作
List list4 = lists.parallelStream().collect(Collectors.toList());
System.out.println(list4.size());
}
}
运行结果如下:
1000
1000
994
1000
可以看到,lists.parallelStream().forEach(x-> list3.add(x)) 会引发线程安全问题,而lists.parallelStream().collect(Collectors.toList())不会引起线程安全问题。
关于stream的collect操作,官方文档有云:当并行 执行时,可以实例化,填充和合并多个中间结果,以便保持可变结构的隔离。因此,即使与非线程安全的数据结构(例如ArrayList)并行执行,并行还原也不需要额外的同步。
结论:并行stream的线程安全问题,在业务处理的过程中,主要通过自定义编码添加线程锁的方式,或者使用stream api中提供的线程安全的终端操作来完成执行过程。不过,在更多的场景中,如果我们遇到多线程问题,会直接使用线程安全的集合来规范数据源。