第一章 Java为什么引入 Lmabda表达式
目的尽可能轻量级的将代码封装为数据
1.1 什么是Lambda表达式
Lambda表达式也被成为箭头函数、匿名函数、闭包
Lambda表达式体现的是轻量级函数式编程思想
‘->’符号是Lambda表达式的核心符号,符号左侧是操作参数,符号右侧是操作表达式
1.2 Model Code as Data
Model Code As Data,编码及数据,尽可能轻量级的将代码封装为数据
传统解决方案:接口&实现类(匿名内部类)
- 但传统方案存在的问题:
a 语法冗余(有很多和数据处理无关的代码)
b this关键字(匿名内部类中,this关键字在内部类型中,变量绑定和变量访问存在很大误区)
c 变量捕获(内部类型中,当前作用域中的变量的处理,会有一些特殊要求)
d 数据控制(数据量控制并不是非常友好)等
![](https://i-blog.csdnimg.cn/blog_migrate/f4f5c58574313c2edfce9381009a1ccd.png)
第二章 函数式接口的概述和定义
2.1函数式接口定义
函数式接口(functuon interface),就是Java类型系统中的接口
函数式接口,是只包含一个接口方法的特殊接口
语义化检测注解:@FunctionalInterface,用来检查函数式接口的合法性
语义化检测注解:@Functionallnterface这是用来检查函数式接口的合法性的注解
![](https://i-blog.csdnimg.cn/blog_migrate/79b229b34323e3f5f602fd2d2b1526e4.png)
==============================================================
![](https://i-blog.csdnimg.cn/blog_migrate/6428a46e9a14fec336512f82a15ab15a.png)
函数式接口定义总结:定义一个函数式接口只需要在接口中提供一个接口方法,并在接口上添加@Functionallnterface注解即可,如果添加了多个接口方法,@FunctionAllnterface就会报错,
那么此时也不再是函数式接口,但请注意在一个接口中,默认方法、静态方法、函数式接口是可以共同存在的
![](https://i-blog.csdnimg.cn/blog_migrate/83c9a96eb7267dcb0bed7611ffc87f8f.png)
默认方法和静态方法使用
这是两个函数式接口例子如下:
![](https://i-blog.csdnimg.cn/blog_migrate/cbf1512f83d832b3d0a4fc555e595b62.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4eafa0bd60965a1200d56408efef0c2c.png)
定义一个函数式接口只需要在接口中提供一个接口方法,并在接口上添加@FunctionalInterface注解即可,如果添加了多个接口方法,则注解@FunctionalInterface就会报错,那么此时不再是函数式接口,但默认方法,静态接口是允许存在的
默认接口方法的特性
以用户身份认证标记接口为例创建一个实现类 是用来返回用户身份的接口方法
![](https://i-blog.csdnimg.cn/blog_migrate/f8c4a5d8c659f63c78d547fa2cb0b1e2.png)
对当前代码进行测试
![](https://i-blog.csdnimg.cn/blog_migrate/ef4d26e393ab90a991a6a9a32401a404.png)
像以上代码当需求进行变动后,就比如说要求用户返回用户身份后还可以同时获取用户的身份信息,那么就需要修改所有的实现类代码,这是一种不好的行为,因为一个接口方法就应该做到单一职责,那么我们在不修改原本的实习类的方法下,还有哪些办法可以再获取用户的身高信息呢?
-
在接口里新增一个接口方法,但是此时该接口不再是函数式接口,就是一个普通接口,再根据着接口书写一个实现类,并重写接口的里面的方法,在实现类的方法里面编写需要 (不满足函数式接口)
-
在接口里面使用默认方法(满足函数式接口)
-
在接口里面使用静态方法(满足函数式接口)
![](https://i-blog.csdnimg.cn/blog_migrate/f7b54fbef97536720bbc73c916d782bb.png)
这样我们就可以直接通过代码来调用默认方法
![](https://i-blog.csdnimg.cn/blog_migrate/22dc64d4a2bce46e7cf2b27f0223360b.png)
静态接口方法的特性
以消息发送接口为例,在接口中添加一个静态方法:验证消息格式是否合法
![](https://i-blog.csdnimg.cn/blog_migrate/4ab04c6f8726b44f236f4456096bcd92.png)
添加一个实现类,重写接口中的方法,节省时间并直接进行了测试
![](https://i-blog.csdnimg.cn/blog_migrate/fc3b86be0b5bb7f4e691f79016bbe288.png)
在format方法中,我们只是打印了一句话 消息转换成了------》Hello-World2001/6/12。,并返回了msg,这时候我们可以直接调用接口的静态方法来验证消息的合法性。通过执行结果可以看出,静态方法不会对函数式接口的语义也不会产生影响,总结:默认接口,函数式接口,静态接口可以在一个接口类中同时存在。
从Object中继承的方法不会影响函数式接口
由于java中的类都直接的或者间接的继承了Object类,所以从Object继承的方法,无论是否是抽象的,都不会影响你函数式接口的语义。
例如,在 IUserCredential 中添加一个来自object的方法 toString(),该函数式接口也是不会报错的
![](https://i-blog.csdnimg.cn/blog_migrate/531143d20717f836cdf271e0acc07f34.png)
总结:默认接口,函数式接口,静态接口,从Object中继承的方法接口是可以在一个接口类中同时存在的。
Lambda表达式和函数式接口的关系
Lambda 只能操作一个方法 Java 中的Lambda表达式就是一个函数式接口的实现
实现接口方法的另一种方式是使用匿名内部类,
![](https://i-blog.csdnimg.cn/blog_migrate/1a81fba0f55a116cec937da24be31ab2.png)
通过观察匿名内部类的方式实现接口方法和实现类实现接口的方式实现接口方法,就可以发现,其实和数据相关的代码只有
" return “admin”.equals(username) ? “系统管理员” : “普通会员”; " 这一行,其他的代码都是冗余代码,那么能不能对代码进行优化呢?这就要使用到JDK8中的Lambda表达式。
![](https://i-blog.csdnimg.cn/blog_migrate/ddb07f7d1c172ba83d0b846538b01d80.png)
相比较前面的匿名内部类,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类型结果,适合需要判断的场景
![](https://i-blog.csdnimg.cn/blog_migrate/8b8a1ed356770fe12b7be91c609215de.png)
2.java.util.function.Consumer
接收参数T,不反回结果
![](https://i-blog.csdnimg.cn/blog_migrate/999c3f323f2bbeea0ee6f1aa99d0b13f.png)
3. java.util.function.Function<T,R>
接收一个参数对象T,返回结果对象R
![](https://i-blog.csdnimg.cn/blog_migrate/d27547e3b4ea9ea8ebb2d15f0a5991f2.png)
4. java.util.function.Supplier
不接受参数,提供T对象的创建工厂
![](https://i-blog.csdnimg.cn/blog_migrate/ef1075fdcb9881711f7c290ff6e94dc4.png)
5. java.util.function.UnaryOperator
接收参数对象T,返回结果对象T ,常用于适配器模式
![](https://i-blog.csdnimg.cn/blog_migrate/a51ba5bcebb70bc42c5a49674e009622.png)
6. java.util.function.BinaryOperator
接收两个T对象,返回一个T对象的结果(使用场景:例如对两个对象进行比较,返回较大的结果)
![](https://i-blog.csdnimg.cn/blog_migrate/5b1db9baad6b4c7c42267692041b89ef.png)
总结: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) 方法
![](https://i-blog.csdnimg.cn/blog_migrate/afe12dfa45267c8f9ccc8d569685ae63.png)
然后定义重载方法lambdaMethod,参数分别是Parame1和Parame2
![](https://i-blog.csdnimg.cn/blog_migrate/c3c1e32b47f9229df96d5e3a2d345f82.png)
使用传统的匿名内部类的方式调用,在main方法中创建App4的对象,使用对象点lambdaMethdo的方式new一个Parame1或者Parame2
![](https://i-blog.csdnimg.cn/blog_migrate/b4b7c5d59cc477e42ec35eb71af938e2.png)
使用lambda表达式的话因为是重载方法,jvm自动推导类型的时候类型检查不通过,会报如下错误
![](https://i-blog.csdnimg.cn/blog_migrate/7bbbaf45ee9ebd0ae800c4f744f151c1.png)
在这种情况下只能使用匿名内部类来替代lambda表达式
深入理解Lambda表达式
Lambda表达式低层解析运行原理
创建一个类App,并创建一个用于Lambda表达式执行的函数式接口IMakeUP,提供一个方法makeUp(String msg),在main方法中创建Lambda表达式打印msg
![](https://i-blog.csdnimg.cn/blog_migrate/05731510377c83154cd26d4bd301f56a.png)
将App.java文件编译(编译java文件命令 javac 文件名)后,我们可以看到生成了App.class文件和IMakeUP.class文件
![](https://i-blog.csdnimg.cn/blog_migrate/ab06c84810813b49076c12ea20247e02.png)
通过使用 javap -p App.class 命令对class文件进行反编译得到结果
![](https://i-blog.csdnimg.cn/blog_migrate/0bdb64480d8afbcd04c7bc25dd65271d.png)
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);
}
构造方法引用
构造方法的引用需要绑定一个函数式接口,首先创造一个函数式接口
![](https://i-blog.csdnimg.cn/blog_migrate/410587aab287519c2c794b9d96bf84f6.png)
![](https://i-blog.csdnimg.cn/blog_migrate/2a02da7124fdb63e102974cd718b08a5.png)
实例方法引用
创建类型对应的对象 -->对象引用::实例方法名称
在MethodReference下创建一个新类PersonUtils,添加一个方法comerByName(),根据人员的名称的hash值来进行排序
![](https://i-blog.csdnimg.cn/blog_migrate/bb33f0061764384da8a33cce0351b6c8.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7fa435ab7b8780d7de5f2bb9743ad405.png)
静态方法引用
类型名称::方法名称
![](https://i-blog.csdnimg.cn/blog_migrate/fc3e74321a4c043639e1c87644cc5068.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9e6430ec3a212296b15602319ccd187b.png)
Stream概述
什么是Stream?
Java Stream是一种基于流式处理的API,它提供了一种简洁而高效的方式来处理集合、数组或任何其他数据源中的元素。在使用Java 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]
}
}
注意:这三种方式的性能是相同的,只是精简了代码长度
StreamAPI
什么是聚合操作?
在常规业务处理中,针对业务的批量操作,
例如,在电商项目中 ,获取指定数据的年平均消费额,获取指定店铺中最便宜的商品,获取指定店铺的当月有效订单数量等等
聚合操作:常规业务处理中,针对批量数据的操作(如获取指定店铺的年平均额,最便宜的商品等)
stream的处理流程
1. 获取数据源
2. 对数据源进行数据转换(一次或多次)
3. 执行操作,获取结果
4. 在进行数据转换时,要保证原有stream对象不会发生变化的前提下,获取到新的stream对象。可以继续进行下一次转换。这 样就允许我们在开发的过程中,可以类似列表的操作一样,执行多次数据转换和运算过程,最终将我们多种的业务逻辑添加到 stream的处理流程中
Steam的处理流程:
获取数据源->数据转换(可以执行一次或者多次)->获取结果
获取Steam对象
-
从集合,数组中获取
Collection.steam(),如上一节中的account.steam(); //从集合中获取Steam对象
Collection.parallelSteam(); //获取到一个支持并发的Steam对象;
Arrs.Stream(T t); //从数组中获取Stream对象
-
从缓冲流中获取
BufferReader
BufferReader.lines()->stream();
-
静态工厂
java.util.stream.Intstream().range()…
java.nio.file.Files.work()…
-
自行构建
java.util.Spliterator
-
更多的方式
Random.ints()
Pattern.splitAsStream()…
Stream操作类型
Java Stream主要分为两种类型:中间操作和终端操作。中间操作是在数据源上进行的转换操作,每次操作都会返回一个新的Stream实例,以便继续进行操作。而终端操作是指对Stream进行最终操作的操作,如收集、计算或迭代等。 Stream的操作类型主要分为两种主要类型和一种辅助类型
中间操作,可以有多个,每次返回一个新的流,可进行链式操作。
终端操作,只能有一个,每次执行完,这个流就结束了,因此只能放在最后。
一些常见的Java Stream操作:
· 过滤操作(filter):使用给定的谓词过滤数据源中的元素。
· 映射操作(map):将数据源中的每个元素映射为新元素。
· 排序操作(sorted):按指定的顺序对数据源中的元素进行排序。
· 去重操作(distinct):从数据源中删除重复的元素。
· 统计操作(count、min、max、sum、average):统计数据源中的元素。
· 收集操作(collect):将数据源中的元素收集到一个集合中。
· 迭代操作(forEach):对数据源中的每个元素进行迭代操作。
· 匹配操作(allMatch、anyMatch、noneMatch):使用给定的谓词测试数据源中的元素是否匹配。
· 并行流操作(parallelStream):使用并行处理加速处理数据源中的元素。
Java Stream可以使程序员更加简洁、高效地处理数据,使我们的代码更加易于阅读和维护。它还提供了许多其他功能,如支持延迟计算、内部迭代等。在实际的开发中,Java Stream可以广泛应用于集合处理、数据筛选和转换等场景。
中间操作API(intermediate)
中间操作API -- intermediate
操作结果是一个stream,中间操作可以有一个或多个连续的中间操作。
中间操作API:需要注意的是,所有的中间操作,只记录操作方式,不做具体执行,直到结束操作执行时,才做数据的最终执行.中间操作就是业务的逻辑处理
中间操作:就是业务逻辑处理
中间操作过程:
- 无状态:数据处理时,不受前置中间操作的影响。也就是说,前面做的中间操作结果不会影响后面的中间操作。API:map/filter/peek/parallel/sequential/unordered
无状态: 数据处理时,不受前置中间操作的影响,就是前面的中间操作不会对当前的中间操作产生影响.
- 有状态:数据处理时,会受到前置中间操作的影响。比如,前置操作是排序操作,当前操作是截取操作,截取操作会对排序后的数据进行截取。API:distinct/sorted/limit/skip
有状态: 数据处理时,会受到前一个中间操作的影响.
例如,前一个中间操作是一个排序操作,当前中间操作是一个截取操作,有状态下,当前操作会对排序后的结果进行截取.
结束操作(终结操作)(Terminal)
终结| 结束操作 -- terminal
需要注意:一个stream对象,只能有一个terminal操作,这个操作一旦发生,就会真是处理数据,生成对应的处理结果
一个Stream只能有一个终结操作,一旦执行终结操作,Stream就会真实处理数据,生成对应的处理结果,并且这个结果是不可逆的…
终结操作又区分为:短路和非短路操作. 短路操作和非短路操作是根据处理结果来定义的
终结操作:
- 非短路操作:当前的stream对象必须处理完集合中的所有数据,才能得到处理结果。API:
forEach/forEachOrdered/toArray/reduce/collect/min/max/count/iterator
非短路操作:Stream对象必须处理完集合中所有的数据,才能返回处理结果
- 短路操作:当前的stream对象在处理过程中,一旦满足某个条件,即可以得到结果。 API:
anyMatch/allMatch/noneMatch/findFirst/findAny等
某些情况下,会使用Short-circuiting,使用条件:无限大的Stream-> 有限大的Stream。
短路操作:当前的Strame对象在处理过程中,一旦满足某个条件,就可以获取结果,并不需要处理所有的数据
如何得到Stream对象
将多个数据转换为Stream对象
package org.example.collection;
import java.util.*;
import java.util.stream.Stream;
public class StreamCollections {
public static void main(String[] args) {
//批量数据(数组,集合,链表)->Stream对象
//多个数据转换得到Stream对象
System.out.println(Stream.of("admin", "pangGe", "root")); //java.util.stream.ReferencePipeline$******
//数组转换Stream对象
String[] userName1 = new String[]{"admin","pangGe","root"};
Stream<String> stream1 = Arrays.stream(userName1);
System.out.println(stream1);//java.util.stream.ReferencePipeline$******
//列表转换Stream对象
List<String> userName2 = new ArrayList<>();
userName2.add("admin");
userName2.add("pangGe");
userName2.add("root");
Stream<String> stream2 = userName2.stream();
System.out.println(stream2);//java.util.stream.ReferencePipeline$******
//集合转换Stream对象
Set<String> userName3 = new HashSet<>();
userName3.add("admin");
userName3.add("pangGe");
userName3.add("root");
Stream<String> stream3 = userName3.stream();
System.out.println(stream3);//java.util.stream.ReferencePipeline$******
//Map转换Stream对象
Map<String,Integer> userName4 = new HashMap<>();
userName4.put("admin",21);
userName4.put("pangGe",22);
userName4.put("root",23);
System.out.println(userName4.entrySet().stream());//java.util.stream.ReferencePipeline$******
}
}
Stream 对于基本数据类型的功能性封装
针对于基本数据类型Stream在运算时会进行频繁的装箱拆箱,所以对于基本数据类型进行功能性封装
只针对于常用的 int lang double 类型 ,其他的没有
![](https://i-blog.csdnimg.cn/blog_migrate/2797165fff01e22feb5e9d7da60b7ba9.png)
Java为每种基本数据类型都提供了对应的包装类,比如说int的包装类就是Integer
扩展Java中的装箱&拆箱
Java为每种基本数据类型都提供了对应的包装类,而且还提供了包装类与基本数据类型之间的互相转化机制,也就是所谓的“装箱”和“拆箱”。
概念:
装箱:把基本数据类型转换成包装类的过程。-----> 比如说 int转换成 Integer
拆箱:把包装类转换成基本数据类型的过程。----->比如说 Integer 转换成int
分类:
从实现的角度而言,可以分为“手动拆装箱”与“自动拆装箱”的操作。
手动装箱:可以结合类型的构造方法或者valueOf()方法实现。
手动拆箱:可以通过xxxValue()方法实现。
自动装箱:把一个基本类型变量直接赋值给对应的包装类变量。
自动拆箱:允许把包装类对象直接赋值给对应的基本数据类型变量。
Demo
以Integer和Boolean为例,手动拆装箱如下图1所示,自动拆装箱如下图2所示。
PS(附言):如果JDK版本较高(譬如jdk11以上),在应用构造方法进行手动装箱时,编译器会在方法上提示有删除线,标识此方法不推荐,但并不影响程序运行与方法调用。
自动装箱的底层,依然是手动装箱、拆箱操作。只是Java通过一种相对简洁的语法方式,进行了封装(也称 语法糖/编译器级别特性)。其中,在自动装箱操作中,使用的是各自类型的valueOf()方法,自动拆箱操作中使用的是各自的xxxValue()方法。
//图1
public static void main(String[] args){
// 手动装箱
int one = 12;
// 方式1:构造方法传参
Integer t1 = new Integer(one);
// 方式2:类型.valueOf方法
Integer t2 = Integer.valueOf(one);
boolean flag = true;
// 方式1:构造方法传参
Boolean f1 = new Boolean(flag);
// 方式2:类型.valueOf方法
Boolean f2 = Boolean.valueOf(flag);
// 手动拆箱
// 对象.xxxValue()方法
int two = t1.intValue();
boolean otherFlag = f1.booleanValue();
}
//图2
public static void main(String[] args){
// 自动装箱
int one = 12;
Integer t1 = one;
boolean flag = true;
Boolean f2 = flag;
// 自动拆箱
int two = t1;
boolean otherFlag = f1;
}
应用:
当进行赋值操作时,会触发拆装箱操作
当方法调用,传入参数时,也会触发相关操作
当结合操作符进行运算操作时,也会触发相关操作
如下图3所示,类中定义静态方法testAge,当在main方法中,调用该方法并传入包装类对象时,执行拆箱操作;进入testAge方法,当进行加法运算时,执行装箱操作;后进入if判断与5进行取余操作,执行了拆箱操作。而这其中的拆装箱都是自动完成的。
//图3
public class WrapTest{
public static void main(String[] args){
// 传参(拆箱):Integer-->int
testAge(new Integer(1));
}
public static void testAge(int i){
// 计算(装箱):int-->Integer
Integer templess = 20 + i;
// 计算(拆箱):Integer-->int
if(templess % 5 == 0)
System.out.println("能被5整除");
else
System.out.println("不能被5整除");
}
}
//运算结果:不能被5整除
Stream转换得到指定的数据类型(数组,集合,字符串,map)
数组
![](https://i-blog.csdnimg.cn/blog_migrate/eeb4e521a1bb7b40034ccacc94cf8fe6.png)
字符串
![](https://i-blog.csdnimg.cn/blog_migrate/3cee4536ae698db0bca5e211e2aff2d6.png)
列表
![](https://i-blog.csdnimg.cn/blog_migrate/46f6e579ac5e3ac51892e5b25bc61abc.png)
集合
![](https://i-blog.csdnimg.cn/blog_migrate/75bf4b42d547909a0bc5624fb7081b5b.png)
Map
![](https://i-blog.csdnimg.cn/blog_migrate/d6423ab7a8b0f9ec6008ada0b8456d6c.png)
在这里,Collectors.toMap方法需要传入一个Function,这个接口在处理的过程中会得到两个不同的数据 ,由于我们的stream
中只包含了一个单个数据,所以我们在处理的过程中需要将Map中的数据进行单独的处理
![](https://i-blog.csdnimg.cn/blog_migrate/35bda7a6de34d8c801d74b6405dfe037.png)
注意:由于stream一旦进行终结操作,那么就意味着整个过程的结束,所以上述代码无法一次性全部执行,只能在执行一个的时候,将其他代码注释掉.如果不注释掉,就会存在这种错误
![](https://i-blog.csdnimg.cn/blog_migrate/1947c1127572664ed32c44dc0e44bbdd.jpeg)
Stream常见的API操作
1.Demo准备数据
![](https://i-blog.csdnimg.cn/blog_migrate/bcbea9b339267b9498ba1a62d0ca6708.png)
map()
在每个数据的前面增加一个字段,姓名:
![](https://i-blog.csdnimg.cn/blog_migrate/ca23861bb2b72730220ef46ff4d87580.png)
forEach()
循环遍历
![](https://i-blog.csdnimg.cn/blog_migrate/06fc5c777b67b286264635456d8d4c32.png)
filter()
添加过滤条件,来过滤符合条件的用户
![](https://i-blog.csdnimg.cn/blog_migrate/41b3f19858c33fa455b74174de67ccf0.png)
peek()
使用peek来进行多次的迭代操作
![](https://i-blog.csdnimg.cn/blog_migrate/73472d5beaaea93eb19606a9002082c1.png)
......
2.Demo准备数据(stream对于数字运算的支持)
![](https://i-blog.csdnimg.cn/blog_migrate/5088a7e0480aed456e9dfa60bb3c4a7d.png)
skip()
![](https://i-blog.csdnimg.cn/blog_migrate/6f1a4c7a7938d187ab57f5cb31fe8cb1.png)
limit()
![](https://i-blog.csdnimg.cn/blog_migrate/8387845378348629192e934ce0533b92.png)
distinct()
![](https://i-blog.csdnimg.cn/blog_migrate/14674e94658e914a6280becd04aa5908.png)
sorted()
![](https://i-blog.csdnimg.cn/blog_migrate/c394b94c826b117edf5b7677fb36c0d4.png)
max()&&min()
![](https://i-blog.csdnimg.cn/blog_migrate/6c68dfb71a7a97bb3a1a7d59c0f1d843.png)
reduce()
![](https://i-blog.csdnimg.cn/blog_migrate/3391a527fac57e81df90f891f38ebfd7.png)
......
Lambda和Stream的性能问题
1 通过基本数据类型:整数进行测试
创建一个integerLists 集合,添加一些随机数集合数据
![](https://i-blog.csdnimg.cn/blog_migrate/bf0a32ffafaa59e809d0e6725529ff12.png)
性能测试
1 stream 对象
创建一个方法testStreame 接收一个list集合方法,通过stream对象获取最大值
![](https://i-blog.csdnimg.cn/blog_migrate/d9427710a82c363adc136bc9da7a0d51.png)
测试结果:
![](https://i-blog.csdnimg.cn/blog_migrate/560caafd1413139d9d0991e5b4b20acc.png)
2.for循环
创建一个方法testForloop 接收一个list集合方法,通过stream对象获取最大值
![](https://i-blog.csdnimg.cn/blog_migrate/11c440f5f7d8f89c71676dfafa374c23.png)
测试结果:
![](https://i-blog.csdnimg.cn/blog_migrate/f6ff55077c17f389bf6fb92d5e207e88.png)
3 parallelstream
创建一个方法testParallelstream 接收一个list集合方法,通过stream对象获取最大值
![](https://i-blog.csdnimg.cn/blog_migrate/4ccf592fe1f98abf1e5d217338fe67e3.png)
测试结果:
![](https://i-blog.csdnimg.cn/blog_migrate/1c47119f6fe38d85802e0414b9fb0241.png)
4 增强型for循环
创建一个方法testStrongstream 接收一个list集合方法,通过stream对象获取最大值
![](https://i-blog.csdnimg.cn/blog_migrate/429fb3529c29a5b01ae0e5b47c6017e0.png)
测试结果:
![](https://i-blog.csdnimg.cn/blog_migrate/6feb17b9482114f948b94e05cd928d1b.png)
5 迭代器操作
创建一个方法testIterator接收一个list集合方法,通过stream对象获取最大值
![](https://i-blog.csdnimg.cn/blog_migrate/32d74b4b936e1fb865d5d3588149792a.png)
测试结果:
![](https://i-blog.csdnimg.cn/blog_migrate/5cec39e4e124cacd7ce5dff4ae99ef5e.png)
总结:串行的Stream 的执行时间比较长
![](https://i-blog.csdnimg.cn/blog_migrate/bcc6ba00775fb03b5a21a34e5b4a5883.png)
2 通过复杂数据类型:对象进行测试
创建一个对象产品.并增加一个全属性的构造方法
![](https://i-blog.csdnimg.cn/blog_migrate/bffea5d05feeb7bc9ab272bdc691d751.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d06e08699abc5a335a3dbfdba63a9eac.png)
通过五种方式对获取热度最高的产品进行测试
1 Stream
![](https://i-blog.csdnimg.cn/blog_migrate/dab8558113ef967368850768ccdfa34a.png)
2 parallelstream
![](https://i-blog.csdnimg.cn/blog_migrate/9fd1c2992db8d4639989f23bdcf1f0ba.png)
3 for
![](https://i-blog.csdnimg.cn/blog_migrate/8433d2e01db90e5cd9b391df1519af50.png)
4 增强for
![](https://i-blog.csdnimg.cn/blog_migrate/986830c0f6dc8d692a2d6c11201d5813.png)
5迭代器
![](https://i-blog.csdnimg.cn/blog_migrate/0509883b7b783ca77e7e70f941bf8897.png)
测试结果:
![](https://i-blog.csdnimg.cn/blog_migrate/4a3b103fce800d01d50cec25e3229ec2.png)
总结:在处理对象的过程中,串行Stream 依旧是执行时间最长的,parallelstream的处理事件已经可以和迭代器媲美,比普通的for要高,最快的还是迭代器.和增强for
Stream 的线程安全问题
Demo
通过整数列表的并行复制来测试Stream是否存在安全问题
初始化一个整数集合,分别通过串行Stream和并行Stream来进行集合间的复制,并打印源集合和复制后的集合的长度
![](https://i-blog.csdnimg.cn/blog_migrate/af936a7cc0b13a6995ca475c8b70346d.png)
总结:Demo中我们源数据长度为200,串行的Stream的长度没变,但是并行的Stream的集合长度变小了.说明并行Stream存在线程安全问题.
补充:supplement
Java的Predicate接口以及其用法 predicate:谓语;断言
Java 8引入了许多新特性,其中包括函数式编程中的核心概念,例如Lambda表达式和函数接口。这些新特性在很大程度上简化了Java代码,使其更具可读性和功能性。
Predicate接口是什么?
在Java中,Predicate
接口是一个函数接口,也就是说它只有一个抽象方法。这个接口的主要目的是用于判断一个对象是否满足某个条件,如果满足则返回true
,否则返回false
。
这个接口在java.util.function
包中定义,它的定义如下:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
其中,T
是传入test
方法的参数的类型,test
方法返回一个boolean
类型的结果。
Predicate接口的基本用法
让我们看一个基本的示例,其中我们使用Predicate
接口来检查一个数字是否为偶数。
import java.util.function.Predicate;
public class Main {
public static void main(String[] args) {
Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println(isEven.test(4)); // 输出:true
System.out.println(isEven.test(5)); // 输出:false
}
}
在这个例子中,我们定义了一个Predicate
接口的实例isEven
,然后使用Lambda表达式(num -> num % 2 == 0)
为其赋值。然后我们可以使用isEven.test()
方法来检查一个数字是否是偶数。
Lmabda表达式的基本语法
[接口声明] = (参数) ->{执行代码} 接口名 给接口取名 = (参数)->{执行代码}
结合流API使用Predicate接口
Predicate
接口与Java 8的流API(Stream API)结合使用可以大大简化数据处理代码。例如,如果我们有一个数字列表,我们可以使用Predicate
和流API来筛选出其中的所有偶数。
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
Predicate<Integer> isEven = num -> num % 2 == 0;
List<Integer> evenNumbers = numbers.stream()
.filter(isEven)
.collect(Collectors.toList());
System.out.println(evenNumbers); // 输出:[2, 4, 6, 8]
}
}
在这个例子中,我们首先创建了一个包含1-9的数字列表。然后我们定义了一个Predicate接口的实例isEven,用来检查一个数字是否是偶数。接下来我们使用流API的filter方法,传入isEven作为参数,来筛选出列表中的所有偶数。最后我们使用collect方法将结果转换为一个新的列表。
通过上述示例,我们可以看出,Predicate接口与Java 8的流API结合使用,可以使我们的代码更加简洁、易读。
总的来说,Predicate接口是Java 8中引入的一个非常实用的特性,它在很多场景下都可以提升我们代码的简洁性和可读性。尤其是和流API结合使用,可以让我们更方便地处理数据集合。