1. 函数式编程
1.1 简介
1.1.1 函数式编程的介绍
函数式编程是一种编程范式,她将计算视为函数的运算,并避免变化状态和可变数据。他是一种声明式编程范式,也就是说,编程是用表达式或者声明而不是语句来完成的
比起指令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程
1.1.2 函数式编程的特点
a. 纯函数:对函数的执行没有副作用,即在执行过程中没有带来状态的改变
b. 返回值仅仅依赖于输入参数,即只要输入参数唯一的时候,返回值也唯一
c. 高阶函数:函数的参数可以是一个或多个函数,返回值也可以是一个函数
2. Lambda表达式(Lambda Expression)
2.1 Lambda简介
2.1.1 概述
Lambda表达式,是JDK 8的新特性,也被称为箭头函数、匿名函数、闭包;它所体现的是一种轻量级函数式编程思想;在其表达式中,“->”符号是Lambda表达式的核心操作符号,符号的左边是操作参数,右边是操作表达式;
l
a
m
b
d
a
表
达
式
:
(
a
,
b
)
−
>
a
+
b
lambda 表达式: (a,b) -> a + b
lambda表达式:(a,b)−>a+b
2.1.2 MCAD模式问题
MCAD(Model Code as Data)开发模式,编码及数据,尽可能轻量级的将代码封装为数据,传统的解决方案是利用接口&实现类(或匿名内部类)来实现,但是传统的方法会有一些问题,比如
- 大量的重复代码产生的语法冗余;
- 在匿名内部类处理方式当中,this关键字在内部类型中它的变量绑定以及变量访问存在 很大的误区;
- 变量捕获,也就是在内部类型中当前作用域当中的变量处理会有一些特殊的要求
- 对数据的控制也显得没有那么友好
上述问题通过一段代码来看一下,JDK 8版本,开发工具IDEA,Maven工程(怎么创建工程这里就不多说了),注意创建完的Maven项目要把JDK的版本从默认1.7调到1.8或更高
需求环境:线程类的创建
/**
* Created by IntelliJ IDEA.
* 需求环境:线程类的创建
* @author xiren
* @date 2021/02/03 19:18
*/
public class ThreadDemo {
public static void main(String[] args) {
// 传统模式下,新线程的创建
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("threading......" + Thread.currentThread().getId());
}
}).start();
}
}
上面的代码即使在传统方式下一个新线程的创建,大家自己运行一下;可以看出来,在传统模式下,一个新线程的创建,我们只需要创建这个线程即可,在处理过程中和数据有关的主要集中在run方法中来处理,但这样,在语法语义上面就产生了冗余,这个时候我们在使用lambda表达式再来创建一个线程
/**
* Created by IntelliJ IDEA.
* 需求环境:线程类的创建
* @author xiren
* @date 2021/02/03 19:18
*/
public class ThreadDemo {
public static void main(String[] args) {
// JDK 8 Lambda表达式优化线程模式
new Thread(() -> {
System.out.println("lambda threading......" + hread.currentThread().getId());
}).start();
}
}
对比上下两块的代码,实现的是同一个功能,但是对比内部类的实现方式上,lambda主要包含了跟数据相关的内容,而其他语法语义的代码相比之下要少了许多;
2.1.3 注意
lambda不是解决未知问题的新技术,而是对现有解决方案的语义化优化,是否使用我们还是要根据实际需求和性能问题来考虑
2.2 Lambda表示式基础运用
2.2.1 函数式接口(function interface)
函数式接口,这是JDK 8在传统语义上面抽象出来的一个新的语义化术语;它本质上就是Java类型系统中的接口,是只包含了一个接口方法的特殊接口
为了表示和正确的描述函数式接口,JDK 8提供了独立的注解,语义化检测注解:@FunctionalInterface
下面这段代码,带我们认识一下函数式接口(为了后续好说明,每次的有改动的代码我都会加黑表明是哪个文件,过程中没有标记的便是没有改动)
接口类IUserCredential
/**
* Created by IntelliJ IDEA.
* 用户身份认证标记接口
* 在接口中只包含一个抽象化方法的接口,即称为函数化接口
* 当包含第二个未被实现的抽象化方法式,@FunctionalInterface注解就会报错
* @author xiren
* @date 2021/02/03 20:23
*/
@FunctionalInterface
public interface IUserCredential {
/**
* 通过账号验证用户身份信息
* @param username 用户账号
* @return 对应身份信息
*/
String verifyUser(String username);
}
然后我们来写个对应的实现类来完善一下,一边后面的内容使用
实现类UserCredentialImpl
import org.xiren.IUserCredential;
/**
* Created by IntelliJ IDEA.
* 用户身份认证标记实现类
* @author xiren
* @date 2021/02/03 21:11
*/
public class UserCredentialImpl implements IUserCredential {
/**
* 通过账号验证用户身份信息
* @param username 用户账号
* @return 对应身份信息
*/
@Override
public String verifyUser(String username) {
if("admin".equals(username)) {
return "系统管理员";
}else if ("manager".equals(username)) {
return "用户管理员";
}else {
return "其他用户";
}
}
}
测试方法
import org.xiren.impl.UserCredentialImpl;
/**
* Created by IntelliJ IDEA.
*
* @author xiren
* @date 2021/02/03 21:14
*/
public class App {
public static void main(String[] args) {
IUserCredential iUserCredential = new UserCredentialImpl();
System.out.println(iUserCredential.verifyUser("admin"));
}
}
2.2.2 函数式接口和lambda表达式之间的关系
默认接口方法
上面的代码帮我们快速的实现了一个用户身份信息的认证功能,但是当需求改动之后,比如说所有的用户认证,如果认证成功,那么我就要去获取到用户的身份信息,这个时候,按照我们之前的思路,当然式再在接口里面去写一个方法来帮我们实现这个功能;而JDK 8,我们可以直接对接口进行改造,直接上代码
接口类IUserCredential
/**
* Created by IntelliJ IDEA.
* 用户身份认证标记接口
* 在接口中只包含一个抽象化方法的接口,即称为函数化接口
* 当包含第二个未被实现的抽象化方法式,@FunctionalInterface注解就会报错
* @author xiren
* @date 2021/02/03 20:23
*/
@FunctionalInterface
public interface IUserCredential {
/**
* 通过账号验证用户身份信息
* @param username 用户账号
* @return 对应身份信息
*/
String verifyUser(String username);
/** 需求改动: 所有的用户验证,可以同时获取用户的验证信息[是否认证成功|成功~返回用户|null]*/
/**
* 获取用户身份信息
* @param username 用户账号
* @return 对应身份信息
*/
default String getCredential(String username) {
// 模拟方法
if("admin".equals(username)) {
return username + " + 系统管理员";
}else if ("manager".equals(username)) {
return username + " + 用户管理员";
}else {
return username + " + 其他用户";
}
}
}
测试方法
import org.xiren.impl.UserCredentialImpl;
/**
* Created by IntelliJ IDEA.
*
* @author xiren
* @date 2021/02/03 21:14
*/
public class App {
public static void main(String[] args) {
IUserCredential iUserCredential = new UserCredentialImpl();
System.out.println(iUserCredential.verifyUser("admin"));
}
}
这里我们在接口处利用default来定义了一个默认方法,来实现我们过去用户身份信息的功能,跟我们以往的再去建立一个接口方法,写一个对应的实现类有一些些的不同了,并且这种默认方法也可以被直接调用;所以我们可以通过这种默认方法的方式来给所有实现子类添加对应的一些方法,并且这种默认方法的添加对于函数时接口来说没有任何影响,因为默认方法不算是未实现的抽象方法;
静态接口方法
静态方法同样是对功能的拓展,但和默认方法不同的是,默认方法应用的场景是给所有的子类对象增加的一些方法,静态方法略有差异,这里还是先看代码
接口类IMessageFormat
/**
* Created by IntelliJ IDEA.
* 消息传输格式化转换接口
* @author xiren
* @date 2021/02/03 20:30
*/
@FunctionalInterface
public interface IMessageFormat {
/**
* 消息转换方法
* @param message 要转换的消息
* @param format 转换的格式
* @return 返回转换后的数据
*/
String format(String message, String format);
/** 新需求:验证当前的消息是否合法*/
/**
* 验证当前的消息合法性[静态方法]
* @param message 当前消息
* @return T or F
*/
static boolean verifyMessage(String message) {
if(message != null) {
return true;
}
return false;
}
}
实现类MessageFormat
import org.xiren.IMessageFormat;
/**
* Created by IntelliJ IDEA.
* 消息传输格式化转换实现类
* @author xiren
* @date 2021/02/03 21:58
*/
public class MessageFormatImpl implements IMessageFormat {
/**
* 消息转换方法
* @param message 要转换的消息
* @param format 转换的格式
* @return 返回转换后的数据
*/
@Override
public String format(String message, String format) {
System.out.println("消息转换...");
return message;
}
}
测试方法
import org.xiren.impl.UserCredentialImpl;
/**
* Created by IntelliJ IDEA.
*
* @author xiren
* @date 2021/02/03 21:14
*/
public class App {
public static void main(String[] args) {
// 测试UserCredential
IUserCredential iUserCredential = new UserCredentialImpl();
System.out.println(iUserCredential.verifyUser("admin"));
// 需求改动: 所有的用户验证,可以同时获取用户的验证信息[是否认证成功|成功~返回用户|null]
System.out.println(iUserCredential.getCredential("admin"));
// 测试MessageFormat
String message = "Hello";
// 调用静态方法
if(IMessageFormat.verifyMessage(message)) {
IMessageFormat messageFormat = new MessageFormatImpl();
messageFormat.format(message,"UTF-8");
}
}
}
这里我们能够看到,静态方法的使用同样对我们的函数式语法没有任何的影响,但是静态方法的调用会和默认方法有所差别;当然,默认方法,静态方法可以共存在一个函数式接口当中
特别提示一点,在Java中所有的对象都是直接或者简介继承自Object对象,自Object中继承过来的方法,即使是抽象方法,也不会影响我们函数式接口的语法语义
到了这里,你可能会疑惑,一个接口里面只能写一个实现类,那么我们要开发复杂功能的时候岂不是要写好多个接口,那这样子开发有什么意义;这就要说到lambda表达式,因为lambda表达式中只能操作一个方法,所以孕育而生的便是函数式接口,而lambda表达式的核心,就是一个函数式接口的实现,来我们看下面一段代码
测试方法
import org.xiren.impl.MessageFormatImpl;
import org.xiren.impl.UserCredentialImpl;
/**
* Created by IntelliJ IDEA.
*
* @author xiren
* @date 2021/02/03 21:14
*/
public class App {
public static void main(String[] args) {
// 测试UserCredential
IUserCredential iUserCredential = new UserCredentialImpl();
System.out.println(iUserCredential.verifyUser("admin"));
// 需求改动: 所有的用户验证,可以同时获取用户的验证信息[是否认证成功|成功~返回用户|null]
System.out.println(iUserCredential.getCredential("admin"));
// 测试MessageFormat
String message = "Hello";
// 调用静态方法
if(IMessageFormat.verifyMessage(message)) {
IMessageFormat messageFormat = new MessageFormatImpl();
messageFormat.format(message,"UTF-8");
}
// 匿名内部类来实现接口的实现方法
IUserCredential iUserCredential1 = new UserCredentialImpl() {
@Override
public String verifyUser(String username) {
return "admin".equals(username)?"管理员":"其他";
}
};
System.out.println(iUserCredential1.verifyUser("admin"));
System.out.println(iUserCredential1.verifyUser("manager"));
}
}
这里我们利用了匿名内部类来实现了对应接口的抽象方法,相比较之前的单独实现类,真个代码要显得更加的简洁,但是通过观察,不管是匿名实现类还是单独实现类,其中有关于数据操作的代码都只有一小部分,剩下的部分都可以算是冗余部分,这个就可以利用lambda表达式来优化
import org.xiren.impl.MessageFormatImpl;
import org.xiren.impl.UserCredentialImpl;
/**
* Created by IntelliJ IDEA.
*
* @author xiren
* @date 2021/02/03 21:14
*/
public class App {
public static void main(String[] args) {
// 测试UserCredential
IUserCredential iUserCredential = new UserCredentialImpl();
System.out.println(iUserCredential.verifyUser("admin"));
// 需求改动: 所有的用户验证,可以同时获取用户的验证信息[是否认证成功|成功~返回用户|null]
System.out.println(iUserCredential.getCredential("admin"));
// 测试MessageFormat
String message = "Hello";
// 调用静态方法
if(IMessageFormat.verifyMessage(message)) {
IMessageFormat messageFormat = new MessageFormatImpl();
messageFormat.format(message,"UTF-8");
}
// 匿名内部类来实现接口的实现方法
IUserCredential iUserCredential1 = new UserCredentialImpl() {
@Override
public String verifyUser(String username) {
return "admin".equals(username)?"管理员":"其他";
}
};
System.out.println(iUserCredential1.verifyUser("admin"));
System.out.println(iUserCredential1.verifyUser("manager"));
// lambda表达式,针对函数式接口的实现
IUserCredential iUserCredential2 = (String username) -> {
return "admin".equals(username)?"lambda管理员":"lambda其他";
};
System.out.println(iUserCredential2.verifyUser("admin"));
System.out.println(iUserCredential2.verifyUser("manager"));
}
}
相比较前面两种实现方法,lambda表达式的实现方式语义上显得更加简洁,语法上对于冗余代码的处理也显得更加优秀,而且这几种实现的结果没有任何区别。
2.2.3 常见的函数式接口
上面讲了lambda表达式和函数式接口之间的关系,其实在我们使用的JDK的内部也存在着大量的符合函数式编程的接口,就比如下面这些,当我们在使用的时候发现某个接口符合函数式接口的定义时,那我们都可以使用lambda表达式进行实现
java.lang.Runable // 多线程的实现接口
java.lang.Comparable // 数据比较的实现接口
java.lang.Comparator
java.io.FileFilter // 文件处理的接口
JDK 8中,提供了java.util.function这个包,在这个包里面提供了常用的函数式功能接口,有兴趣的朋友可以去看一下下面提到的方法的源码
场景一:接受参数对象T,经过处理后,返回一个Boolean类型的结果
示例:传入一个身份信息进行比对
使用的包为:java.util.function.Predicate
然后我们来简单的模拟一下上述的场景
import java.util.function.Predicate;
/**
* Created by IntelliJ IDEA.
* 场景一:Predicate
* @author xiren
* @date 2021/02/04 15:42
*/
public class CommonInterface {
public static void main(String[] args) {
Predicate<String> predicate = (String username) -> {
return "admin".equals(username);
};
System.out.println(predicate.test("admin"));
System.out.println(predicate.test("manager"));
}
}
相比于上面我们自定义的接口来实现功能,JDK提供的接口,极大的便利了我们的开发
场景二:接受参数对象T,经过处理后,不返回结果
示例:接受一段文字消息并发送
使用的包为:java.util.function.Consumer
然后我们来简单的模拟一下上述的场景
import java.util.function.Consumer;
/**
* Created by IntelliJ IDEA.
* 场景二:Consumer
* @author xiren
* @date 2021/02/04 15:42
*/
public class CommonInterface {
public static void main(String[] args) {
Consumer<String> consumer = (String message) -> {
System.out.println("要发送的消息:" + message);
System.out.println("消息发送完成");
};
consumer.accept("这是Consumer接口...");
}
}
场景三:接受参数对象T,经过处理后,返回一个对象R
示例:接收到了性别字符串转化为0,1存入数据库
使用的包为:java.util.function.Function<T,R>
然后我们来简单的模拟一下上述的场景
import java.util.function.Function;
/**
* Created by IntelliJ IDEA.
* 场景三:Function
* @author xiren
* @date 2021/02/04 15:42
*/
public class CommonInterface {
public static void main(String[] args) {
Function<String,Integer> function = (String gender) -> {
return "male".equals(gender)?1:0;
};
System.out.println(function.apply("male"));
System.out.println(function.apply("famale"));
}
}
场景四:不接受参数,提供T对象的创建工厂
示例:调用接口,来随机生成系统密钥
使用的包为:java.util.function.Supplier
然后我们来简单的模拟一下上述的场景
import java.util.UUID;
import java.util.function.Supplier;
/**
* Created by IntelliJ IDEA.
* 场景四:Supplier
* @author xiren
* @date 2021/02/04 15:42
*/
public class CommonInterface {
public static void main(String[] args) {
Supplier<String> supplier = () -> {
return UUID.randomUUID().toString();
};
System.out.println(supplier.get());
System.out.println(supplier.get());
System.out.println(supplier.get());
}
}
场景五:接受参数对象T,返回参数对象T
示例:对用户发来的图片进行处理并返回
使用的包为:java.util.function.UnaryOperator
然后我们来简单的模拟一下上述的场景
import java.util.function.UnaryOperator;
/**
* Created by IntelliJ IDEA.
* 场景五:UnaryOperator
* @author xiren
* @date 2021/02/04 15:42
*/
public class CommonInterface {
public static void main(String[] args) {
UnaryOperator<String> unaryOperator = (String image) -> {
image += "[100*200]";
return image;
};
System.out.println(unaryOperator.apply("原图--"));
}
}
场景六:接受两个参数对象T,返回一个参数对象T
示例:两个数据之间的比对,然后根据规则拿到一个数据
使用的包为:java.util.function.BinaryOperator
然后我们来简单的模拟一下上述的场景
import java.util.function.BinaryOperator;
/**
* Created by IntelliJ IDEA.
* 场景六:BinaryOperator
* @author xiren
* @date 2021/02/04 15:42
*/
public class CommonInterface {
public static void main(String[] args) {
BinaryOperator<Integer> binaryOperator = (Integer a, Integer b) -> {
return a>b?a:b;
};
System.out.println(binaryOperator.apply(12,13));
}
}
以上六个就是一些比较常见的函数式接口,当然还有很多的其他接口,大家可以自行去看
2.2.4 lambda表达式基本语法
基本语法
lambda表达式分为四个部分:
1)声明:就是和lambda表达式绑定的接口类型;
2)参数:包含在一对圆括号中,和绑定的接口中的抽象方法中的参数个数及顺序一致;
3)操作符:lambda表达式的操作符为“->”
4)执行代码块:包含在一对大括号中,出现在操作符的右侧
[接口声明] = (参数) -> {执行代码块};
注意:
a.lambda表达式,必须与接口进行绑定
b.lambda表达式的参数,可以附带0到n个参数,括号中的参数类型可以不用指定,jvm在运行的时候,会自动的根据绑定的抽象方法中的参数进行推导
c.lamdba表达式的返回值,如果代码块只有一行,并且没有大括号,不用写return关键字,单行代码的执行结果会自动返回;如果添加了大括号,或者有多行代码,不许通过return关键字返回执行结果
a.不带参数,没有返回值的lambda表达式
public class LambdaDemo {
public static void main(String[] args) {
ILambda1 lambda1 = () -> {
System.out.println("这是没有参数,没有返回值的lambda表达式");
};
/** 当执行代码块部分只有一条语句的时候,可以省略大括号,简写为
ILambda1 lambda1 = () -> System.out.println("这是没有参数,没有返回值的lambda表达式");*/
lambda1.test1();
}
/** 没有参数,没有返回值的lambda表达式绑定的接口*/
interface ILambda1 {
/** 测试方法1*/
void test1();
}
}
b.带参数的,没有返回值的lambda表达式
public class LambdaDemo {
public static void main(String[] args) {
Ilambda2 lambda2 = (String n, int a) -> {
System.out.println(n + "say: my age is " + a);
};
/**lambda表达式在写的时候,参数部分可以不指定参数类型,lambda表达式会根据内部方法来确定参数类型
Ilambda2 lambda2 = (n, a) -> System.out.println(n + "say: my age is " + a);*/
lambda2.test2("xiren", 24);
}
/** 带参数,没有返回值的lambda表达式绑定的接口*/
interface Ilambda2 {
/** 测试方法2*/
void test2(String name, int age);
}
}
c.带参数,带返回值的lambda表达式
public class LambdaDemo {
public static void main(String[] args) {
Ilambda3 lambda3 = (a, b) -> {
return a+b;
};
/**如果代码块只有一行,并且省略大括号,那么可以简化为
Ilambda3 lambda3 = (a, b) -> a+b;*/
System.out.println(lambda3.test3(11,22));
}
/** 带参数,带返回值的lambda表达式绑定的接口*/
interface Ilambda3 {
/** 测试方法3*/
int test3(int a, int b);
}
}
2.2.5 捕获和检查
变量捕获
/**
* Created by IntelliJ IDEA.
* 变量捕获
* @author xiren
* @date 2021/02/04 18:31
*/
public class Test {
/**
* lambda中的变量操作,优化了匿名内部类中this关键字,不再单独建立对象作用域
* 表达式本事就是所属对象的一部分,在语法语义上使用更加简洁
*/
String s1 = "全局变量";
public static void main(String[] args) {
Test test = new Test();
// 匿名内部类测试
test.testInnerCase();
// lambda测试
test.testLambda();
}
/** 1. 匿名内部类中对于变量的访问*/
public void testInnerCase() {
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,不能对局部变量进行数据修改
// 访问内部变量
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); //同样不能进行数据修改,默认推导变量的修饰符:final
// 访问内部变量
System.out.println(s3);
s3 = "内部变量lambda直接修改";
System.out.println(s3);
}).start();
}
}
类型检查
import java.util.ArrayList;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* 类型检查
* @author xiren
* @date 2021/02/04 22:35
*/
public class CheckTest {
public static void test(MyInterface<String, List> myInterface){
List<String> list = myInterface.strategy("Hello", new ArrayList());
System.out.println(list);
}
public static void main(String[] args) {
test((x, y) -> {
y.add(x);
return y;
});
/**类型检查
(x,y)->{..} --> test(param) --> param==MyInterface --> lambda表达式 -->MyInterface类型
这个就是对于lambda表达式的类型检查,MyInterface接口就是lambda表达式的目标类型(target typing)
* 参数类型检查
(x,y)->{..} --> MyInterface.strategy(T t, R r) --> MyInterface<String, List> myInterface
--> T==String, R==List --> lambda表达式 --> (x,y)==strategy(T t, R r) --> x==T==String, y==R==List
这个就是对于lambda表达式的参数的类型检查,jvm会在运行中按照参数的顺序进行检查并匹配相对应的方法
*/
}
}
@FunctionalInterface
interface MyInterface<T, R> {
/**
* 泛型函数接口
* @param t 泛型T
* @param r 泛型R
* @return 返回泛型
*/
R strategy (T t, R r);
}
2.2.6 方法重载与运行原理
方法重载
/**
* Created by IntelliJ IDEA.
* 方法重载
* @author xiren
* @date 2021/02/04 23:48
*/
public class ReloadTest {
public static void main(String[] args) {
ReloadTest test = new ReloadTest();
/** 匿名内部类实现方法的重载*/
test.lambdaMethod(new Param1(){
@Override
public void outInfo(String info) {
System.out.println(info);
}
});
test.lambdaMethod(new Param2(){
@Override
public void outInfo(String info) {
System.out.println("---------");
System.out.println(info);
}
});
/** lambda表达式实现重载*/
/**
* lambda表达式存在类型检查 --> 自动推导lambda表达式的目标类型
* lambdaMethod(() -> {});直接使用 --> 方法为重载方法,存在两种类型的参数
* --> Param1 函数式接口
* --> Param2 函数式接口
调用方法 --> 传递lambda表达式 --> 自动检查 --> 出现混淆
*/
test.lambdaMethod((String info) -> {
System.out.println(info);
});
}
interface Param1 {
/**
* 接口1方法
* @param info
*/
void outInfo(String info);
}
interface Param2 {
/**
* 接口2方法
* @param info
*/
void outInfo(String info);
}
/** 定义重载方法*/
public void lambdaMethod(Param1 param1) {
param1.outInfo("Hello , param1");
}
public void lambdaMethod(Param2 param2) {
param2.outInfo("Hello , param2");
}
}
在代码的中部利用lambda表达式实现重载时,由于自动检查而出现的报错,所以这种情况下建议使用匿名实现类来实现方法的重载
底层运行原理
Lambda表达式在JVM底层解析成私有静态方法和匿名内部类型,通过实现接口的匿名内部类型中接口方法调用静态实现方法,完成Lambda表达式的执行,有兴趣的可以去通过命令控制台去反向解析class文件。
2.3 Lambda表达式高级运用
2.3.1 方法引用
方法引用是结合Lambda表达式的一种语法特性,本质上就是对方法调用的简化,方法引用和函数式接口绑定,在使用的时候会创建函数式接口的实例
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* 方法引用
* @author xiren
* @date 2021/02/05 10:20
*/
public class Test2 {
public static void main(String[] args) {
/**
* 1.静态方法引用的使用
* 类型名称.方法名称() --> 类型名称::方法名称
* 2.实例方法引用的使用
* 创建类型对应的对象 --> 对象应用::实例方法名称
*/
// 存储Person对象的列表
List<Person> people = new ArrayList<>();
people.add(new Person("tom","男",12));
people.add(new Person("jerry","女",22));
people.add(new Person("steve","男",18));
people.add(new Person("alpha","女",15));
people.add(new Person("spider","男",28));
// 1.匿名内部类实现方式
Collections.sort(people, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
});
// 2.lambda表达式的实现
Collections.sort(people, (p1, p2) -> p1.getAge()-p2.getAge());
// 3.静态方法引用
Collections.sort(people, Person::compareByAge);
// 4.实例方法的引用
PersonUtil util = new PersonUtil();
Collections.sort(people, util::compareByName);
System.out.println("tom".hashCode());
System.out.println("jerry".hashCode());
System.out.println(people);
// 构造方法引用:绑定函数式接口
IPerson person = Person::new;
Person p = person.initPerson("xiren", "男", 24);
System.out.println(p);
}
}
@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表达式能给我们的开发带来非常大的便利,但是在操作过程中,代码的可塑性会有一定程度的上的下降;在项目中可以针对一定的需求,来使用进行优化,但是切记不能为了使用简易的方法引用而去修改业务的实现逻辑
2.3.2 Stream原理和API
Stream流处理,JDK 8提供的新特性,这里的Stream并不是IO中的数据流stream,也不是集合元素,更不是数据结构,不能存储数据;这里的Stream是跟数据算法及运算有关的;
JDK 8中Stream流的引入,是针对多个集合,数组,容器,数据等存储批量数据的聚合操作时复杂和冗余流程而提出的一套新的API,可以结合lambda表达式通过并行和串行两种不同的方式,完成对批量数据的操作
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
/**
* Created by IntelliJ IDEA.
* Stream
* @author xiren
* @date 2021/02/05 16:04
*/
public class Test3 {
public static void main(String[] args) {
// 1.添加测试数据:存储多个账号的列表
List<String> accounts = new ArrayList<>();
accounts.add("tom");
accounts.add("jerry");
accounts.add("alpha");
accounts.add("tony");
accounts.add("ham");
// 1.1 业务要求:长度大于等于5的有效账号
for (String account : accounts) {
if (account.length() >= 5){
System.out.println("有效账号" + account);
}
}
// 1.2 迭代方式进行操作
Iterator<String> iterator = accounts.iterator();
while (iterator.hasNext()){
String account = iterator.next();
if (account.length() >= 5) {
System.out.println("Iterator有效账号" + account);
}
}
// 1.3 Stream结合lambda表达式,完成业务处理
List validAccounts = accounts.stream().filter(s -> s.length()>=5).collect(Collectors.toList());
System.out.println(validAccounts);
}
}
Stream API
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* Created by IntelliJ IDEA.
* Stream常见操作API介绍
* 1. 聚合操作
* 针对批量业务操作的处理
* 2. Stream的处理流程
* a. 数据源
* b. 数据转换
* c. 获取结果
* 3. 获取Stream对象
* a. 从集合或者数组中获取:
* Collection.Stream().
* 并行数据对象: Collection.parallelStream()
* 数组数据源: Arrays.Stream(T t)
* b. 缓冲流 BufferReader
* BufferReader.lines() --> stream()
* c. 静态工厂
* java.util.stream提供了对基本类型的支持: java.util .stream.IntStream.range()..
* java.nio.file.Files.walk()..来获取
* d. 自行构建
* java.util.Spliterator
* e. 更多的方式
* Random.ints() ; Pattern.splitAsStream()..
* 4. 中间操作API{intermediate}
* 操作结果是一个Stream,中间操作可以有一个或者多个连续的中间操作;
* 需要注意的是,中间操作只记录操作方式,不做具体执行,直到结束操作发生时,才做数据的最终执行
* 中间操作:业务逻辑处理
* 无状态:数据处理时,不受前置中间操作的影响;map/filter/peek/parallel/sequential/unordered
* 有状态:数据处理时,会受到前置中间操作的影响;distinct/sorted/limit/skip
* 5. 结束操作(终结操作){Terminal}
* 注意,一个Stream对象,只能有一个Terminal操作
* 这个操作一旦发生,就会真实处理数据,生成对应的处理结果,过程不可逆
* 短路操作(short-circuiting):当前的Stream对象在处理过程中,一旦满足某个条件,就可以得到结果;
* anyMatch/allMatch/noneMatch/findFirst/findAny等
* 常见场景:无限大的Stream --> 返回一个有限大的Stream,建议包含一个short-circuiting
* 非短路操作:当前的Stream对象必须处理完集合中所有的数据,才能得到处理结果;
* forEach/forEachOrdered/toArray/reduce/collect/min/max/count/iterator
* @author xiren
* @date 2021/02/05 16:50
*/
public class Test4 {
public static void main(String[] args) {
/** 1. 批量数据(多个数据,数组,列表,集合等等) --> Stream对象*/
// 多个数据
Stream stream1 = Stream.of("admin", "tom", "jerry");
// 数组
String[] strArrays = new String[] {"alpha", "nike"};
Stream stream2 = Arrays.stream(strArrays);
// 列表
List<String> list = new ArrayList<>();
list.add("一");
list.add("二");
list.add("三");
list.add("亖");
Stream stream3 = list.stream();
// 集合
Set<String> set = new HashSet<>();
set.add("一");
set.add("二");
set.add("三");
set.add("亖");
Stream stream4 = set.stream();
// Map
Map<String, Integer> map = new HashMap<>();
map.put("tom", 1200);
map.put("jerry", 1500);
map.put("tony", 1800);
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. Stream对象 --> 转换得到指定类型的数据*/
// 数组
Object[] objects = stream2.toArray(String[]::new);
System.out.println(objects);
// 字符串
String string = stream1.collect(Collectors.joining()).toString();
System.out.println(string);
// List
List<String> listX = (List<String>) stream3.collect(Collectors.toList());
System.out.println(listX);
// 集合
Set<String> setX = (Set<String>) stream4.collect(Collectors.toSet());
System.out.println(setX);
// Map
Map<String, String> mapX = (Map<String, String>) stream5.collect(Collectors.toMap(x ->x, y->"value" + y));
System.out.println(mapX);
/** 4. Stream常见API操作*/
List<String> accountList = new ArrayList<>();
accountList.add("赵一1");
accountList.add("钱二");
accountList.add("孙三3");
accountList.add("李四");
accountList.add("周五5");
accountList.add("吴六");
accountList.add("郑七7");
// 在每个数据前面增加前缀 map()中间操作 map()方法接受一个Function接口
accountList = accountList.stream().map(x -> "百家姓:" + x).collect(Collectors.toList());
// filter()添加过滤条件,过滤符合条件的用户
accountList = accountList.stream().filter(x -> x.length()>2).collect(Collectors.toList());
// forEach 增强型循环
accountList.forEach(x -> System.out.println("forEach -> " + x));
// peak()中间操作 ,迭代数据完成数据的依次处理
accountList.stream()
.peek(x -> System.out.println("peek1 = " + x))
.peek(x -> System.out.println("peek2 = " + x))
.forEach(System.out::println);
accountList.forEach(System.out::println);
/** 5. Stream中对于数字运算的支持*/
List<Integer> integerList= new ArrayList<>();
integerList.add(20);
integerList.add(30);
integerList.add(40);
integerList.add(50);
integerList.add(60);
integerList.add(70);
integerList.add(80);
integerList.add(90);
integerList.add(10);
// skip()中间操作,有状态 , 放过一部分数据
integerList.stream().skip(3).forEach(System.out::println);
// limit()中间操作,有状态,限制输出数据量
integerList.stream().skip(3).limit(2).forEach(System.out::println);
// distinct()中间操作,有状态,剔除重复的数据
integerList.stream().distinct().forEach(System.out::println);
// sorted()中间操作,有状态,排序
// max() 获取最大值;min() 获取最小值
Optional<Integer> optional = integerList.stream().max((x, y) -> x - y);
System.out.println(optional.get());
// reduce() 合并处理数据
Optional<Integer> reduce = integerList.stream().reduce((sum, x) -> sum + x);
System.out.println(reduce.get());
}
}
2.3.3 代码重构和线程安全及性能
性能测试
import java.util.*;
/**
* Created by IntelliJ IDEA.
* 性能测试
* @author xiren
* @date 2021/02/06 10:18
*/
public class PerformanceTest {
public static void main(String[] args) {
Random random = new Random();
// 1. 基本数据类型:整数
List<Integer> integerList = new ArrayList<>();
for (int i = 0; i < 1000000; i++) {
integerList.add(random.nextInt(Integer.MAX_VALUE));
}
// 1.1 Stream
testStream(integerList);
// 1.2 parallelStream
testParallelStream(integerList);
// 1.3 普通for循环
testForLoop(integerList);
// 1.4 增强for循环
testStrongForLoop(integerList);
// 1.5 迭代器
testIterator(integerList);
/**
* 运行结果
* testStream:44ms
* testParallelStream:31ms
* testForLoop:6ms
* testStrongForLoop:7ms
* testIterator:8ms
*/
// 2. 复杂数据类型:对象
List<Product> productList = new ArrayList<>();
for (int i = 0; i < 1000000 ; i++) {
productList.add(new Product("pro" + i, i, random.nextInt(Integer.MAX_VALUE)));
}
// 2.1 Stream
testProductStream(productList);
// 2.2 parallelStream
testProductParallelStream(productList);
// 2.3 普通for循环
testProductForLoop(productList);
// 2.4 增强for循环
testProductStrongForLoop(productList);
// 2.5 迭代器
testProductIterator(productList);
/**
* 运行结果
* testProductStream:21ms
* testProductParallelStream:18ms
* testProductForLoop:16ms
* testProductStrongForLoop:16ms
* testIterator:16ms
*/
}
public static void testStream(List<Integer> list) {
long start = System.currentTimeMillis();
Optional<Integer> 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<Integer> 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> iterator = list.iterator();
int max = iterator.next();
while (iterator.hasNext()) {
int current = iterator.next();
if(current > max) {
max = current;
}
}
System.out.println(max);
long end = System.currentTimeMillis();
System.out.println("testIterator:" + (end-start) + "ms") ;
}
public static void testProductStream(List<Product> list) {
long start = System.currentTimeMillis();
Optional<Product> 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<Product> optional = list.parallelStream().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> iterator = list.iterator();
Product maxHot = iterator.next();
while (iterator.hasNext()) {
Product current = iterator.next();
if(current.hot > maxHot.hot) {
maxHot = current;
}
}
System.out.println(maxHot);
long end = System.currentTimeMillis();
System.out.println("testIterator:" + (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性能很差,而并行的性能就要好很多,甚至接近了普通for循环;面对复杂对象时,串行Stream还是很慢,而并行Stream已经和普通for循环很接近,甚至时超过for循环,和增强for循环以及迭代器也很接近了;
结论:在处理简单数据的时候可以直接使用普通迭代,但是在处理复杂事务的时候或者对性能有一定的要求,可以选用并行Stream来处理;
并行线程安全
看原理图,其本质就是将一个操作列中的每个部分拆分成了多个子任务,通过多个线程执行的过程 ,最终在将多个线程执行的小过程进行合并的过程;
如果是串行Stream,可以通过自定义多线程,程序中的逻辑代码,进行数据的同步,来完成数据访问控制;
但是并行Stream引发的多线程,对于数据源就可能存在着安全性的问题,看下面的代码
import java.util.ArrayList;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* 线程安全
* @author xiren
* @date 2021/02/06 14:10
*/
public class SafeTest {
public static void main(String[] args) {
/** 整数列表的数据复制来完成并行Stream线程是否安全的操作*/
List<Integer> list= new ArrayList<>();
for (int i = 0; i < 100000; i++) {
list.add(i);
}
/** 将list中的数据转移到另外一个list当中*/
List<Integer> integerListStream = new ArrayList<>();
List<Integer> integerListParallelStream = new ArrayList<>();
// 串行Stream
list.stream().forEach(x -> integerListStream.add(x));
// 并行Stream
list.parallelStream().forEach(x -> integerListParallelStream.add(x));
System.out.println("list.size: " + list.size());
System.out.println("integerListStream.size: " + integerListStream.size());
System.out.println("integerListParallelStream.size: " + integerListParallelStream.size());
/**
* 运行结果
* list.size: 100000
* integerListStream.size: 100000
* integerListParallelStream.size: 34994
*/
}
}
并行Stream在处理过程中产生了数据的丢失,这个就是因为当前的并行Stream操作的集合并不是线程安全的,所以在过程中,多个线程访问共享数据出现了冲突,最终导致数据的丢失,多次运行甚至可能会出现数据溢出的现象
如何处理这个问题,官方文档也有说明:https://docs.oracle.com/javase/tutorial/collections/streams/parallelism.html#reference
// 解决方法
List<Integer> integerListSolution = list.parallelStream().collect(Collectors.toList());
System.out.println("integerListSolution.size: " + integerListSolution.size());
好了,到这里,lambda的知识就差不多了
祝各位码上无ERROR,键盘无BUG!!