第一章 Java为什么引入 Lmabda表达式
目的尽可能轻量级的将代码封装为数据
1.1 什么是Lambda表达式
Lambda表达式也被成为箭头函数、匿名函数、闭包
Lambda表达式体现的是轻量级函数式编程思想
‘->’符号是Lambda表达式的核心符号,符号左侧是操作参数,符号右侧是操作表达式
1.2 Model Code as Data
Model Code As Data,编码及数据,尽可能轻量级的将代码封装为数据
传统解决方案:接口&实现类(匿名内部类)
- 但传统方案存在的问题:
a 语法冗余(有很多和数据处理无关的代码)
b this关键字(匿名内部类中,this关键字在内部类型中,变量绑定和变量访问存在很大误区)
c 变量捕获(内部类型中,当前作用域中的变量的处理,会有一些特殊要求)
d 数据控制(数据量控制并不是非常友好)等
第二章 函数式接口的概述和定义
2.1函数式接口定义
函数式接口(functuon interface),就是Java类型系统中的接口
函数式接口,是只包含一个接口方法的特殊接口
语义化检测注解:@FunctionalInterface,用来检查函数式接口的合法性
语义化检测注解:@Functionallnterface这是用来检查函数式接口的合法性的注解
==============================================================
函数式接口定义总结:定义一个函数式接口只需要在接口中提供一个接口方法,并在接口上添加@Functionallnterface注解即可,如果添加了多个接口方法,@FunctionAllnterface就会报错,
那么此时也不再是函数式接口,但请注意在一个接口中,默认方法、静态方法、函数式接口是可以共同存在的
默认方法和静态方法使用
这是两个函数式接口例子如下:
定义一个函数式接口只需要在接口中提供一个接口方法,并在接口上添加@FunctionalInterface注解即可,如果添加了多个接口方法,则注解@FunctionalInterface就会报错,那么此时不再是函数式接口,但默认方法,静态接口是允许存在的
默认接口方法的特性
以用户身份认证标记接口为例创建一个实现类 是用来返回用户身份的接口方法
对当前代码进行测试
像以上代码当需求进行变动后,就比如说要求用户返回用户身份后还可以同时获取用户的身份信息,那么就需要修改所有的实现类代码,这是一种不好的行为,因为一个接口方法就应该做到单一职责,那么我们在不修改原本的实习类的方法下,还有哪些办法可以再获取用户的身高信息呢?
在接口里新增一个接口方法,但是此时该接口不再是函数式接口,就是一个普通接口,再根据着接口书写一个实现类,并重写接口的里面的方法,在实现类的方法里面编写需要 (不满足函数式接口)
在接口里面使用默认方法(满足函数式接口)
在接口里面使用静态方法(满足函数式接口)
这样我们就可以直接通过代码来调用默认方法
静态接口方法的特性
以消息发送接口为例,在接口中添加一个静态方法:验证消息格式是否合法
添加一个实现类,重写接口中的方法,节省时间并直接进行了测试
在format方法中,我们只是打印了一句话 消息转换成了------》Hello-World2001/6/12。,并返回了msg,这时候我们可以直接调用接口的静态方法来验证消息的合法性。通过执行结果可以看出,静态方法不会对函数式接口的语义也不会产生影响,总结:默认接口,函数式接口,静态接口可以在一个接口类中同时存在。
从Object中继承的方法不会影响函数式接口
由于java中的类都直接的或者间接的继承了Object类,所以从Object继承的方法,无论是否是抽象的,都不会影响你函数式接口的语义。
例如,在 IUserCredential 中添加一个来自object的方法 toString(),该函数式接口也是不会报错的
总结:默认接口,函数式接口,静态接口,从Object中继承的方法接口是可以在一个接口类中同时存在的。
Lambda表达式和函数式接口的关系
Lambda 只能操作一个方法 Java 中的Lambda表达式就是一个函数式接口的实现
实现接口方法的另一种方式是使用匿名内部类,
通过观察匿名内部类的方式实现接口方法和实现类实现接口的方式实现接口方法,就可以发现,其实和数据相关的代码只有
" return “admin”.equals(username) ? “系统管理员” : “普通会员”; " 这一行,其他的代码都是冗余代码,那么能不能对代码进行优化呢?这就要使用到JDK8中的Lambda表达式。
相比较前面的匿名内部类,Lambda表达式实现方式更为简洁
JDK 中常见的函数式接口
jdk8提供的常见函数式接口
java.util.function提供了大量的函数式接口
Predicate:接收参数T对象,返回一个Boolean类型结果
Consumer:接收参数T对象,没有返回值
Function:接收参数T对象,返回R对象
Supplier:不接收任何对象,只通过get()获取指定类型的对象
UnaryOperator:接收参数对象T,执行完业务后,返回更新后的T对象
BinaryOperator: 接收两个参数T对象,执行业务处理后,返回一个T对象
JDK8提供了java.util.function包,提供了常用的函数式功能接口
demo
1.java.util.function.Predicate
接收参数对象T,返回一个boolean类型结果,适合需要判断的场景
2.java.util.function.Consumer
接收参数T,不反回结果
3. java.util.function.Function<T,R>
接收一个参数对象T,返回结果对象R
4. java.util.function.Supplier
不接受参数,提供T对象的创建工厂
5. java.util.function.UnaryOperator
接收参数对象T,返回结果对象T ,常用于适配器模式
6. java.util.function.BinaryOperator
接收两个T对象,返回一个T对象的结果(使用场景:例如对两个对象进行比较,返回较大的结果)
总结:java.util.function提供了大量的函数式接口
Predicate 接收参数对象T,返回一个boolean类型结果
Consumer 接收参数T,不反回结果
Function 接收一个参数对象T,返回结果对象R
Supplier 不接受参数,提供T对象的创建工厂
UnaryOperator 接收参数对象T,返回结果对象T
BinaryOperator 接收两个T对象,返回一个T对象的结果
Lmabda表达式的基本语法
声明:就是 Lambda表达式绑定的接口类型
参数:包含在一对圆括号中,和绑定的接口中的抽象方法中的参数顺序一致。
操作符:->
执行代码块:包含在一对大括号中,出现在操作符的右侧
[接口声明] = (参数) ->{执行代码} 接口名 给接口取名 = (参数)->{执行代码}
package org.example.lambda;
public class LambdaTest {
@FunctionalInterface
interface TestLambda1{
void testLambda();
}
public static void main(String[] args) {
//没有返回值的Lambda表达式
TestLambda1 testLambda1=()->{
System.out.println("hello,testLambda1");
};
testLambda1.testLambda();// hello,testLambda1
//如果在执行代码块中,只有一行代码,大括号是可以省略的
TestLambda1 testLambda2=()-> System.out.println("hello,testLambda2");;
testLambda2.testLambda();// hello,testLambda2
}
}
package org.example.lambda;
public class LambdaTest {
@FunctionalInterface
interface TestLambda1{
void testLambda();
}
// public static void main(String[] args) {
// //没有返回值的Lambda表达式
// TestLambda1 testLambda1=()->{
// System.out.println("hello,testLambda1");
// };
// testLambda1.testLambda();// hello,testLambda1
//
// //如果在执行代码块中,只有一行代码,大括号是可以省略的
// TestLambda1 testLambda2=()-> System.out.println("hello,testLambda2");;
// testLambda2.testLambda();// hello,testLambda2
//
//
// }
@FunctionalInterface
interface TestLambda2 {
void test(String name, int age);
}
public static void main(String[] args) {
//带有参数 但是没有返回值得Lambda表达式和接口
//带有参数时,要将参数写在小括号中,参数的顺序和接口中定义的顺序相同
TestLambda2 testLambda1 =(String name, int age)->{
System.out.println(name+"今年"+age+"岁了");
};
testLambda1.test("庞鹏程",21);//庞鹏程今年21岁了
//在设置参数时,也可以不写参数的类型,JVM会自动推断出参数的类型,以下方式和上面的代码是一样的
TestLambda2 testLambda2 =(name, age)->{
System.out.println(name+"今年"+age+"岁了");
};
testLambda1.test("庞鹏程",21);//庞鹏程今年21岁了
}
}
package org.example.lambda;
public class LambdaTest {
@FunctionalInterface
interface TestLambda1{
void testLambda();
}
// public static void main(String[] args) {
// //没有返回值的Lambda表达式
// TestLambda1 testLambda1=()->{
// System.out.println("hello,testLambda1");
// };
// testLambda1.testLambda();// hello,testLambda1
//
// //如果在执行代码块中,只有一行代码,大括号是可以省略的
// TestLambda1 testLambda2=()-> System.out.println("hello,testLambda2");;
// testLambda2.testLambda();// hello,testLambda2
//
//
// }
@FunctionalInterface
interface TestLambda2 {
void test(String name, int age);
}
// public static void main(String[] args) {
// //带有参数 但是没有返回值得Lambda表达式和接口
// //带有参数时,要将参数写在小括号中,参数的顺序和接口中定义的顺序相同
// TestLambda2 testLambda1 =(String name, int age)->{
// System.out.println(name+"今年"+age+"岁了");
// };
// testLambda1.test("庞鹏程",21);//庞鹏程今年21岁了
//
// //在设置参数时,也可以不写参数的类型,JVM会自动推断出参数的类型,以下方式和上面的代码是一样的
// TestLambda2 testLambda2 =(name, age)->{
// System.out.println(name+"今年"+age+"岁了");
// };
// testLambda1.test("庞鹏程",21);//庞鹏程今年21岁了
//
// }
@FunctionalInterface
interface TestLambda3 {
int test(int x, int y);
}
public static void main(String[] args) {
//带有参数,带有返回值的Lambda表达式
TestLambda3 testLambda1 = (x,y)->{
int z =x+y;
return z;
};
System.out.println(testLambda1.test(6, 12));//18
//当花括号内只有一行代码时,可以不添加花括号,同时,也不需要添加 return 关键字,虚拟机会自动帮你返回
TestLambda3 testLambda2 =(x,y)-> x+y;
System.out.println(testLambda2.test(6, 12));//18
}
}
总结
1 Lambda 表达式,必须和接口进行绑定
2 Lambda 表达式的参数,可以附带0到n个参数,括号中的参数类型可以不用指定,JVM在运行时,会自动根据绑定的抽象方法进行推导
3 Lambda 表达式的返回值,如果代码快只有一行并且没有大括号,不用写大括号,单行代码会自动返回,如果添加了大括号,或者代码有多行代码,必须通过return 关键字返回结果
变量访问
package org.example.lambda;
/**
* 变量捕获-变量的访问操作
*/
public class VariableCapture {
String s1 = "全局变量";
// 1. 匿名内部类型中对于变量的访问
public void testInnerClass() {
String s2 = "局部变量";
new Thread(new Runnable() {
String s3 = "内部变量";
@Override
public void run() {
// 访问全局变量
/**
* System.out.println(this.s1);this关键字~表示是当前内部类型的对象,故全局变量不能通过this关键字访问
*/
System.out.println(s1); // 全局变量
/**
* 局部变量的访问,~不能对局部变量进行数据的修改[final]
*/
System.out.println(s2);// 局部变量
//s2 = "hello";
/**
* 访问内部变量,是可以通过this关键字访问的,this关键字~表示是当前内部类型的对象
*/
System.out.println(s3); // 内部变量
System.out.println(this.s3); 内部变量
}
}).start();
}
// 2. lamdba表达式对于变量的访问
public void testLambda() {
String s2 = "局部变量lambda";
new Thread(() -> {
String s3 = "内部变量lambda";
/**
* 访问全局变量 在Lambda中是允许通过this关键字访问全局变量的,因为this关键字,表示的就是所属方法所在类型的对象
* 在使用了Lambda中,this关键字表示的就是所属方法所在类型的对象
*/
System.out.println(this.s1);// 全局变量
// 访问局部变量
System.out.println(s2); // 局部变量lambda
//s2 = "hello";// 不能进行数据修改,默认推导变量的修饰符:final
System.out.println(s3); // 内部变量lambda
s3 = "lambda 内部变量直接修改";
System.out.println(s3); // lambda 内部变量直接修改
}).start();
}
public static void main(String[] args) {
VariableCapture variableCapture = new VariableCapture();
variableCapture.testInnerClass();
variableCapture.testLambda();
}
}
Lambda表达式的变量操作,优化了匿名内部类的this关键字,不在单独建立对象作用域。表达式本身就是所属类型对象的一部分,在语法语义上使用更加简洁
Lambda表达式类型检查
表达式类型检查
定义一个函数式接口 MyInterface,接收两个范型T,R,并提供一个方法 strategy 接受参数T 返回参数R
public class TypeCheck {
/**
* 定义一个函数式接口 MyInterface,接收两个范型T,R,并提供一个方法 strategy 接受参数T 返回参数R
* @param <T>
* @param <R>'
*/
//定义函数式接口MyInterface
@FunctionalInterface
interface MyInterface<T,R>{
R strategy(T t, R r);
}
//定义一个方法test,接受一个MyInterface作为参数,范型为String 和List,在方法内部我们将String 添加到List集合中
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
test(new MyInterface<String, List>() {
@Override
public List strategy(String s, List list) {
list.add(s);
return list; //[hello]
}
});
//使用Lambda表达式的方式调用test
/**
* 在Lambda表达式的写法中,并没有指明方法的参数为MyInterface,
* 而是直接传递了参数X,y,这是由底层虚拟机来自动推导出来的。
*/
test((x, y) -> {
y.add(x);
return y; // [hello]
// x.add(y);
// return x;
});
}
}
在Lambda表达式的写法中,并没有指明方法的参数为Myinterface,而是直接传递了参数x,y,这是由底层虚拟机来自动推导出来的。总结当我们使用Lambda表达式语法的时候,jvm会获取当前方法的参数来进行推导,从而自动为我们绑定方法的参数类型.这就是Lambda表达式的类型检查
方法重载和Lambda表达式
首先创建一个类LambdaTest类,在LambdaTest类中创建两个接口 Parame1 和Parame2,并定义outInfo(String info) 方法
然后定义重载方法lambdaMethod,参数分别是Parame1和Parame2
使用传统的匿名内部类的方式调用,在main方法中创建App4的对象,使用对象点lambdaMethdo的方式new一个Parame1或者Parame2
使用lambda表达式的话因为是重载方法,jvm自动推导类型的时候类型检查不通过,会报如下错误
在这种情况下只能使用匿名内部类来替代lambda表达式
深入理解Lambda表达式
Lambda表达式低层解析运行原理
创建一个类App,并创建一个用于Lambda表达式执行的函数式接口IMakeUP,提供一个方法makeUp(String msg),在main方法中创建Lambda表达式打印msg
将App.java文件编译(编译java文件命令 javac 文件名)后,我们可以看到生成了App.class文件和IMakeUP.class文件
通过使用 javap -p App.class 命令对class文件进行反编译得到结果
Lambda表达式在JVM低层解析成私有静态方法和匿名内部类型
通过实现接口的匿名内部类型中接口方法调用静态实现方法,完成Lambda表达式的执行
方法引用
方法引用是结合Lambda表达式的一种语法特性,是用来简化代码书写的,但是代码可读性差,本质就是简化方法的调用方式 静态方法引用 实例方法引用 构造方法引用
Demo
首先创建一个测试类MethodReference, 在里面创建一个内部类Person,添加属性 name(名字),gender(性别),age(年龄),并使用lombok创建get/set方法和构造函数
package org.example.method;
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;
public class MethodReference {
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("ppc", "男", 30));
personList.add(new Person("cxk", "女", 26));
personList.add(new Person("kuLi", "男", 32));
System.out.println(personList);
/**
* [
* Person(name=tom, gender=男, age=16),
* Person(name=jerry, gender=女, age=15),
* Person(name=ppc, gender=男, age=30),
* Person(name=cxk, gender=女, age=26),
* Person(name=kuLi, gender=男, age=32)
* ]
*/
//匿名内部类实现方式--列表排序
Collections.sort(personList, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
});
System.out.println(personList);
/**
* [
* Person(name=jerry, gender=女, age=15),
* Person(name=tom, gender=男, age=16),
* Person(name=cxk, gender=女, age=26),
* Person(name=ppc, gender=男, age=30),
* Person(name=kuLi, gender=男, age=32)
* ]
*/
//lambda表达式实现
Collections.sort(personList, (p1,p2) -> p1.getAge()-p2.getAge());
System.out.println("===========");
System.out.println(personList);
/**
* [
* Person(name=jerry, gender=女, age=15),
* Person(name=tom, gender=男, age=16),
* Person(name=cxk, gender=女, age=26),
* Person(name=ppc, gender=男, age=30),
* Person(name=kuLi, gender=男, age=32)
* ]
*/
/**
* 静态方法引用的使用
* 类型名称.方法名称() ---> 类型名称::方法名称
*/
Collections.sort(personList, Person::compareByAge); //compareByAge是一个静态方法
System.out.println(personList);
/**
* [ Person(name=kuLi, gender=男, age=32),
* Person(name=ppc, gender=男, age=30),
* Person(name=cxk, gender=女, age=26),
* Person(name=tom, gender=男, age=16),
* Person(name=jerry, gender=女, age=15)]
*/
System.out.println("===========");
/**
* 实例方法引用的使用
* 类型名称.实例方法名称() ---> 类型名称::实例方法名称
*/
PersonUtil pu = new PersonUtil();
Collections.sort(personList,pu::compareByName);
System.out.println("tom".hashCode()); //115026
System.out.println("jerry".hashCode()); //115026
System.out.println(personList);
/**
* [ Person(name=cxk, gender=女, age=26),
* Person(name=ppc, gender=男, age=30),
* Person(name=tom, gender=男, age=16),
* Person(name=kuLi, gender=男, age=32),
* Person(name=jerry, gender=女, age=15)
* ]
*/
/**
* 构造方法的引用使用
* 类型对象的构造过程 ---> 类型名称::new
*/
IPerson ip = Person::new;
Person person = ip.initPerson("ppc", "male", 21);
System.out.println(person); //Person(name=ppc, gender=male, age=21)
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class Person {
private String name; // 姓名
private String gender; // 性别
private int age; // 年龄
//静态方法
public static int compareByAge(Person p1, Person p2) {
return p2.getAge() - p1.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);
}
构造方法引用
构造方法的引用需要绑定一个函数式接口,首先创造一个函数式接口
实例方法引用
创建类型对应的对象 -->对象引用::实例方法名称
在MethodReference下创建一个新类PersonUtils,添加一个方法comerByName(),根据人员的名称的hash值来进行排序
静态方法引用
类型名称::方法名称
Stream概述
什么是Stream?
Stream是Java为了操作数组,集合来进行复杂的聚合操作而推出的一套新的API
新创建一个测试类TestStream 创建一个main方法,在main方法中初始化一个字符串集合
package org.example.TestStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
public class TestStream {
public static void main(String[] args) {
/**
* 1. 添加测试数据:存储多个账号的列表
*/
List<String> accounts = new ArrayList<String>();
accounts.add("tom");
accounts.add("ppc");
accounts.add("kuLi");
accounts.add("keBi");
accounts.add("zhanMuSi");
System.out.println(accounts); //[tom, ppc, kuLi, keBi, zhanMuSi]
/**
* 业务要求:要求从存储多个账号的列表中选出长度大于等于5的有效账号
*/
// 1.循环方式
for (String account : accounts) {
if (account.length() >= 5) {
System.out.println("有效账号:" + account); //有效账号:zhanMuSi
}
}
// 2.迭代器方式
Iterator<String> it = accounts.iterator();
while(it.hasNext()) {
String account = it.next();
if (account.length() >=5) {
System.out.println("有效账号:" + account); //有效账号:zhanMuSi
}
}
// 3.使用Steam结合Lambda表达式的方式,完成业务处理
List<String> validAccounts = accounts.stream().filter(s -> s.length() >= 5).collect(Collectors.toList());
System.out.println(validAccounts); //[zhanMuSi]
}
}
注意:这三种方式的性能是相同的,只是精简了代码长度