一、什么是Lambda表达式
1. Lambda表达式概述
- Lambda表达式也被称为箭头函数、匿名函数、闭包。
- Lambda表达式体现的是轻量级函数式编程思想
- “->” 符号是Lambda表达式核心操作符号,符号左侧是操作参数,符号右侧是操作表达式
- JDK8新特性
2. MCAD模式(Model Code As Data)
- Model Code As Data,编码及数据,尽可能轻量级的将代码封装为数据。
- 解决方案:接口 & 实现类(匿名内部类)
- 存在问题:语法冗余、this关键字、变量捕获、数据控制等
3. 问题及优化
- 需求环境:线程类的创建
- 解决方案:匿名内部类实现 / lambda表达式实现
- 示例代码:
public class Demo01 {
public static void main(String[] args) {
// 1. 传统模式下,新线程的创建
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("threading..." + Thread.currentThread().getId());
}
}).start();
// 2. jdk8新特性,lambda表达式优化线程模式
new Thread(()->{
System.out.println("lambda threading..." + Thread.currentThread().getId());
}).start();
}
}
Lambda表达式可以用来替代只有一个抽象方法的接口,这种接口被称为函数式接口(Functional Interface)。虽然函数式接口可以有默认方法和静态方法,但它们必须只有一个抽象方法。
Lambda表达式没有明确的类型,它们的类型是依据具体的上下文推断出来的。Lambda表达式的目标类型是指在表达式上下文中预期的类型,这通常是一个函数式接口。
4. 为什么要用Lambda
- 它不是解决未知问题的新技术。
- 对现有解决方案的语义化优化。
- 需要根据实际需求考虑性能问题。
二、Lambda表达式基础知识
1. 函数式接口(function interface)
- 函数式接口,就是Java类型系统中的接口
- 函数式接口,是只包含一个接口方法的特殊接口
- 语义化检测注解:@Functionalinterface
@Functionalinterface 作用:
明确函数式接口的意图: @FunctionalInterface注解清楚地表明了接口是为了与lambda表达式和方法引用一起使用而设计的。
编译器检查: 使用此注解后,编译器将会检查被注解的接口是否满足函数式接口的条件。如果接口包含多于一个抽象方法,编译器将会报错。
代码示例:
/**
* 用户身份认证标记接口
*/
@FunctionalInterface
public interface IUserCredential {
/**
* 通过用户账号,验证用户身份信息的接口
* @param username 要验证的用户账号
* @return 返回身份信息[系统管理员、用户管理员、普通用户]
*/
String verifyUser(String username);
}
/**
* 消息传输格式化转换接口
*/
@FunctionalInterface
public interface IMessageFormat {
/**
* 消息转换方法
* @param message 要转换的消息
* @param format 转换的格式[xml/json..]
* @return 返回转换后的数据
*/
String format(String message, String format);
}
1.1 接口默认方法和接口静态方法
- 在接口中除了包含抽象方法,也可以包含默认方法和静态方法。
增加了默认方法的代码示例(接口的默认方法是给所有实现子类的对象增加了一些通用方法):
/**
* 用户身份认证标记接口
*/
@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 + 普通会员用户";
}
}
}
import com.imooc.IUserCredential;
public class UserCredentialImpl implements IUserCredential {
@Override
public String verifyUser(String username) {
if ("admin".equals(username)) {
return "系统管理员";
} else if("manager".equals(username)) {
return "用户管理员";
}
return "普通会员";
}
}
public class App
{
public static void main( String[] args ) {
IUserCredential ic = new UserCredentialImpl();
System.out.println(ic.verifyUser("admin"));
System.out.println(ic.getCredential("admin"));
}
}
增加了静态方法的代码示例:
/**
* 消息传输格式化转换接口
*/
@FunctionalInterface
public interface IMessageFormat {
/**
* 消息转换方法
* @param message 要转换的消息
* @param format 转换的格式[xml/json..]
* @return 返回转换后的数据
*/
String format(String message, String format);
/**
* 消息合法性验证方法
* @param msg 要验证的消息
* @return 返回验证结果
*/
static boolean verifyMessage(String msg) {
if (msg != null) {
return true;
}
return false;
}
}
import com.imooc.IMessageFormat;
public class MessageFormatImpl implements IMessageFormat {
@Override
public String format(String message, String format) {
System.out.println("消息转换...");
return message;
}
}
public class App
{
public static void main( String[] args ) {
String msg = "hello world";
if (IMessageFormat.verifyMessage(msg)) {
IMessageFormat format = new MessageFormatImpl();
format.format(msg, "json");
}
}
}
默认方法允许接口有实现,这有助于接口的扩展和方法的共享。静态方法则是属于接口的工具方法,它们不依赖于对象的状态。
函数式语义语法只要求在当前接口中有一个被实现的抽象方法即可,默认方法和静态方法不在其中。
- 来自Object继承的方法
/**
* 消息传输格式化转换接口
*/
@FunctionalInterface
public interface IMessageFormat {
/**
* 消息转换方法
* @param message 要转换的消息
* @param format 转换的格式[xml/json..]
* @return 返回转换后的数据
*/
String format(String message, String format);
String toString(); // 与java.lang.Object类中的toString()方法相同
}
当我们在函数式接口中定义一个抽象方法时,如果这个方法签名与java.lang.Object类中的某个公共方法相同,那么它实际上并不会增加接口的抽象方法数量。这是因为任何实现该接口的类都将从Object继承这些方法的实现,因此接口中的对应方法被视为已经实现。
这意味着即使我们在函数式接口中声明了与Object类中的公共方法具有相同签名的抽象方法(例如toString()、equals()、hashCode()等),编译器也不会报错,因为这些方法在任何实现类中都会有对应的实现,所以这些接口仍然是有效的函数式接口。
1.2 Lambda表达式和函数式接口的关系
- 函数式接口,只包含一个操作方法。
- Lambda表达式,只能操作一个方法。
- Java中的Lambda表达式,核心就是一个函数式接口的实现。
匿名内部类和lambda表达式的代码对比示例:
public class App
{
public static void main( String[] args ) {
// 匿名内部类,实现接口的抽象方法
IUserCredential ic = new IUserCredential() {
@Override
public String verifyUser(String username) {
return "admin".equals(username)?"管理员":"会员";
}
};
System.out.println(ic.verifyUser("manager"));
System.out.println(ic.verifyUser("admin"));
// lambda表达式,针对函数式接口的简单实现
IUserCredential ic2 = (String username) -> {
return "admin".equals(username)?"lbd管理员": "lbd会员";
};
System.out.println(ic2.verifyUser("manager"));
System.out.println(ic2.verifyUser("admin"));
2. 常见的函数式接口
常见的函数式接口:
- java.lang.Runable
- java.lang.Comparable
- java.lang.Comparator
- java.io.FileFilter
jdk8提供了java.util.function包,提供了常用的函数式功能接口,这些接口是专门为Lambda表达式和方法引用设计的。具体来说,java.util.function包中的接口可以用于创建匿名函数,这些匿名函数可以作为参数传递给方法,也可以作为方法的返回值。这种方式使得编写更具表达性、更灵活的代码成为可能。
2.1 Predicate接口
- java.util.function.Predicate
- 接收参数对象T,返回一个boolean类型结果
代码示例:
Predicate<String> pre = (String username) -> {
return "admin".equals(username);
};
System.out.println(pre.test("manager"));
System.out.println(pre.test("admin"));
2.2 Comsumer接口
- java.util.function.Comsumer
- 接收参数对象T,不返回结果
代码示例:
Consumer<String> con = (String message) -> {
System.out.println("要发送的消息:" + message);
System.out.println("消息发送完成");
};
con.accept("hello");
con.accept("lambda expression.");
2.3 Function接口
- java.util.function.Function<T, R>
- 接收参数对象T,返回结果对象R
代码示例:
Function<String, Integer> fun = (String gender) -> {
return "male".equals(gender)?1:0;
};
System.out.println(fun.apply("male"));
System.out.println(fun.apply("female"));
2.4 Supplier接口
- java.util.function.Supplier
- 不接受参数,提供T对象的创建工厂
代码示例:
Supplier<String> sup = () -> {
return UUID.randomUUID().toString();
};
System.out.println(sup.get());
System.out.println(sup.get());
System.out.println(sup.get());
2.5 UnaryOperator接口
- java.util.function.UnaryOperator
- 接收参数对象T,返回结果对象T
代码示例:
UnaryOperator<String> uo = (String img)-> {
img += "[100x200]";
return img;
};
System.out.println(uo.apply("原图--"));
2.6 BinaryOperator接口
- java.util.function.BinaryOperator
- 接受两个T对象,返回一个T对象结果
代码示例:
BinaryOperator<Integer> bo = (Integer i1, Integer i2) -> {
return i1 > i2? i1: i2;
};
System.out.println(bo.apply(12, 13));
2.7 总结
java.util.function提供了大量的函数式接口
- Predicate 接收参数T对象,返回一个boolean类型结果
- Consumer 接收参数T对象,没有返回值
- Function 接收参数T对象,返回R对象
- Supplier 不接受任何参数,直接通过get()获取指定类型的对象
- UnaryOperator 接口参数T对象,执行业务处理后,返回更新后的T对象
- BinaryOperator 接口接收两个T对象,执行业务处理后,返回一个T对象
3. Lambda语法
3.1 基本语法
主要包含4个部分
- 声明:就是和lambda表达式绑定的接口类型
- 参数:包含在一对圆括号中,和绑定的接口中的抽象方法中的参数个数及顺序一致。
- 操作符:->
- 执行代码块:包含在一对大括号中,出现在操作符号的右侧
具体格式:
[接口声明] = (参数) -> {执行代码块};
代码示例:
// 没有参数,没有返回值的lambda表达式绑定的接口
interface ILambda1{
void test();
}
// 带有参数,没有返回值的lambda表达式
interface ILambda2{
void test(String name, int age);
}
// 带有参数,带有返回值的lambda表达式
interface ILambda3 {
int test(int x, int y);
}
ILambda1 i1 = () -> {
System.out.println("hello!");
System.out.println("welcome!");
};
i1.test();
ILambda1 i2 = () -> System.out.println("hello");
i2.test();
ILambda2 i21 = (String n, int a) -> {
System.out.println(n + "say: my year's old is " + a);
};
i21.test("jerry", 18);
ILambda2 i22 = (n, a) -> {
System.out.println(n + " 说:我今年" + a + "岁了.");
};
i22.test("tom", 22);
ILambda3 i3 = (x, y) -> {
int z = x + y;
return z;
};
System.out.println(i3.test(11, 22));
ILambda3 i31 = (x, y) -> x + y;
System.out.println(i31.test(100, 200));
- 如果在Lambda表达式的执行代码块中有1行以上代码,需要添加花括号。如果只有1行代码则可以省略花括号。
总结:
- lambda表达式,必须和接口进行绑定。
- lambda表达式的参数,可以附带0个到n个参数,括号中的参数类型可以不用指定,jvm在运行时,会自动根据绑定的抽象方法中的参数进行推导。
- lambda表达式的返回值,如果代码块只有一行,并且没有大括号,不用写return关键字,单行代码的执行结果,会自动返回。如果添加了大括号,或者有多行代码,必须通过return关键字返回执行结果。
3.2 变量捕获
- 匿名内部类中的变量捕获
- Lambda表达式中的变量捕获
public class App2 {
String s1 = "全局变量";
// 1. 匿名内部类型中对于变量的访问
public void testInnerClass() {
String s2 = "局部变量";
new Thread(new Runnable() {
String s3 = "内部变量";
@Override
public void run() {
// 访问全局变量
// System.out.println(this.s1);// this关键字~表示是当前内部类型的对象
System.out.println(s1);
System.out.println(s2);// 局部变量的访问,~不能对局部变量进行数据的修改[final]
// s2 = "hello";
System.out.println(s3);
System.out.println(this.s3);
}
}).start();
}
// 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();
}
public static void main(String[] args) {
App2 app = new App2();
app.testInnerClass();
app.testLambda();
}
}
Lambda表达式中的变量操作优化了匿名内部类中的this关键字,不再单独建立对象作用域,表达式本身就是所属对象类型的一部分,在语法语义上使用更加简洁。
3.3 类型检查
对于语法相同的Lambda表达式,JVM在运行过程中在底层通过解释及重构进行类型的自动推导,表现在代码中就是Lambda表达式的类型检查。
public class App3 {
public static void test(MyInterface<String, List> inter) {
List<String> list = inter.strategy("hello", new ArrayList());
System.out.println(list);
}
public static void main(String[] args) {
test(new MyInterface<String, List>() {
@Override
public List strategy(String s, List list) {
list.add(s);
return list;
}
});
test((x, y) -> {
y.add(x);
return y;
});
}
}
@FunctionalInterface
interface MyInterface<T, R> {
R strategy (T t, R r);
}
-
Lambda表达式类型检查
(x,y)->{…} --> test(param) --> param==MyInterface --> lambda表达式-> MyInterface类型
这个就是对于lambda表达式的类型检查,MyInterface接口就是lambda表达式的目标类型(target typing) -
Lambda表达式参数类型检查
(x,y)->{…} --> MyInterface.strategy(T r, R r)–> MyInterface<String, List> inter
–> T == String R == List --> lambda–> (x, y) == strategy(T t , R r)–> x == T == String y == R == List
3.4 方法重载和Lambda表达式
public class App4 {
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");
}
public static void main(String[] args) {
App4 app = new App4();
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表达式存在类型检查-> 自动推导lambda表达式的目标类型
lambdaMethod() -> 方法 -> 重载方法
-> Param1 函数式接口
-> Param2 函数式接口
调用方法-> 传递Lambda表达式-> 自动推导->
-> Param1 | Param2
*/
// 这里使用lambda表达式会报错
// app.lambdaMethod( (String info) -> {
// System.out.println(info);
// });
}
}
JVM对Lambda表达式的自动推导,在某些情况下限制了传统语法结构下的功能操作,如果在出现方法重载的类型中参数都是函数式接口的情况,请使用匿名内部类的方式来替代Lambda表达式。
4. Lambda运行原理
- Lambda表达式在JVM底层解析成私有静态方法和匿名内部类型。
- 通过实现接口的匿名内部类型中接口方法调用静态实现方法,完成Lambda表达式的执行。
public class App {
public static void main(String [] args) {
IMarkUp mu = (message) -> System.out.println(message);
mu.markUp("lambda!");
}
}
// 函数式接口
interface IMarkUp {
void markUp(String msg);
}
对于上述代码,使用javac
先编译生成class文件后,使用javap -p
查看类中所有访问级别的成员,可以看到如下结果:
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);
}
可以看到有一个生成的静态私有方法,这个静态私有方法就是lambda表达式的实现,也就是说在编译时在App类下生成了一个名为lambda$main$0
的私有静态方法。
在运行App时,可以执行如下选项java -Djdk.internal.lambda.dumpProxyClasses App
生成一个lambda表达式匿名内部类的字节码文件。
-Djdk.internal.lambda.dumpProxyClasses 是用来设置一个特殊的系统属性,该属性指导 JVM 在生成和使用 lambda 表达式时将动态生成的代理类(lambda 表达式背后的匿名类)保存到磁盘上。
使用javap -p
查看这个lambda表达式的匿名内部类字节码文件,可以看到如下结果:
final class App$$Lambda$1 implements IMarkUp {
private App$$Lambda$1();
public void markUp(java.lang.String);
}
这表示lambda表达式生成了一个实现了指定函数式接口的内部类型。
因此,对于最初的代码,在编译后的结果可以视为如下代码:
public class App {
public static void main(String [] args) {
IMarkUp mu = (message) -> System.out.println(message);
mu.markUp("lambda!"); // 等于 new App$$Lambda$1().markUp("lambda!");
}
/*
private static void lambda$main$0(java.lang.String message) {
System.out.println(message);
}
*/
/*
final class App$$Lambda$1 implements IMarkUp {
private App$$Lambda$1(){}
public void markUp(java.lang.String msg){
App.lambda$main$0(msg);
}
}
*/
}
// 函数式接口
interface IMarkUp {
void markUp(String msg);
}
总结:
- lambda表达式在编译后会生成私有的静态方法,方法体即为lambda表达式的执行代码体;
- 在编译后针对lambda表达式的目标接口生成一个内部类型实现该接口,在实现接口的类型的具体实现方法中完成对第一步生成的私有静态方法的具体执行过程的调用
- 在具体执行时,实际是
new
了一个第二步的内部类的对象,通过该对象调用它的实现方法,并将实际参数传递给它。
三、Lambda表达式高级扩展
1. 方法引用
- 方法引用是结合Lambda表达式的一种语法特性
方法引用是一种简化 lambda 表达式的写法,它允许你直接引用现有的方法或构造函数。方法引用可以被看作是一种特殊类型的 lambda 表达式,用于直接调用目标方法而不需要提供方法体。
(1)静态方法引用
类型名称.方法名称() --> 类型名称::方法名称
(2)实例方法引用
创建类型对应的一个对象 --> 对象引用::实例方法名称
(3)构造方法引用
类型对象的构建过程 --> 类型名称::new
public class Test {
public static void main(String[] args) {
// 存储Person对象的列表
List<Person> personList = new ArrayList<>();
personList.add(new Person("tom", "男", 16));
personList.add(new Person("jerry", "女", 15));
personList.add(new Person("shuke", "男", 30));
personList.add(new Person("beita", "女", 26));
personList.add(new Person("damu", "男", 32));
// 1. 匿名内部类实现方式
Collections.sort(personList, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
});
System.out.println(personList);
// 2. lambda表达式的实现方式
Collections.sort(personList, (p1, p2) -> p1.getAge() - p2.getAge());
// 3. 静态方法引用
Collections.sort(personList, Person::compareByAge);
// 4. 实例方法引用
PersonUtil pu = new PersonUtil();
Collections.sort(personList, pu::compareByName);
System.out.println("tom".hashCode());
System.out.println("jerry".hashCode());
System.out.println(personList);
// 5. 构造方法引用:绑定函数式接口
IPerson ip = Person::new;
Person person = ip.initPerson("jerry", "男", 22);
System.out.println(person);
}
}
@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();
}
}
class PersonUtil {
// 增加一个实例方法
public int compareByName(Person p1, Person p2) {
return p1.getName().hashCode() - p2.getName().hashCode();
}
}
interface IPerson {
// 抽象方法:通过指定类型的构造方法初始化对象数据
Person initPerson(String name, String gender, int age);
}
使用Lambda表达式会造成代码可读性下降,在实际项目中可以针对具体的功能需求,结合Lambda表达式在语法语义上进行优化,但是不能为了使用简易的方法引用去修改业务实现逻辑。
2. Stream API
2.1 Stream概述
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。
简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
特点:
- 不是数据结构,不会保存数据。
- 不会修改原来的数据源,它会将操作后的数据保存到另外一个对象中。(保留意见:毕竟peek方法可以修改流中元素)
- 惰性求值,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到执行终止操作的时候才会进行实际的计算。
代码示例:
public class Test2 {
public static void main(String[] args) {
// 1. 添加测试数据:存储多个账号的列表
List<String> accounts = new ArrayList<String>();
accounts.add("tom");
accounts.add("jerry");
accounts.add("beita");
accounts.add("shuke");
accounts.add("damu");
// 1.1. 业务要求:长度大于等于5的有效账号
for (String account : accounts) {
if (account.length() >= 5) {
System.out.println("有效账号:" + account);
}
}
// 1.2. 迭代方式进行操作
Iterator<String> it = accounts.iterator();
while(it.hasNext()) {
String account = it.next();
if (account.length() >= 5) {
System.out.println("it有效账号:" + account);
}
}
// 1.3. Stream结合lambda表达式,完成业务处理
List<String> validAccounts = accounts.stream().filter(s->s.length()>=5).collect(Collectors.toList());
System.out.println(validAccounts);
}
}
2.2 Stream原理
stream的处理流程
- 数据源
- 数据转换
- 获取结果
(1)获取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()..
(2)中间操作API(intermediate)
操作结果是一个Stream,中间操作可以有一个或者多个连续的中间操作,需要注意的是,中间操作只记录操作方式,不做具体执行,直到结束操作发生时,才做数据的最终执行。
中间操作:就是业务逻辑处理。
中间操作过程:
- 无状态:数据处理时,不受前置中间操作的影响。
map/filter/peek/parallel/sequential/unordered - 有状态:数据处理时,受到前置中间操作的影响。
distinct/sorted/limit/skip
(3)终结操作|结束操作(Terminal)
需要注意:一个Stream对象,只能有一个Terminal操作,这个操作一旦发生,就会真实处理数据,生成对应的处理结果。
终结操作:
- 非短路操作:当前的Stream对象必须处理完集合中所有数据,才能得到处理结果。
forEach/forEachOrdered/toArray/reduce/collect/min/max/count/iterator - 短路操作:当前的Stream对象在处理过程中,一旦满足某个条件,就可以得到结果。
anyMatch/allMatch/noneMatch/findFirst/findAny等
短路操作也被称为Short-circuiting,一般用于无限大的Stream-> 有限大的Stream。
总结:
3. 集合元素操作
3.1 类型转换:其他类型(创建/获取)-> Stream对象
public class Test1 {
public static void main(String[] args) {
// 1. 批量数据的数据源 -> Stream对象
// 多个数据
Stream stream = Stream.of("admin", "tom", "damu", 122, 333);
// 数组
String [] strArrays = new String[] {"xueqi", "biyao"};
Stream stream2 = Arrays.stream(strArrays);
// 列表
List<String> list = new ArrayList<>();
list.add("少林");
list.add("武当");
list.add("青城");
list.add("崆峒");
list.add("峨眉");
Stream stream3 = list.stream();
// 集合
Set<String> set = new HashSet<>();
set.add("少林罗汉拳");
set.add("武当长拳");
set.add("青城剑法");
Stream stream4 = set.stream();
// Map
Map<String, Integer> map = new HashMap<>();
map.put("tom", 1000);
map.put("jerry", 1200);
map.put("shuke", 1000);
Stream stream5 = map.entrySet().stream();
// 2. Stream对象对于基本数据类型的功能封装,针对频繁进行装箱拆箱的数据类型进行基本处理
// int / long / double
IntStream.of(new int[] {10, 20, 30}).forEach(System.out::println); // 无论经过多少中间操作,仅会进行一次装箱拆箱操作
IntStream.range(1, 5).forEach(System.out::println);
IntStream.rangeClosed(1, 5).forEach(System.out::println);
}
}
3.2 类型转换:Stream对象 -> 其他类型
public class Test1 {
public static void main(String[] args) {
Stream stream = Stream.of("admin", "tom", "damu");
// Stream对象 --> 转换得到指定的数据类型
// 注意:Stream对象只能被消费一次
// 数组
Object[] objx = stream.toArray(String[]::new);
// 字符串
String str = stream.collect(Collectors.joining()).toString();
System.out.println(str);
// 列表
List<String> listx = (List<String>) stream.collect(Collectors.toList());
System.out.println(listx);
// 集合
Set<String> setx = (Set<String>) stream.collect(Collectors.toSet());
System.out.println(setx);
// Map
Map<String, String> mapx = (Map<String, String>) stream.collect(Collectors.toMap(x->x, y->"value:"+y));
System.out.println(mapx);
}
}
3.3 Stream常见API操作
public class Test1 {
public static void main(String[] args) {
// Stream中常见的API操作
List<String> accountList = new ArrayList<>();
accountList.add("songjiang");
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() 中间操作,迭代数据完成对单个数据的依次处理过程
// 在处理时可以通过peek这种中间操作对需要对整个集合进行迭代的处理过程进行简化,让多个处理过程进行合并,对集合进行一次迭代来完成数据的处理过程
accountList.stream()
.peek(x -> System.out.println("peek 1: " + x)) // peek是中间操作
.peek(x -> System.out.println("peek 2:" + x))
.forEach(System.out::println); // forEach是终结操作
// Stream中对于数字运算的支持
List<Integer> 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());
}
}
4. 实际需求重构
(1)原始代码
Controller代码:
@RestController
@RequestMapping("/emp")
public class EmpController {
@Autowired
private EmpService empService;
@GetMapping("/list")
public List<Employee> getAllEmployee() {
return empService.getAllEmployee();
}
@GetMapping("/{id}")
public Employee getEmployeeById(@PathVariable String id) {
return empService.getEmplyeeById(id);
}
@GetMapping("/nickname/{nickname}")
public List<Employee> getEmployeeByNickname(@PathVariable String nickname) {
return empService.getEmployeeByNickname(nickname);
}
@GetMapping("/empname/{empname}")
public List<Employee> getEmployeeByName(@PathVariable("empname") String name) {
return empService.getEmployeeByName(name);
}
}
Service代码:
@Service
public class EmpService {
@Autowired
private EmpDAO empDAO;
/**
* 查询所有职员
* @return 查询到的职员
*/
public List<Employee> getAllEmployee() {
List<Employee> list = null;
try {
list = empDAO.findAll();
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
/**
* 根据编号查询职员
* @param id 职员编号
* @return 职员数据
*/
public Employee getEmplyeeById(String id) {
Employee employee = null;
try {
employee = empDAO.findById(id);
} catch (SQLException e) {
e.printStackTrace();
}
return employee;
}
/**
* 根据昵称获取职员数据
* @param nickname 职员昵称
* @return 返回职员数据
*/
public List<Employee> getEmployeeByNickname(String nickname) {
List<Employee> list = null;
try {
list = empDAO.findByStrategy(new IStrategy<Employee>() {
@Override
public Boolean test(Employee employee) {
return employee.getNickname().contains(nickname);
}
});
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
public List<Employee> getEmployeeByName(String name) {
List<Employee> list = null;
try {
list = empDAO.findByStrategy(new IStrategy<Employee>() {
@Override
public Boolean test(Employee employee) {
return employee.getEmpName().contains(name);
}
});
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
}
(2)使用Lambda重构后代码
Controller代码:
@RestController
@RequestMapping("/emp")
public class EmpController {
@Autowired
private EmpService empService;
@GetMapping("/list")
public List<Employee> getAllEmployee() {
return empService.getAllEmployee();
}
@GetMapping("/{id}")
public Employee getEmployeeById(@PathVariable String id) {
return empService.getEmplyeeById(id);
}
@GetMapping("/nickname/{nickname}")
public List<Employee> getEmployeeByNickname(@PathVariable String nickname) {
return empService.getEmployeeByLambda(employee->employee.getNickname().contains(nickname));
}
@GetMapping("/empname/{empname}")
public List<Employee> getEmployeeByName(@PathVariable("empname") String name) {
return empService.getEmployeeByLambda(employee->employee.getEmpName().contains(name));
}
}
Service代码:
@Service
public class EmpService {
@Autowired
private EmpDAO empDAO;
/**
* 查询所有职员
* @return 查询到的职员
*/
public List<Employee> getAllEmployee() {
List<Employee> list = null;
try {
list = empDAO.findAll();
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
/**
* 根据编号查询职员
* @param id 职员编号
* @return 职员数据
*/
public Employee getEmplyeeById(String id) {
Employee employee = null;
try {
employee = empDAO.findById(id);
} catch (SQLException e) {
e.printStackTrace();
}
return employee;
}
/**
* 按照条件查询职员数据的方法
* @param strategy 条件策略
* @return 符合条件的职员数据
*/
public List<Employee> getEmployeeByLambda(IStrategy<Employee> strategy) {
List<Employee> list = null;
try {
list = empDAO.findByStrategy(strategy);
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
}
可以通过Lambda表达式的特性,高度封装数据接口,将数据业务的处理逻辑上移交给业务调用者进行处理。数据提供者只需要管理数据接口即可,实现整个业务功能中数据逻辑以及业务处理方式的解耦。
5. 线程安全与性能
5.1 性能测试
基本数据类型(整数):
public class Test {
public static void main(String[] args) {
Random random = new Random();
List<Integer> integerList = new ArrayList<Integer>();
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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> list) {
long start = System.currentTimeMillis();
Iterator<Integer> 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");
}
运行结果:
复杂数据类型(对象):
public class Test {
public static void main(String[] args) {
Random random = new Random();
List<Product> 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<Product> 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<Product> 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<Product> 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<Product> 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<Product> list) {
long start = System.currentTimeMillis();
Iterator<Product> 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;
}
}
运行结果:
针对服务商用环境,社区技术人员进行了更加严格的测试,分别从基本数据类型构建的数组、字符串列表以及复杂对象列表进行了测试。
在多核CPU以及内存足够的情况下可以看到,Stream在处理复杂对象时带来了性能提升,同时随着CPU核数增加,并行Stream的处理方式提升更加明显。
最终结论:
- 对于简单数据类型的迭代处理可以直接通过外部迭代进行操作,如果在性能上有一定要求,可以选择并行Stream进行操作;
- 对于复杂对象列表的处理操作,Stream的串行操作和普通迭代相差无几,可以通过简洁的Stream语法来替代普通迭代操作,同时如果在性能上有一定要求,可以选择并行Stream进行操作;
- 并行Stream在多核环境下更能发挥处理优势。
5.2 线程安全
Stream并行运行原理:
并行Stream引发的多线程对数据源是否存在数据安全的问题?
示例代码:
public class Test2 {
public static void main(String[] args) {
// 整数列表
List<Integer> lists = new ArrayList<Integer>();
// 增加数据
for (int i = 0; i < 1000; i++){
lists.add(i);
}
// 串行Stream
List<Integer> list2 = new ArrayList<>();
lists.stream().forEach(x->list2.add(x));
System.out.println(lists.size());
System.out.println(list2.size());
// 并行Stream
List<Integer> list3 = new ArrayList<>();
lists.parallelStream().forEach(x-> list3.add(x));
System.out.println(list3.size());
//
List<Integer> list4 = lists.parallelStream().collect(Collectors.toList());
System.out.println(list4.size());
}
}
运行结果:
并行Stream在操作过程中使用的Collections本身就不是线程安全的,所以在多线程操作的情况下可能会因为多个线程的处理过程产生数据不一致的错误。在处理过程中,Collections集合本身提供了一些线程同步块,通过这些线程同步块可以实现对于数据的同步处理。需要注意的是,如果使用Collections集合本身提供的线程同步块,会引起线程竞争的问题,如果想避免线程竞争问题,将聚合操作和并行Stream一起使用能得到一个在非线程安全的情况下对于数据的处理过程,但是前提是在操作过程中对于非线程安全的集合中的数据不能进行修改。
在JDK文档对于forEach方法的描述中我们也可以看到类似的说明:
但是JDK也提供了一些线程安全的终端操作,如collect方法:
结论:
- Stream并行的线程安全问题,在业务处理的过程中主要通过自定义编码添加线程锁的方式或者使用Stream API中提供的线程安全的终端操作完成执行过程。但是在更多场景中,如果遇到多线程问题,会直接使用线程安全的集合来规范数据源。