04-函数式编程
- 函数对象
- Java_Stream_API
0. Java历代重要版本
- Java5(2004.9)
- 泛型、枚举、注解、自动拆装箱
- Java8(2014.3)
- 函数式编程、Stream API
- Java11 LTS(2018.9)
- Java17 LTS(2021.9)
- Java21 LTS(2023.9)
函数式编程、Stream API
- 函数伊始(函数、函数对象)
- 函数编程语法(Lambda、方法引用、闭包、柯里化)
- Stream API
- 实际应用(统计、异步、框架、并行、事件)
- 实现原理(Lambda、方法引用、闭包、可切分迭代器)
1. 函数伊始
- 函数即规则:只要输入相同,无论多少次调用,无论什么时间调用,输出相同
1. 合格的函数
// 演示【合格】的函数
public class Sample1 {
public static void main(String[] args) {
System.out.println(square(10));
System.out.println(square(10));
System.out.println(square(10));
}
static int square(int x) {
return x * x;
}
}
// 成为合格函数:引用的外部数据必须是不可变的(除参数外)
public class Sample2 {
public static void main(String[] args) {
System.out.println(pray("张三"));
System.out.println(pray("张三"));
System.out.println(pray("张三"));
// buddha.name = "魔王";
System.out.println(pray("张三"));
}
static class Buddha {
// 不可变
final String name;
public Buddha(String name) {
this.name = name;
}
}
// jdk16只有getter()
// record Buddha(String name) {
// }
static Buddha buddha = new Buddha("如来");
static String pray(String person) {
return (person + "向[" + buddha.name + "]虔诚祈祷");
}
}
- 方法本质上也是函数。不过方法绑定在对象之上,它是对象个人法则
- 函数是:函数(对象数据,其它参数)
- 方法是:对象数据.方法(其它参数)
// 成员方法算不算函数?
// 方法不算函数。它只能算是某个对象专用的法则,是小道,还成不了大道
public class Sample3 {
static class Student {
final String name;
public Student(String name) {
this.name = name;
}
// 底层完整写法
public String getName(Student this) {
return this.name;
}
}
public static void main(String[] args) {
Student s1 = new Student("张三");
System.out.println(s1.getName()); // getName(s1)
System.out.println(s1.getName()); // getName(s1)
Student s2 = new Student("李四");
System.out.println(s2.getName()); // getName(s2)
System.out.println(s2.getName()); // getName(s2)
}
}
2. 有形的函数
- 函数化对象(即可传播)
- 函数本无形,也就是它代表的规则:位置固定、不能传播
- 若要有形,让函数的规则能够传播,需要将函数化为对象
public class Sample4 {
// 普通函数
static int add(int a, int b) {
return a + b;
}
// 接口的目的是为了将来用它来执行函数对象,此接口中只能有一个方法定义
interface Lambda {
int calculate(int a, int b);
}
// 函数化为对象
static Lambda add = (a, b) -> a + b;
public static void main(String[] args) {
// 纯粹的一条两数加法规则,它的位置是固定的,要使用它,需要通过 Sample4.add 找到它,然后执行
System.out.println(Sample4.add(3, 4));
// (add对象)就像长了腿,位置是可以变化的,想去哪里就去哪里,哪里要用到这条加法规则,就把它传递过去
System.out.println(add.calculate(5, 6));
}
}
public class Sample5 {
// 将对象变为字节,Socket传播
interface Lambda extends Serializable {
int calculate(int a, int b);
}
static class Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8080);
System.out.println("server start...");
while (true) {
Socket s = ss.accept();
Thread.ofVirtual().start(() -> {
try {
ObjectInputStream is = new ObjectInputStream(s.getInputStream());
Lambda lambda = (Lambda) is.readObject();
int a = ThreadLocalRandom.current().nextInt(10);
int b = ThreadLocalRandom.current().nextInt(10);
System.out.printf("%s %d op %d = %d%n", s.getRemoteSocketAddress().toString(), a, b, lambda.calculate(a, b));
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
});
}
}
}
// Server_JVM 端必须有Client0这个类,相当于把实现绑定在了服务器端。才可以把Client0传播
static class Client0 {
int add(int a, int b) {
return a + b;
}
}
// ------------------------------------------------------------------------------------------------
// Server 虚拟机端只需有 Lambda 接口定义,实现与服务器无关
static class Client1 {
public static void main(String[] args) throws IOException {
try (Socket s = new Socket("127.0.0.1", 8080)) {
Lambda lambda = (a, b) -> a + b;
ObjectOutputStream os = new ObjectOutputStream(s.getOutputStream());
os.writeObject(lambda);
os.flush();
}
}
}
static class Client2 {
public static void main(String[] args) throws IOException {
try (Socket s = new Socket("127.0.0.1", 8080)) {
Lambda lambda = (a, b) -> a - b;
ObjectOutputStream os = new ObjectOutputStream(s.getOutputStream());
os.writeObject(lambda);
os.flush();
}
}
}
static class Client3 {
public static void main(String[] args) throws IOException {
try (Socket s = new Socket("127.0.0.1", 8080)) {
Lambda lambda = (a, b) -> a * b;
ObjectOutputStream os = new ObjectOutputStream(s.getOutputStream());
os.writeObject(lambda);
os.flush();
}
}
}
}
3. 函数对象
- 行为参数化
- 延迟执行
1. 行为参数化
// 函数对象好处1:行为参数化
public class Sample6 {
public static void main(String[] args) {
List<Student> students = List.of(
new Student("张无忌", 18, "男"),
new Student("杨不悔", 16, "女"),
new Student("周芷若", 19, "女"),
new Student("宋青书", 20, "男")
);
// 需求1:筛选男性学生
List<Student> sex = filter0(students, student -> student.sex.equals("男"));
// 需求2:筛选18岁以下学生
List<Student> age = filter0(students, student -> student.age < 18);
}
interface Lambda {
boolean test(Student student);
}
static List<Student> filter0(List<Student> students, Lambda lambda) {
List<Student> result = new ArrayList<>();
for (Student student : students) {
if (lambda.test(student)) {
result.add(student);
}
}
return result;
}
// 参数,逻辑部分 student -> student.sex.equals("男")
static List<Student> filter(List<Student> students) {
List<Student> result = new ArrayList<>();
for (Student student : students) {
if (student.sex.equals("男")) {
result.add(student);
}
}
return result;
}
// student -> student.age < 18
static List<Student> filter2(List<Student> students) {
List<Student> result = new ArrayList<>();
for (Student student : students) {
if (student.age < 18) {
result.add(student);
}
}
return result;
}
@AllArgsConstructor
@Data
static class Student {
private String name;
private int age;
private String sex;
}
}
2. 延迟执行
在记录日志时,假设日志级别是 INFO,debug 方法会遇到下面的问题:
- 本不需要记录日志,但
expensive()
仍被执行了
public class Sample7 {
// static Logger logger = init(Level.DEBUG);
static Logger logger = init(Level.INFO);
public static void main(String[] args) {
if (logger.isDebugEnabled()) {
logger.debug("{}", expensive());
}
logger.debug("{}", expensive()); // info时,无日志输出,expensive()仍执行
logger.debug("{}", () -> expensive()); // 函数对象使得 expensive 延迟执行
}
static String expensive() {
System.out.println("执行耗时操作...");
return "日志";
}
// 日志级别、输出格式配置(略)
static Logger init(Level level) {
ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder()
.setStatusLevel(Level.ERROR)
.setConfigurationName("BuilderTest");
AppenderComponentBuilder appender =
builder.newAppender("Stdout", "CONSOLE")
.addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT)
.add(builder.newLayout("PatternLayout").addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable"));
builder.add(appender)
.add(builder.newRootLogger(level).add(builder.newAppenderRef("Stdout")));
Configurator.initialize(builder.build());
return LogManager.getLogger();
}
}
public abstract class AbstractLogger implements ExtendedLogger, LocationAwareLogger, Serializable {
public void debug(final String message, final Supplier<?>... paramSuppliers) {
this.logIfEnabled(FQCN, Level.DEBUG, (Marker)null, (String)message, (Supplier[])paramSuppliers);
}
public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final String message, final Supplier<?>... paramSuppliers) {
if (this.isEnabled(level, marker, message)) {
this.logMessage(fqcn, level, marker, message, paramSuppliers);
}
}
}
2. 函数编程语法
- 函数对象表现形式
- 函数对象类型
- 方法引用
- 闭包和柯里化
- 高阶函数
1. 函数对象表现形式
- lambda表达式。功能更全面
- 方法引用。写法更简洁
1. lambda表达式
- lambda 对象类型是由其行为决定,如果有一些 lambda 对象,它们的入参类型、返回值类型都一致,那么可以看作是同一类的 lambda 对象,它们的类型,用函数式接口来表示
// 明确指出参数类型
(int a, int b) -> a + b;
// 代码多于一行,不能省略{}以及最后一行的 return
(int a, int b) -> { int c = a + b; return c; }
// 可以根据上下文推断出参数类型时,可以省略参数类型
interface Lambda1 {
int op(int a, int b);
}
interface Lambda2 {
double op(double a, double b);
}
// 只有一个参数时,可以省略()
a -> a:
2. 方法引用
- 入参类型、返回值类型都一致,可以看作同一类的对象,也是用函数式接口表示
Math::max => (int a,int b) -> Math.max(a,b);
Student::getName => (Student stu) -> stu.getName();
System.out::println => (Object obj) -> System.out.println(obj);
Student::new => () -> new student();
2. 函数对象类型
仅包含一个抽象方法,@FunctionalInterface
修饰(函数式接口)
- 参数个数类型相同
- 返回值类型相同
public class CategoryTest {
@Data
static class Student {
private String name;
private String sex;
private int age;
}
// ----------------------------------------------------------------
Type1 t1 = a -> (a & 1) == 0;
Type1 t2 = a -> BigInteger.valueOf(a).isProbablePrime(100);
@FunctionalInterface
interface Type1 {
boolean op(int a);
}
// ----------------------------------------------------------------
Type2 t3 = (a, b, c) -> a + b + c;
@FunctionalInterface
interface Type2 {
int op(int a, int b, int c);
}
// ----------------------------------------------------------------
Type3 t4 = (a, b) -> a - b;
Type3 t5 = (a, b) -> a * b;
@FunctionalInterface
interface Type3 {
int op(int a, int b);
}
// ----------------------------------------------------------------
@FunctionalInterface
interface Type4 {
Student op();
}
@FunctionalInterface
interface Type5 {
List<Student> op();
}
// ----------------------------------------------------------------
Type6<Student> t6 = () -> new Student();
Type6<List<Student>> t7 = () -> new ArrayList<>();
@FunctionalInterface
interface Type6<T> {
T op();
}
// ----------------------------------------------------------------
Type7<String, Student> t8 = s -> s.getName();
Type7<Integer, Student> t9 = s -> s.getAge();
@FunctionalInterface
interface Type7<O, I> {
O op(I input);
}
@FunctionalInterface
interface IntTernaryOperator {
int op(int a, int b, int c);
}
// ----------------------------------------------------------------
public static void main(String[] args) {
IntPredicate x1 = a -> (a & 1) == 0;
IntPredicate x2 = a -> BigInteger.valueOf(a).isProbablePrime(100);
IntTernaryOperator x3 = (a, b, c) -> a + b + c;
IntBinaryOperator x4 = (a, b) -> a - b;
IntBinaryOperator x5 = (a, b) -> a * b;
Supplier<Student> x6 = () -> new Student();
Supplier<List<Student>> x7 = () -> new ArrayList<>();
Function<Student, String> x8 = s -> s.getName();
Function<Student, Integer> x9 = s -> s.getAge();
}
}
1. 常见函数接口
Runnable
() -> void
Callable
() -> T
Comparator
(T,T) -> int
Consumer, BiConsumer, IntConsumer, LongConsumer, DoubleConsumer
(T) -> void
,Bi是两参,Int指参数是 int
Function, BiFunction, Int Long Double ...
(T) -> R
,Bi是两参,Int指参数是 int
Predicate, BiPredicate, Int Long Double ...
(T) -> boolean
,Bi是两参,Int指参数是 int
Supplier, Int Long Double ...
() -> T
,Int 指返回值是 int
UnaryOperator, BinaryOperator, Int Long Double ...
(T) -> T
,Unary 一参,Binary 两参,Int 指参数是 int
2. 命名规则
函数接口的命名规律
- 带有 Unary 是一元的意思,表示一个参数
- 带有 Bi 或 Binary 是二元的意思,表示两个参数
- Ternary 三元
- Quatenary 四元
- ...
名称 | 含义 |
---|---|
Consumer | 有参,无返回值 |
Supplier | 无参,有返回值 |
Predicate | 有参,返回 boolean |
Operator | 有参,有返回值,并且类型一样 |
Function | 有参,有返回值 |
前缀 | 含义 |
---|---|
Unary | 一元 |
Binary | 二元 |
Ternary | 三元 |
Quatenary | 四元 |
... | ... |
public class Exercise3 {
public static void main(String[] args) {
List<Integer> list = List.of(1, 2, 3, 4, 5, 6);
List<Integer> result = filter(list, (Integer number) -> (number & 1) == 1);
System.out.println(result);
}
/*
(Integer number) -> (number & 1) == 0
*/
static List<Integer> filter(List<Integer> list, Predicate<Integer> predicate) {
List<Integer> result = new ArrayList<>();
for (Integer number : list) {
// 筛选:判断是否是偶数,以后可能改变筛选规则
if(predicate.test(number)) {
result.add(number);
}
}
return result;
}
/*
(Integer number) -> String.valueOf(number)
*/
static List<String> map(List<Integer> list) {
List<String> result = new ArrayList<>();
for (Integer number : list) {
// 转换:将数字转为字符串,以后可能改变转换规则
result.add(String.valueOf(number));
}
return result;
}
static List<String> map(List<Integer> list, Function<Integer, String> fun) {
List<String> result = new ArrayList<>();
for (Integer number : list) {
// 转换:将数字转为字符串,以后可能改变转换规则
result.add(fun.apply(number));
}
return result;
}
/*
(Integer number) -> System.out.println(number);
*/
static void consume(List<Integer> list) {
for (Integer number : list) {
// 消费:打印,以后可能改变消费规则
System.out.println(number);
}
}
static void consume(List<Integer> list, Consumer<Integer> consumer) {
for (Integer number : list) {
// 消费:打印,以后可能改变消费规则
consumer.accept(number);
}
}
/*
() -> ThreadLocalRandom.current().nextInt();
*/
static List<Integer> supply(int count) {
List<Integer> result = new ArrayList<>();
for (int i = 0; i < count; i++) {
// 生成:随机数,以后可能改变生成规则
result.add(ThreadLocalRandom.current().nextInt());
}
return result;
}
/*
() -> ThreadLocalRandom.current().nextInt();
*/
static List<Integer> supply(int count, Supplier<Integer> supplier) {
List<Integer> result = new ArrayList<>();
for (int i = 0; i < count; i++) {
// 生成:随机数,以后可能改变生成规则
result.add(supplier.get());
}
return result;
}
}
3. 方法引用
- 将现有方法的调用化为函数对象
- 静态方法
(String s) -> Integer.parseInt(s)
<=>Integer::parseInt
- 非静态方法
(stu) -> stu.getName()
<=>Student::getName
- 构造方法
() -> new student()
<=>Student::new
编号 | 格式 | 特点 | 备注 |
---|---|---|---|
1 | 类名::静态方法 | 参数一致 | |
2 | 类名::非静态方法 | 参数多一个该类对象 | |
3 | 对象::非静态方法 | 参数一致 | |
4 | 类名::new | 参数一致 | |
5 | this::非静态方法 | 3特例,少用 | |
6 | super::非静态方法 | 3特例,少用 |
1. 类名::静态
对应怎样的函数对象?
- 逻辑,就是执行此静态方法
- 参数,就是静态方法的参数
Math::abs -> Math.abs(n) (n) -> Math.abs(n)
Math::max -> Math.max(a,b) (a, b) -> Math.max(a, b)
public class MethodRef1 {
public static void main(String[] args) {
/*
需求:挑选出所有男性学生
*/
Stream.of(
new Student("张无忌", "男"),
new Student("周芷若", "女"),
new Student("宋青书", "男")
)
.filter(stu -> stu.sex().equals("男")) // lambda表达式
.filter(MethodRef1::isMale) // 静态方法引用
.filter(Student::isMale) // 非静态方法引用
.forEach(stu -> System.out.println(stu)); // lambda表达式
.forEach(MethodRef1::print); // 静态方法引用
.forEach(Student::print); // 非静态方法引用
}
public static boolean isMale(Student stu) {
return stu.sex().equals("男");
}
public static void print(Student stu) {
System.out.println(stu);
}
record Student(String name, String sex) {
public void print() {
System.out.println(this);
}
public boolean isMale() {
return this.sex.equals("男");
}
}
}
2. 类名::非静态
对应怎样的函数对象?执行流中的对象的方法
- 逻辑,就是执行此非静态方法
- 参数,一个是此类对象,另一个是非静态方法的参数
Student::getName -> stu.getName() (stu) -> stu.getName()
Student::setName -> stu.setName(name) (stu, name) -> stu.setName(name)
public class MethodRef2 {
public static void main(String[] args) {
highOrder(Student::hello);
highOrder((stu, msg) -> stu.hello(msg));
}
static void highOrder(Lambda lambda) {
System.out.println(lambda.transfer(new Student("张三"), "你好"));
}
@FunctionalInterface
interface Lambda {
String transfer(Student stu, String message);
}
@AllArgsConstructor
static class Student {
String name;
public String hello(String message) {
return this.name + " say: " + message;
}
}
}
3. 对象::非静态
它对应怎样的函数对象呢?
- 逻辑,执行此对象的非静态方法
- 参数,非静态方法的参数
System.out::println -> System.out.println(obj) (obj) -> System.out.println(obj)
public class MethodRef3 {
static class Util {
public boolean isMale(Student stu) {
return stu.sex().equals("男");
}
public String xyz(Student stu) {
return stu.name();
}
}
public static void main(String[] args) {
/*
(stu) -> util.isMale(stu)
(stu) -> util.xyz(stu)
*/
Util util = new Util();
Stream.of(
new Student("张无忌", "男"),
new Student("周芷若", "女"),
new Student("宋青书", "男")
)
.filter(util::isMale)
// .map(stu -> stu.name()) // lambda表达式
.map(util::xyz) // 对象::非静态
// .map(Student::name) // 类名::非静态
.forEach(System.out::println);
}
/**
* record 定义对象
* 自动生成name, sex属性的getter()
*/
record Student(String name, String sex) {
// record直接生成好
// public String name() {
// return this.name;
// }
}
}
4. 构造方法
对应怎样的函数对象?
- 逻辑,执行此构造方法
- 参数,构造方法的参数
Student::new -> new Student() () -> new Student()
Student::new -> new Student(name) (name) -> new Student(name)
public class MethodRef4 {
@ToString
static class Student {
private final String name;
private final Integer age;
public Student() {
this.name = "无参";
this.age = 18;
}
public Student(String name) {
this.name = name;
this.age = 18;
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
}
public static void main(String[] args) {
Supplier<Student> s1 = Student::new;
Function<String, Student> s2 = Student::new;
BiFunction<String, Integer, Student> s3 = Student::new;
System.out.println(s1.get());
System.out.println(s2.apply("张三"));
System.out.println(s3.apply("李四", 25));
}
}
5. this, super
对象::非静态方法
特例this::非静态方法
,只能用在类内部super::非静态方法
,只能用在类内部
public class MethodRef5 {
public static void main(String[] args) {
Stream<Student> stream = Stream.of(
new Student("张无忌", "男"),
new Student("周芷若", "女"),
new Student("宋青书", "男")
);
Util util = new UtilExt();
util.hiOrder(stream);
}
record Student(String name, String sex) {
}
static class Util {
// public子类可用
public boolean isMale(Student stu) {
return stu.sex().equals("男");
}
private boolean isFemale(Student stu) {
return stu.sex().equals("女");
}
// 过滤男性学生并打印
void hiOrder(Stream<Student> stream) {
stream
// .filter(stu -> this.isMale(stu))
.filter(this::isMale)
.forEach(System.out::println);
}
}
static class UtilExt extends Util {
// 过滤女性学生并打印
void hiOrder(Stream<Student> stream) {
stream
// .filter(stu -> this.isMale(stu))
// .filter(this::isMale)
.filter(super::isFemale)
.forEach(System.out::println);
}
}
}
6. 特例
- 对于无需返回值的函数接口
- eg:
Consumer
和Runnable
它们可以配合有返回值的函数对象使用
- eg:
- 函数接口和方法引用之间,可以差一个返回值
public class MethodRef7 {
public static void main(String[] args) {
Consumer<Object> x = MethodRef7::print1;
Function<Object, Integer> y = MethodRef7::print2;
Consumer<Object> z = MethodRef7::print2;
}
// 无返回值
static void print1(Object obj) {
System.out.println(obj);
}
// 有返回值
static int print2(Object obj) {
System.out.println(obj);
return 1;
}
}
7. 练习
public class Exercise4 {
record Student(String name) {
}
// lambda表达式 <=> 方法引用
public static void main(String[] args) {
Function<String, Integer> function1 = (String s) -> Integer.parseInt(s);
Function<String, Integer> function2 = Integer::parseInt;
BiPredicate<List<String>, String> biPredicate1 = (list, element) -> list.contains(element);
BiPredicate<List<String>, String> biPredicate2 = List::contains;
BiPredicate<Student, Object> biPredicate3 = (stu, obj) -> stu.equals(obj);
BiPredicate<Student, Object> biPredicate4 = Student::equals;
Predicate<File> predicate1 = (file) -> file.exists();
Predicate<File> predicate2 = File::exists;
Runtime runtime = Runtime.getRuntime();
Supplier<Long> supplier1 = () -> runtime.freeMemory();
Supplier<Long> supplier2 = Runtime.getRuntime()::freeMemory;
}
}
public class Exercise5 {
record Color(Integer red, Integer green, Integer blue) {
}
// `Color::new` 来构造 Color 对象,还应当补充哪些代码(函数式接口)
public static void main(String[] args) {
TernaryFunction lambda = Color::new; // (Integer, Integer, Integer) -> Color
Color white = lambda.create(255, 255, 255);
System.out.println(white);
}
@FunctionalInterface
interface TernaryFunction {
Color create(Integer red, Integer green, Integer blue);
}
}
public class Exercise6 {
record Student(String name, int age) {
boolean abc() {
return this.age() >= 18;
}
}
/*
传入参数时,分别用
1. 类名::静态方法
2. 类名::非静态方法
来表示【学生年龄大于等于18】的条件
*/
static void highOrder(Predicate<Student> predicate) {
List<Student> list = List.of(
new Student("张三", 18),
new Student("李四", 17),
new Student("王五", 20));
for (Student stu : list) {
if (predicate.test(stu)) {
System.out.println(stu + "通过测试");
}
}
}
static boolean ageGreaterOrEquals18(Student student) {
return student.age() >= 18;
}
public static void main(String[] args) {
// 静态
highOrder(Exercise6::ageGreaterOrEquals18);
// 非静态
highOrder(Student::abc);
}
}
4. 闭包
1. 什么是闭包
- 函数对象和外部变量绑定在一起
public class ClosureTest1 {
@FunctionalInterface
interface Lambda {
int op(int y);
}
static class Student {
int d;
public Student(int d) {
this.d = d;
}
}
static void highOrder(Lambda lambda) {
System.out.println(lambda.op(1));
}
// ------------------------------------------------------------
public static void main(String[] args) {
/*
- 函数对象 `(int y) -> x + y` 与外部变量 x 形成了闭包
- `effective final` 表示行为和 `final` 一样
- x修改报错
*/
int x = 10;
highOrder((int y) -> x + y);
x = 20; // 报错
Student stu = new Student(20);
Lambda lambda = y -> y + stu.d;
highOrder(lambda);
// stu = new Student(30); 报错
// 不报错 => 函数的不可变性被破坏。违背函数式编程
stu.d = 40;
highOrder(lambda);
}
// ------------------------------------------------------------
static int a = 1;
int b = 2;
public void test(int c) {
// 静态变量
highOrder(y -> a + y);
// 成员变量
highOrder(y -> b + y);
// 方法参数
highOrder(y -> c + y);
}
}
2. 闭包限制
- 闭包变量必须是
final
或effective final
effective final
:虽然没有用final
修饰,但就像是用final
修饰了一样,不能重新赋值,否则就语法错误- 意味着闭包变量,在装进包里的那一刻,就不能变化了
- 道理也简单,为了保证函数的不变性,防止破坏逻辑
3. 闭包作用
- 给函数执行提供数据手段
public class ClosureTest2 {
// 闭包作用:给函数对象提供参数以外的数据
public static void main(String[] args) throws IOException {
// 创建 10 个任务对象,并且每个任务对象给一个任务编号
List<Runnable> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
int k = i + 1; // 闭包变量为 `effective final`
Runnable task = () -> System.out.println(Thread.currentThread() + ":执行任务" + k);
list.add(task);
}
ExecutorService service = Executors.newVirtualThreadPerTaskExecutor();
for (Runnable task : list) {
service.submit(task);
}
System.in.read();
}
}
5. 柯里化Currying
1. 什么是柯里化
- 让接收多个参数的函数转换成一系列接收一个参数的函数
2. 如何实现柯里化
- 结合闭包实现
3. 柯里化的作用
- 让函数对象分步执行(本质上是利用多个函数对象和闭包)
public class Carrying0Test {
@FunctionalInterface
interface F2 {
int op(int a, int b);
}
@FunctionalInterface
interface Fa {
Fb op(int a);
}
@FunctionalInterface
interface Fb {
int op(int b);
}
// --------------------------------------------------
public static void main(String[] args) {
// 两个参数的函数对象
F2 f2 = (a, b) -> a + b;
System.out.println(f2.op(10, 20));
/* 改造:
(a) -> 返回另一个函数对象
(b) -> a + b
*/
Fa fa = (a) -> (b) -> a + b;
Fb fb = fa.op(10);
int r = fb.op(20);
System.out.println(r);
}
}
public class Carrying1Test {
@FunctionalInterface
interface Fa {
Fb op(List<Integer> a);
}
@FunctionalInterface
interface Fb {
Fc op(List<Integer> b);
}
@FunctionalInterface
interface Fc {
List<Integer> op(List<Integer> c);
}
/*
目标:把三份数据合在一起,逻辑既定,但数据不能一次得到
a -> 函数对象
b -> 函数对象
c -> 完成合并
*/
static Fb step1() {
List<Integer> x = List.of(1, 2, 3);
Fa fa = a -> b -> c -> {
List<Integer> list = new ArrayList<>();
list.addAll(a);
list.addAll(b);
list.addAll(c);
return list;
};
return fa.op(x);
}
static Fc step2(Fb fb) {
List<Integer> y = List.of(4, 5, 6);
return fb.op(y);
}
static void step3(Fc fc) {
List<Integer> z = List.of(7, 8, 9);
List<Integer> result = fc.op(z);
System.out.println(result);
}
public static void main(String[] args) {
step3(step2(step1()));
}
}
6. 高阶函数
- 所谓高阶,指它是其它函数对象的使用者
- 作用
- 将通用、复杂的逻辑隐含在高阶函数内
- 将易变、未定的逻辑放在外部的函数对象中
1. 内循环
背景
- 不想自己写集合遍历代码
- 不知道哪种集合遍历效率更高
- 对集合元素进行只读操作
public class C01InnerLoop {
public static void main(String[] args) {
List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7);
hiOrder(list, (value) -> System.out.println(value));
}
// 需求:逆序遍历集合,只想负责元素处理,不改变集合
public static <T> void hiOrder(List<T> list, Consumer<T> consumer) {
ListIterator<T> iterator = list.listIterator(list.size());
while (iterator.hasPrevious()) {
T value = iterator.previous();
consumer.accept(value);
}
}
}
2. 二叉树遍历
背景
- 不想自己写二叉树遍历代码
- 不知道哪种遍历方式更好
- 对树节点进行只读操作
public class C02BinaryTree {
public record TreeNode(int value, TreeNode left, TreeNode right) {
public String toString() {
return "%d ".formatted(value);
}
}
enum Type {
PRE, IN, POST
}
// 非递归
public static void traversal2(TreeNode root, Type type, Consumer<TreeNode> consumer) {
if (root == null) {
return;
}
// 前序处理值
if (type == Type.PRE) {
consumer.accept(root);
}
traversal2(root.left, type, consumer);
// 中序处理值
if (type == Type.IN) {
consumer.accept(root);
}
traversal2(root.right, type, consumer);
// 后序处理值
if (type == Type.POST) {
consumer.accept(root);
}
}
// 递归
public static void traversal(TreeNode root, Type type, Consumer<TreeNode> consumer) {
// 用来记住回去的路
LinkedList<TreeNode> stack = new LinkedList<>();
// 当前节点
TreeNode curr = root;
// 记录最近一次处理完的节点
TreeNode last = null;
// 没有向左走到头或者还有未归的路
while (curr != null || !stack.isEmpty()) {
// 左边未走完
if (curr != null) {
// 记住来时的路
stack.push(curr);
// ------------------ 处理前序遍历的值
if (type == Type.PRE) {
consumer.accept(curr);
}
// 下次向左走
curr = curr.left;
}
// 左边已走完
else {
// 上次的路
TreeNode peek = stack.peek();
// 没有右子树
if (peek.right == null) {
// ------------------ 处理中序、后序遍历的值
if (type == Type.IN || type == Type.POST) {
consumer.accept(peek);
}
last = stack.pop();
}
// 有右子树, 已走完
else if (peek.right == last) {
// ------------------ 处理后序遍历的值
if (type == Type.POST) {
consumer.accept(peek);
}
last = stack.pop();
}
// 有右子树, 未走完
else {
// ------------------ 处理中序遍历的值
if (type == Type.IN) {
consumer.accept(peek);
}
// 下次向右走
curr = peek.right;
}
}
}
}
public static void main(String[] args) {
/*
1
/ \
2 3
/ / \
4 5 6
前序 1 2 4 3 5 6 值左右
中序 4 2 1 5 3 6 左值右
后序 4 2 5 6 3 1 左右值
*/
TreeNode root = new TreeNode(1,
new TreeNode(2,
new TreeNode(4, null, null),
null
),
new TreeNode(3,
new TreeNode(5, null, null),
new TreeNode(6, null, null)
)
);
traversal2(root, Type.PRE, System.out::print);
System.out.println();
traversal2(root, Type.IN, System.out::print);
System.out.println();
traversal2(root, Type.POST, System.out::print);
System.out.println();
}
}
7. 简单流
- 模仿学习过的
Stream
,自己实现SimpleStream
- 提供基本的高阶函数。eg:
map, filter, forEach
- 提供基本的高阶函数。eg:
public class SimpleStream<T> {
private Collection<T> collection;
// 类上的范型不能作用到方法上,单独定义
public static <T> SimpleStream<T> of(Collection<T> collection) {
return new SimpleStream<>(collection);
}
private SimpleStream(Collection<T> collection) {
this.collection = collection;
}
// ---------------------------------------------------------------------------------------
// C:容器类型,supplier:用来创建容器
public <C> C collect(Supplier<C> supplier, BiConsumer<C, T> consumer) {
C c = supplier.get(); // 创建了容器
for (T t : collection) {
consumer.accept(c, t); // 向容器中添加元素
}
return c;
}
// ---------------------------------------------------------------------------------------
public SimpleStream<T> filter(Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for (T t : collection) {
if (predicate.test(t)) {
result.add(t);
}
}
return new SimpleStream<>(result);
}
public void forEach(Consumer<T> consumer) {
for (T t : collection) {
consumer.accept(t);
}
}
// ---------------------------------------------------------------------------------------
public <U> SimpleStream<U> map(Function<T, U> function) {
List<U> result = new ArrayList<>();
for (T t : collection) {
U u = function.apply(t);
result.add(u);
}
return new SimpleStream<>(result);
}
// ---------------------------------------------------------------------------------------
// o:p的初始值
public T reduce(T o, BinaryOperator<T> operator) {
T p = o; // 上次的合并结果
for (T t : collection) { // t 是本次遍历的元素
p = operator.apply(p, t);
}
return p;
}
}
List<Integer> list = List.of(1, 2, 3, 4, 5, 1, 2, 3);
@Test
public void collect() {
/*
key value
1 2
2 2
3 2
4 1
5 1
*/
// 返回值确定类型
HashMap<Integer, Integer> map1 = SimpleStream.of(list)
.collect(
HashMap::new,
(map, t) -> {
if (!map.containsKey(t)) {
map.put(t, 1);
} else {
Integer v = map.get(t);
map.put(t, v + 1);
}
}
);
System.out.println(map1);
/*
computeIfAbsent()
- 如果 key 在 map 中不存在,将 key 连同新生成的 value 值存入 map,并返回 value
- 如果 key 在 map 中存在,会返回此 key 上次的 value 值
1, 2, 3, 4, 5, 1, 2, 3
key value
1 AtomicInteger(2)
2 AtomicInteger(2)
3 AtomicInteger(2)
4 AtomicInteger(1)
5 AtomicInteger(1)
*/
HashMap<Integer, AtomicInteger> map2 = SimpleStream.of(list)
.collect(
HashMap::new,
(map, t) -> map.computeIfAbsent(t, k -> new AtomicInteger()).getAndIncrement()
);
System.out.println(map2);
// ------------------------------------------------------------
HashSet<Integer> set = SimpleStream.of(list)
.collect(HashSet::new, HashSet::add); // HashSet::add <=> (set, t) -> set.add(t)
System.out.println(set);
StringBuilder sb = SimpleStream.of(list)
.collect(StringBuilder::new, StringBuilder::append);
System.out.println(sb);
SimpleStream.of(list)
.collect(() -> new StringJoiner("-"), (joiner, t) -> joiner.add(String.valueOf(t)));
}
@Test
public void filter() {
SimpleStream.of(list)
.filter(x -> (x & 1) == 1)
.map(x -> x * x)
.forEach(System.out::println);
}
@Test
public void map() {
StringJoiner sj = SimpleStream.of(list)
.map(String::valueOf)
.collect(() -> new StringJoiner("-"), StringJoiner::add);
}
@Test
public void reduce() {
Integer sum = SimpleStream.of(list).reduce(0, Integer::sum);
Integer min = SimpleStream.of(list).reduce(Integer.MAX_VALUE, Math::min);
Integer max = SimpleStream.of(list).reduce(Integer.MIN_VALUE, Math::max);
}
1. 化简
- 两个元素,按照某种规则合并为一个
- 合并规则:
- 两个元素里挑小的
- 两个元素里挑大的
- 两个元素相加
@FunctionalInterface
public interface BinaryOperator<T> {
T apply(T a, T b);
}
Integer::max
Integer::min
Integer::sum
2. 收集
- 提供一个新的容器(集合),将元素加入其中
- 收集规则:
- 用
Set
收集 - 用
StringBuilder
收集 - 用
Map
收集
- 用
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
}
3. Stream API
- 一次使用:流只能使用一次(终结方法只能调用一次)
- 两类操作:
- 中间操作,lazy 懒惰的
- 终结操作,eager 迫切的
public class C15Summary {
public static void main(String[] args) {
/*
掌握 Stream 流的特性
1. 一次使用
2. 两类操作
- 中间操作(多次):lazy 懒惰
- 终结操作(单次):eager 迫切)
*/
Stream<Integer> s1 = Stream.of(1, 3, 5); // 水滴
// ----------------------------------- ------------------------- 阀门
s1
.map(x -> x + 1) // 水管
.filter(x -> x <= 5) // 水管
.forEach(x -> System.out.println(x)); // 水管 总阀门
}
}
1. filter()
public class C01FilterTest {
public static void main(String[] args) {
Stream.of(
new Fruit("草莓", "Strawberry", "浆果", "红色"),
new Fruit("桑葚", "Mulberry", "浆果", "紫色"),
new Fruit("杨梅", "Waxberry", "浆果", "红色"),
new Fruit("核桃", "Walnut", "坚果", "棕色"),
new Fruit("花生", "Peanut", "坚果", "棕色"),
new Fruit("蓝莓", "Blueberry", "浆果", "蓝色")
)
// .filter(f -> f.category().equals("浆果") && f.color().equals("蓝色"))
.filter(f -> f.category().equals("浆果"))
.filter(f -> f.color().equals("蓝色"))
.forEach(System.out::println);
}
record Fruit(String cname, String name, String category, String color) {
}
}
2. map()
public class C02MapTest {
public static void main(String[] args) {
Stream.of(
new Fruit("草莓", "Strawberry", "浆果", "红色"),
new Fruit("桑葚", "Mulberry", "浆果", "紫色"),
new Fruit("杨梅", "Waxberry", "浆果", "红色"),
new Fruit("核桃", "Walnut", "坚果", "棕色"),
new Fruit("草莓", "Peanut", "坚果", "棕色"),
new Fruit("蓝莓", "Blueberry", "浆果", "蓝色")
)
.map(f -> f.cname() + "酱")
.forEach(System.out::println);
}
record Fruit(String cname, String name, String category, String color) {
}
}
3. flatMap()
public class C03FlatMapTest {
public static void main(String[] args) {
Stream.of(
List.of(
new Fruit("草莓", "Strawberry", "浆果", "红色"),
new Fruit("桑葚", "Mulberry", "浆果", "紫色"),
new Fruit("杨梅", "Waxberry", "浆果", "红色"),
new Fruit("蓝莓", "Blueberry", "浆果", "蓝色")
),
List.of(
new Fruit("核桃", "Walnut", "坚果", "棕色"),
new Fruit("草莓", "Peanut", "坚果", "棕色")
)
)
.flatMap(list -> list.stream())
.forEach(System.out::println);
Integer[][] array2D = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
};
Arrays.stream(array2D)
.flatMap(array -> Arrays.stream(array))
.forEach(System.out::println);
}
record Fruit(String cname, String name, String category, String color) {
}
}
4. 构建流
- 用已有数据构建出
Stream
对象- 从集合构建
集合.stream()
- 从数组构建
Arrays.stream(数组)
- 从对象构建
Stream.of(对象 ...)
- 从集合构建
public class C04BuildTest {
public static void main(String[] args) {
// 1. 从集合构建
List.of(1, 2, 3).stream().forEach(System.out::println);
Set.of(1, 2, 3).stream().forEach(System.out::println);
Map.of("a", 1, "b", 2).entrySet().stream().forEach(System.out::println);
// 2. 从数组构建
int[] array = {1, 2, 3};
Arrays.stream(array).forEach(System.out::println);
// 3. 从对象构建
Stream.of(1, 2, 3, 4, 5).forEach(System.out::println);
}
}
1. 合并、截取
- 合并:
Stream.concat(stream1, stream2);
- 截取
stream.skip(2).limit(2)
concat.takeWhile(predicate)
直到条件不成立,舍弃剩余元素concat.dropWhile(predicate)
直到条件不成立,留下剩余元素
public class C05ConcatSplitTest {
public static void main(String[] args) {
// 1. 合并
Stream<Integer> s1 = Stream.of(1, 2, 3);
Stream<Integer> s2 = Stream.of(4, 5, 1, 2);
Stream<Integer> concat = Stream.concat(s1, s2);
concat.forEach(System.out::println);
/*
2. 截取 - 直接给出截取位置
skip(long n) 跳过 n 个数据,保留剩下的
limit(long n) 保留 n 个数据,剩下的不要
*/
concat.skip(2).forEach(System.out::print);
concat.limit(2).forEach(System.out::print);
concat.skip(2).limit(2).forEach(System.out::print);
/*
1 2 3 4 5 1 2
*/
/*
3. 截取 - 根据条件确定截取位置
takeWhile(Predicate p) 条件成立保留,一旦条件不成立,剩下的不要
dropWhile(Predicate p) 条件成立舍弃,一旦条件不成立,剩下的保留
*/
concat.takeWhile(x -> x < 3)
.forEach(System.out::print);
concat.dropWhile(x -> x < 3)
.forEach(System.out::print);
}
}
2. 生成流
- 简单生成:
IntStream.range(1, 10)
、IntStream.rangeClosed(0, 9)
- 依赖上一个值生成当前值:
IntStream.iterate(1, x -> x + 2)
- 不依赖上一个值生成当前值:
IntStream.generate(...)
public class C06GenerateTest {
public static void main(String[] args) {
// 1. IntStream.range()
IntStream.range(1, 10)
.forEach(System.out::println); // 左闭右开
IntStream.rangeClosed(1, 10).forEach(System.out::println); // 左闭右闭
// 2. IntStream.iterate() 生成 1 3 5 7 9 ... 奇数序列
// 根据上一个元素值来生成当前元素
IntStream.iterate(1, x -> x + 2).limit(10)
.forEach(System.out::println);
// iterate(int seed, IntPredicate hasNext, IntUnaryOperator next)
IntStream.iterate(1, x -> x <= 9, x -> x + 2)
.forEach(System.out::println);
// 3. IntStream.generate()
IntStream.generate(() -> ThreadLocalRandom.current().nextInt(100)).limit(5)
.forEach(System.out::println);
ThreadLocalRandom.current().ints(5, 0, 100).forEach(System.out::println);
}
}
5. 查找、判断
1. find()
filter(...).findFirst()
filter(...).findAny()
返回的是OptionalInt
对象,因为可能流中不存在偶数- 对于
OptionalInt
对象,一般需要用ifPresent
或orElse
(提供默认值)来处理
- 对于
findAny
在顺序流中与findFirst
表现相同,区别在于并行流下会更快
public class C07FindTest {
public static void main(String[] args) {
IntStream stream = IntStream.of(1, 2, 3, 4, 5, 6);
// 1. findFirst() 找到第一个元素。orElse()不存在,输出默认值
stream.filter(x -> (x & 1) == 0)
.findFirst().orElse(-1);
// ifPresent()存在,执行后面逻辑
stream.filter(x -> (x & 1) == 0)
.findFirst().ifPresent((x) -> System.out.println(x));
// 2. findAny() 找到任意一个元素
stream.filter(x -> (x & 1) == 0)
.findAny().ifPresent((x) -> System.out.println(x));
}
}
2. Match()
anyMatch(Predicate p)
allMatch(Predicate p)
noneMatch(Predicate p)
public class C08MatchTest {
public static void main(String[] args) {
IntStream stream = IntStream.of(1, 3, 5);
// 判断流中是否存在任意一个偶数
boolean anyMatch = stream.anyMatch(x -> (x & 1) == 0);
// 判断流是否全部是偶数
boolean allMatch = stream.allMatch(x -> (x & 1) == 0);
// 判断流是否全部不是偶数
boolean noneMatch = stream.noneMatch(x -> (x & 1) == 0);
}
}
6. distinct(), sorted()
- 去重、排序
public class C09SortTest {
public static void main(String[] args) {
// 1. 去重 `distinct()`
IntStream.of(1, 2, 3, 1, 2, 3, 3, 4, 5)
.distinct()
.forEach(System.out::println);
// 2. 排序
Stream.of(
new Hero("令狐冲", 90),
new Hero("风清扬", 98),
new Hero("独孤求败", 100),
new Hero("方证", 92),
new Hero("东方不败", 98),
new Hero("冲虚", 90),
new Hero("向问天", 88),
new Hero("任我行", 92),
new Hero("不戒", 88)
)
.sorted((a, b) -> a.strength() < b.strength() ? -1 : a.strength() == b.strength() ? 0 : 1)
.sorted((a, b) -> Integer.compare(a.strength(), b.strength()))
.sorted(Comparator.comparingInt(h -> h.strength()))
.sorted(Comparator.comparingInt(Hero::strength).reversed()) // 按武力降序
// 按武力降序,武力相等的按名字长度升序
.sorted(Comparator.comparingInt(Hero::strength).reversed()
.thenComparingInt(h -> h.name().length()))
.forEach(System.out::println);
}
record Hero(String name, int strength) {
}
}
- 增加一个辅助方法
record Hero(String name, int strength) {
int nameLength() {
return this.name.length();
}
}
.sorted((e, f) -> {
int res =
((Comparator<Hero>) (c, d) ->
((Comparator<Hero>) (a, b) -> Integer.compare(a.strength(), b.strength()))
.compare(d, c))
.compare(e, f);
return (res == 0) ? Integer.compare(e.nameLength(), f.nameLength()) : res;
})
- 如果不好看,改成下面的代码
.sorted(step3(step2(step1())))
static Comparator<Hero> step1() {
return (a, b) -> Integer.compare(a.strength(), b.strength());
}
static Comparator<Hero> step2(Comparator<Hero> step1) {
return (c, d) -> step1.compare(d, c);
}
static Comparator<Hero> step3(Comparator<Hero> step2) {
return (e, f) -> {
int res = step2.compare(e, f);
return (res == 0) ? Integer.compare(e.nameLength(), f.nameLength()) : res;
};
}
7. reduce()
reduce(init, (p, x) -> r)
- init 代表初始值
(p, x) -> r
是一个BinaryOperator
,作用是根据上次化简结果 p 和当前元素 x,得到本次化简结果 r
这样两两化简,可以将流中的所有元素合并成一个结果
/*
化简:两两合并,只剩一个
适合:最大值、最小值、求和、求个数...
.reduce((p, x) -> r) p 上次的合并结果,x 当前元素,r 本次合并结果
.reduce(init, (p, x) -> r)
.reduce(init, (p, x) -> r, (r1, r2) -> r)
*/
public class C10ReduceTest {
record Hero(String name, int strength) {
}
public static void main(String[] args) {
Stream<Hero> stream = Stream.of(
new Hero("令狐冲", 90),
new Hero("风清扬", 98),
new Hero("独孤求败", 100),
new Hero("方证", 92),
new Hero("东方不败", 98),
new Hero("冲虚", 90),
new Hero("向问天", 88),
new Hero("任我行", 92),
new Hero("不戒", 88)
);
// 1. 求武力最高的 hero。流中无元素 --> Optional。指定了初始值就返回初始值
Optional<Hero> result = stream.reduce((h1, h2) -> h1.strength() > h2.strength() ? h1 : h2);
Hero result = stream.reduce(new Hero("-", -1), (h1, h2) -> h1.strength() > h2.strength() ? h1 : h2);
System.out.println(result);
// 2. 求高手总数
Integer reduce = stream.map(h -> 1).reduce(0, (a, b) -> a + b);
// 3. 总武力值
int sum = stream.mapToInt(Hero::strength).sum();
// 4. 常用简化
long count = stream.count();
Optional<Hero> max = stream.max(Comparator.comparingInt(Hero::strength));
Optional<Hero> min = stream.min(Comparator.comparingInt(Hero::strength));
OptionalDouble average = stream.mapToInt(Hero::strength).average();
}
}
8. collect()
collect(supplier, accumulator, combiner)
supplier
是描述如何创建收集容器 c :()-> c
accumulator
是描述如何向容器 c 添加元素 x:(c, x) -> void
combiner
是描述如何合并两个容器:(c1, c2) -> void
- 串行流下不需要合并容器
- 并行流如果用的是并发容器,也不需要合并
public class C11CollectTest {
/*
收集:将元素收集入容器
.collect(() -> c, (c, x) -> void, ?)
() -> c 创建容器 c
(c, x) -> void 将元素 x 加入 容器 c
*/
public static void main(String[] args) {
Stream<String> stream = Stream.of("令狐冲", "风清扬", "独孤求败", "方证",
"东方不败", "冲虚", "向问天", "任我行", "不戒", "不戒", "不戒", "不戒");
// 1. 收集到 List
List<String> list1 = stream.collect(() -> new ArrayList<>(), (list, x) -> list.add(x), (a, b) -> {});
List<String> list2 = stream.collect(ArrayList::new, ArrayList::add, (a, b) -> {});
// 2. 收集到 Set
Set<String> set = stream.collect(LinkedHashSet::new, Set::add, (a, b) -> {});
// 3. 收集到 Map
Map<String, Integer> maps = stream.collect(HashMap::new, (map, x) -> map.put(x, 1), (a, b) -> {});
StringBuilder sb = stream.collect(StringBuilder::new, StringBuilder::append, (a, b) -> {});
StringJoiner sj = stream.collect(() -> new StringJoiner(","), StringJoiner::add, (a, b) -> {});
}
}
9. Collector
- 收集器 = 创建容器 + 怎样添加 + 两个容器元素合并到一起
// Collector 收集器
public class C12CollectorTest {
record Hero(String name, int strength) {
}
public static void main(String[] args) {
Stream<String> stream = Stream.of("令狐冲", "风清扬", "独孤求败", "方证",
"东方不败", "冲虚", "向问天", "任我行", "不戒");
// 1. 收集到 List
List<String> list = stream.collect(Collectors.toList());
// 2. 收集到 Set
Set<String> set = stream.collect(Collectors.toSet());
// 3. 收集到 StringBuilder(底层StringBuilder)
String sb = stream.collect(Collectors.joining());
// 4. 收集到 StringJoiner
String sj = stream.collect(Collectors.joining(","));
// 5. 收集到 Map
Map<String, Integer> map = stream.collect(Collectors.toMap(x -> x, x -> 1));
/*
Map
3: new ArrayList(["令狐冲","风清扬","向问天","任我行"])
4: new ArrayList(["独孤求败","东方不败"])
2: new ArrayList(["方证","冲虚","不戒"])
下游收集器
*/
// map1 = {2=[方证, 冲虚, 不戒], 3=[令狐冲, 风清扬, 向问天, 任我行], 4=[独孤求败, 东方不败]}
Map<Integer, List<String>> map1 = stream.collect(
Collectors.groupingBy(x -> x.length(), Collectors.toList())
);
// map2 = {2=方证,冲虚,不戒, 3=令狐冲,风清扬,向问天,任我行, 4=独孤求败,东方不败}
Map<Integer, String> map2 = stream.collect(
Collectors.groupingBy(x -> x.length(), Collectors.joining(","))
);
}
}
1. DownCollector
- 下游收集器
API | 含义 |
---|---|
mapping(x -> y, dc) | 将 x 转换为 y,用下游收集器 dc 收集 |
flatMapping(x -> subStream, dc) | 将 x 转换为 subStream,用下游收集器 dc 收集 |
filtering(x -> boolean, dc) | 过滤后,用下游收集器 dc 收集 |
counting() | 求个数 |
minBy((a, b) -> int) | 求最小 |
maxBy((a, b) -> int) | 求最大 |
summingInt(x -> int) | 转 int 后求和 |
averagingInt(x -> int) | 转 int 后求平均 |
reducing(init, (p, x) -> r) | init 初始值,用上次结果 p 和当前元素 x 生成本次结果r |
// 静态导入
import static java.util.stream.Collectors.*;
public class C13GroupingByTest {
record Hero(String name, int strength) {
}
public static void main(String[] args) {
Stream<Hero> stream = Stream.of(
new Hero("令狐冲", 90),
new Hero("风清扬", 98),
new Hero("独孤求败", 100),
new Hero("方证", 92),
new Hero("东方不败", 98),
new Hero("冲虚", 90),
new Hero("向问天", 88),
new Hero("任我行", 92),
new Hero("不戒", 88)
);
/*
1. 根据名字长度分组,分组后组内只保留他们的武力值
mapping(x -> y, dc)
- new Hero("令狐冲", 90) -> 90
- dc 下游收集器 down_collector
*/
Map<Integer, List<Integer>> map1 = stream.collect(groupingBy(
h -> h.name().length(),
mapping(h -> h.strength(), toList())
));
// map1 = {2=[92, 90, 88], 3=[90, 98, 88, 92], 4=[100, 98]}
System.out.println("map1 = " + map1);
/*
2. 根据名字长度分组,分组后组内过滤掉武力小于 90 的
filtering(x -> boolean, dc)
*/
// 在分组收集的过程中,执行过滤(用处不大,可先过滤)
Map<Integer, List<Hero>> map2 = stream.collect(groupingBy(
h -> h.name().length(),
filtering(h -> h.strength() >= 90, toList())
));
// map2 = { 2=[Hero[name=方证, strength=92], Hero[name=冲虚, strength=90]],
// 3=[Hero[name=令狐冲, strength=90], Hero[name=风清扬, strength=98], Hero[name=任我行, strength=92]],
// 4=[Hero[name=独孤求败, strength=100], Hero[name=东方不败, strength=98]]}
System.out.println("map2 = " + map2);
// 先过滤,再来分组收集
stream.filter(h -> h.strength() >= 90).collect(groupingBy(h -> h.name().length(), toList()));
/*
3. 根据名字长度分组,分组后组内保留人名,并且人名切分成单个字符
flatMapping(x -> substream, dc)
*/
"令狐冲".chars().mapToObj(Character::toString).forEach(System.out::println);
Map<Integer, List<String>> map3 = stream.collect(groupingBy(
h -> h.name().length(),
flatMapping(h -> h.name().chars().mapToObj(Character::toString), toList())
));
// map3 = {2=[方, 证, 冲, 虚, 不, 戒], 3=[令, 狐, 冲, 风, 清, 扬, 向, 问, 天, 任, 我, 行], 4=[独, 孤, 求, 败, 东, 方, 不, 败]}
System.out.println("map3 = " + map3);
/*
4. 根据名字长度分组,分组后求每组个数
counting()
*/
Map<Integer, Long> map4 = stream.collect(groupingBy(
h -> h.name().length(),
counting()
));
// map4 = {2=3, 3=4, 4=2}
System.out.println("map4 = " + map4);
/*
5. minBy((a,b)->int) 根据名字长度分组,分组后求每组武功最低的人
6. maxBy((a,b)->int) 根据名字长度分组,分组后求每组武功最高的人
*/
Map<Integer, Optional<Hero>> map5 = stream.collect(groupingBy(
h -> h.name().length(),
minBy(Comparator.comparingInt(Hero::strength))
));
// map5 = { 2=Optional[Hero[name=不戒, strength=88]],
// 3=Optional[Hero[name=向问天, strength=88]],
// 4=Optional[Hero[name=东方不败, strength=98]]}
System.out.println("map5 = " + map5);
stream.collect(groupingBy(
h -> h.name().length(),
maxBy(Comparator.comparingInt(Hero::strength))
));
/*
7. summingInt(x->int) 根据名字长度分组,分组后求每组武力和
8. averagingDouble(x->double) 根据名字长度分组,分组后求每组武力平均值
*/
Map<Integer, Integer> map6 = stream.collect(groupingBy(
h -> h.name().length(),
summingInt(h -> h.strength())
));
// map6 = {2=270, 3=368, 4=198}
System.out.println("map6 = " + map6);
stream.collect(groupingBy(
h -> h.name().length(),
averagingDouble(h -> h.strength())
));
/*
9. reducing(init, (p, x) -> r)
- 求和
- 求个数
*/
Map<Integer, Integer> map7 = stream.collect(groupingBy(
h -> h.name().length(),
mapping(h -> h.strength(), reducing(0, (p, x) -> p + x))));
// map7 = {2=270, 3=368, 4=198}
System.out.println("map7 = " + map7);
Map<Integer, Integer> map8 = stream.collect(groupingBy(
h -> h.name().length(),
mapping(h -> 1, reducing(0, (p, x) -> p + x))
));
// map8 = {2=3, 3=4, 4=2}
System.out.println("map8 = " + map8);
// 求平均,缺少 finisher
Map<Integer, double[]> map9 = stream.collect(groupingBy(
h -> h.name().length(),
mapping(h -> new double[]{h.strength(), 1},
reducing(new double[]{0, 0}, (p, x) -> new double[]{p[0] + x[0], p[1] + x[1]}))
));
for (Map.Entry<Integer, double[]> e : map9.entrySet()) {
System.out.println(e.getKey() + ":" + Arrays.toString(e.getValue()));
}
// 2:[270.0, 3.0]
// 3:[368.0, 4.0]
// 4:[198.0, 2.0]
}
}
10. 基本流
int 流 | 含义 |
---|---|
intstream.mapToObj(int -> obj) | 转换为 obj 流 |
intstream.boxed() | 转换为 Integer 流 |
intstream.sum() | 🎭求和 |
intstream.min() | 🎭求最小值, 返回 Optional |
intstream.max() | 🎭求最大值, 返回 Optional |
intstream.average() | 求平均值, 返回 Optional |
intstream.summaryStatistics() | 🎭综合 count sum min max average |
转换 | 含义 |
---|---|
stream.map(x -> y) | 将 x 转换为 y |
stream.flatMap(x -> substream) | 将 x 转换为 substream |
stream.mapMulti((x, consumer) -> void) | consumer 消费的 x 会进入结果 |
stream.mapToInt(x -> int) | 将 x 转换为 int |
stream.mapToLong(x -> long) | 将 x 转换为 long |
stream.mapToDouble(x -> double) | 将 x 转换为 double |
public class C14Effective {
record Hero(String name, int strength) {
}
/*
三种基本流
*/
public static void main(String[] args) {
IntStream a = IntStream.of(97, 98, 99);
LongStream b = LongStream.of(1L, 2L, 3L);
DoubleStream c = DoubleStream.of(1.0, 2.0, 3.0);
Stream<Integer> d = Stream.of(1, 2, 3);
IntSummaryStatistics stat = a.summaryStatistics();
long sum = stat.getSum();
long count = stat.getCount();
int max = stat.getMax();
int min = stat.getMin();
double average = stat.getAverage();
// IntStream => Stream
a.mapToObj(Character::toString)
.forEach(System.out::println);
Stream<Hero> stream = Stream.of(
new Hero("令狐冲", 90),
new Hero("风清扬", 98)
);
// Stream => IntStream
stream.mapToInt(Hero::strength)
.forEach(System.out::println);
}
}
11. 并行
// 并行流
public class C16Parallel {
public static void main(String[] args) {
List<Integer> collect1 = Stream.of(1, 2, 3, 4)
.collect(Collector.of(
() -> {
System.out.printf("%-12s %s%n", simple(), "create");
return new ArrayList<Integer>();
},
(list, x) -> {
List<Integer> old = new ArrayList<>(list);
list.add(x);
System.out.printf("%-12s %s.add(%d)=>%s%n", simple(), old, x, list);
},
(list1, list2) -> {
List<Integer> old = new ArrayList<>(list1);
list1.addAll(list2);
System.out.printf("%-12s %s.add(%s)=>%s%n", simple(), old, list2, list1);
return list1;
},
list -> null,
Collector.Characteristics.IDENTITY_FINISH
));
System.out.println("collect1 = " + collect1);
/*
1. 数据量问题: 数据量大时才建议用并行流
2. 线程会无限增加吗: 跟 cpu 能处理的线程数相关
3. 收尾的意义: 转不可变集合、StringBuilder 转 String ...
4. 是否线程安全: 不会有线程安全问题
5. 特性:
- 是否需要收尾(默认收尾)
- 是否需要保证顺序(默认保证)
- 容器是否支持并发(默认不需要支持)
6. 到达选择哪一种?
1. Characteristics.CONCURRENT + Characteristics.UNORDERED + 线程安全容器 并发量大性能可能会受影响
2. 默认 + 线程不安全容器 占用内存多,合并多也会影响性能
*/
List<Integer> collect2 = Stream.of(1, 2, 3, 4)
.parallel()
.collect(Collector.of(
() -> {
System.out.printf("%-12s %s%n", simple(), "create");
// 数据没有线程共享
return new ArrayList<Integer>(); // 1. 如何创建容器
// 支持并发,线程共享一个容器
// return new Vector<Integer>();
},
(list, x) -> {
List<Integer> old = new ArrayList<>(list);
list.add(x); // 2. 如何向容器添加数据
System.out.printf("%-12s %s.add(%d)=>%s%n", simple(), old, x, list);
},
(list1, list2) -> {
List<Integer> old = new ArrayList<>(list1);
list1.addAll(list2); // 3. 如何合并两个容器的数据
System.out.printf("%-12s %s.add(%s)=>%s%n", simple(), old, list2, list1);
return list1;
},
list -> {
System.out.printf("%-12s finish: %s=>%s%n", simple(), list, list);
// 不可变list
return Collections.unmodifiableList(list);
} // 4. 收尾
, Collector.Characteristics.IDENTITY_FINISH // 不需要收尾
, Collector.Characteristics.UNORDERED // 不需要保证顺序
, Collector.Characteristics.CONCURRENT // 容器需要支持并发
));
System.out.println(collect2);
collect2.add(100);
System.out.println(collect2);
}
private static String simple() {
String name = Thread.currentThread().getName();
int idx = name.indexOf("worker");
if (idx > 0) {
return name.substring(idx);
}
return name;
}
}
1. 效率
1. 数组求和
primitive
:用 loop 循环对 int 求和boxed
:用 loop 循环对 Integer 求和intStream
:用 IntStream 对 int 求和stream
:用 Stream 对 Integer 求和
元素个数 100
Benchmark | Mode | Cnt | Score (ns/op) | Error (ns/op) | Units |
---|---|---|---|---|---|
T01Sum.primitive | avgt | 5 | 25.424 | ± 0.782 | ns/op |
T01Sum.intStream | avgt | 5 | 47.482 | ± 1.145 | ns/op |
T01Sum.boxed | avgt | 5 | 72.457 | ± 4.136 | ns/op |
T01Sum.stream | avgt | 5 | 465.141 | ± 4.891 | ns/op |
元素个数 1_000
Benchmark | Mode | Cnt | Score (ns/op) | Error (ns/op) | Units |
---|---|---|---|---|---|
T01Sum.primitive | avgt | 5 | 270.556 | ± 1.277 | ns/op |
T01Sum.intStream | avgt | 5 | 292.467 | ± 10.987 | ns/op |
T01Sum.boxed | avgt | 5 | 583.929 | ± 57.338 | ns/op |
T01Sum.stream | avgt | 5 | 5948.294 | ± 2209.211 | ns/op |
元素个数 10_000
Benchmark | Mode | Cnt | Score (ns/op) | Error (ns/op) | Units |
---|---|---|---|---|---|
T01Sum.primitive | avgt | 5 | 2681.651 | ± 12.614 | ns/op |
T01Sum.intStream | avgt | 5 | 2718.408 | ± 52.418 | ns/op |
T01Sum.boxed | avgt | 5 | 6391.285 | ± 358.154 | ns/op |
T01Sum.stream | avgt | 5 | 44414.884 | ± 3213.055 | ns/op |
结论:
- 做数值计算,优先挑选基本流(IntStream 等)。数据量较大,其性能已经非常接近普通 for 循环
- 做数值计算,应当避免普通流(Stream)性能与其它几种相比,慢一个数量级
// 性能:求和
public class T01Sum {
@State(Scope.Benchmark)
public static class MyState {
public static final int COUNT = 10_000;
public int[] numbers = new int[COUNT];
public List<Integer> numberList = new ArrayList<>(COUNT);
public MyState() {
for (int i = 0; i < COUNT; i++) {
int x = i + 1;
numbers[i] = x;
numberList.add(i, x);
}
}
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public int primitive(MyState state) {
int sum = 0;
for (int number : state.numbers) {
sum += number;
}
return sum;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public int boxed(MyState state) {
int sum = 0;
for (Integer i : state.numberList) {
sum += i;
}
return sum;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public int stream(MyState state) {
return state.numberList.stream().reduce(0, (a, b) -> a + b);
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public int intStream(MyState state) {
return IntStream.of(state.numbers).sum();
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(T01Sum.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
// MyState state = new MyState();
// T01Sum test = new T01Sum();
// System.out.println(test.primitive(state));
// System.out.println(test.boxed(state));
// System.out.println(test.stream(state));
// System.out.println(test.intStream(state));
}
}
2. 求最大值
其中(原始数据都是 int,没有包装类)
primitive
:loop 循环求最大值sequence
:串行流求最大值parallel
:并行流求最大值custom
:自定义多线程并行求最大值
元素个数 100
Benchmark | Mode | Cnt | Score (ns/op) | Error (ns/op) | Units |
---|---|---|---|---|---|
T02Parallel.custom | avgt | 5 | 39619.796 | ± 1263.036 | ns/op |
T02Parallel.parallel | avgt | 5 | 6754.239 | ± 79.894 | ns/op |
T02Parallel.primitive | avgt | 5 | 29.538 | ± 3.056 | ns/op |
T02Parallel.sequence | avgt | 5 | 80.170 | ± 1.940 | ns/op |
元素个数 10_000
Benchmark | Mode | Cnt | Score (ns/op) | Error (ns/op) | Units |
---|---|---|---|---|---|
T02Parallel.custom | avgt | 5 | 41656.093 | ± 1537.237 | ns/op |
T02Parallel.parallel | avgt | 5 | 11218.573 | ± 1994.863 | ns/op |
T02Parallel.primitive | avgt | 5 | 2217.562 | ± 80.981 | ns/op |
T02Parallel.sequence | avgt | 5 | 5682.482 | ± 264.645 | ns/op |
元素个数 1_000_000
Benchmark | Mode | Cnt | Score (ns/op) | Error (ns/op) | Units |
---|---|---|---|---|---|
T02Parallel.custom | avgt | 5 | 194984.564 | ± 25794.484 | ns/op |
T02Parallel.parallel | avgt | 5 | 298940.794 | ± 31944.959 | ns/op |
T02Parallel.primitive | avgt | 5 | 325178.873 | ± 81314.981 | ns/op |
T02Parallel.sequence | avgt | 5 | 618274.062 | ± 5867.812 | ns/op |
结论:
- 并行流相对自己用多线程实现分而治之更简洁
- 并行流只有在数据量非常大时,才能充分发力,数据量少,不如串行流
// 性能:求最大值
public class T02Parallel {
static final int n = 1_000_000;
@State(Scope.Benchmark)
public static class MyState {
int[] numbers = new int[n];
{
for (int i = 0; i < n; i++) {
numbers[i] = ThreadLocalRandom.current().nextInt(10_000_000);
}
}
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public int primitive(MyState state) {
int max = 0;
for (int number : state.numbers) {
if (number > max) {
max = number;
}
}
return max;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public int sequence(MyState state) {
return IntStream.of(state.numbers).max().orElse(0);
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public int parallel(MyState state) {
return IntStream.of(state.numbers).parallel().max().orElse(0);
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public int custom(MyState state) throws ExecutionException, InterruptedException {
int[] numbers = state.numbers;
int step = n / 10;
ArrayList<Future<Integer>> result = new ArrayList<>();
try (ExecutorService service = Executors.newVirtualThreadPerTaskExecutor()) {
for (int j = 0; j < n; j += step) {
int k = j;
result.add(service.submit(() -> {
int max = 0;
for (int i = k; i < k + step; i++) {
if (numbers[i] > max) {
max = numbers[i];
}
}
return max;
}));
}
}
// System.out.println(result.size());
int max = 0;
for (Future<Integer> future : result) {
if (future.get() > max) {
max = future.get();
}
}
return max;
}
public static void main(String[] args) throws RunnerException, ExecutionException, InterruptedException {
Options opt = new OptionsBuilder()
.include(T02Parallel.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
// MyState state = new MyState();
// T02Parallel test = new T02Parallel();
// System.out.println(test.primitive(state));
// System.out.println(test.sequence(state));
// System.out.println(test.parallel(state));
// System.out.println(test.custom(state));
}
}
3. 并行(发)收集
元素个数 100
Benchmark | Mode | Cnt | Score (ns/op) | Error (ns/op) | Units |
---|---|---|---|---|---|
map_merge | avgt | 5 | 1312.389 | ± 90.683 | ns/op |
map_computeIfAbsent | avgt | 5 | 1776.391 | ± 255.271 | ns/op |
sequence | avgt | 5 | 1727.739 | ± 28.821 | ns/op |
parallelNoConcurrent | avgt | 5 | 27654.004 | ± 496.970 | ns/op |
parallelConcurrent | avgt | 5 | 16320.113 | ± 344.766 | ns/op |
元素个数 10_000
Benchmark | Mode | Cnt | Score (ns/op) | Error (ns/op) | Units |
---|---|---|---|---|---|
map_merge | avgt | 5 | 211526.546 | ± 13549.703 | ns/op |
map_computeIfAbsent | avgt | 5 | 203794.146 | ± 3525.972 | ns/op |
sequence | avgt | 5 | 237688.651 | ± 7593.483 | ns/op |
parallelNoConcurrent | avgt | 5 | 527203.976 | ± 3496.107 | ns/op |
parallelConcurrent | avgt | 5 | 369630.728 | ± 20549.731 | ns/op |
元素个数 1_000_000
Benchmark | Mode | Cnt | Score (ms/op) | Error (ms/op) | Units |
---|---|---|---|---|---|
map_merge | avgt | 5 | 69.154 | ± 3.456 | ms/op |
map_computeIfAbsent | avgt | 5 | 83.815 | ± 2.307 | ms/op |
sequence | avgt | 5 | 103.585 | ± 0.834 | ns/op |
parallelNoConcurrent | avgt | 5 | 167.032 | ± 15.406 | ms/op |
parallelConcurrent | avgt | 5 | 52.326 | ± 1.501 | ms/op |
结论:
sequence
:是一个容器单线程收集,数据量少时性能占优parallelNoConcurrent
:是多个容器多线程并行收集,时间应该花费在合并容器上,性能最差parallelConcurrent
:是一个容器多线程并发收集,在数据量大时性能较优
// 性能:并行(发)收集
// ConcurrentHashMap
// HashMap
public class T03Concurrent {
static final int n = 1000000;
@State(Scope.Benchmark)
public static class MyState {
int[] numbers = new int[n];
{
for (int i = 0; i < n; i++) {
numbers[i] = ThreadLocalRandom.current().nextInt(n / 10);
}
}
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public Map<Integer, Integer> loop1(MyState state) {
Map<Integer, Integer> map = new HashMap<>();
for (int number : state.numbers) {
map.merge(number, 1, Integer::sum);
}
return map;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public Map<Integer, AtomicInteger> loop2(MyState state) {
Map<Integer, AtomicInteger> map = new HashMap<>();
for (int number : state.numbers) {
map.computeIfAbsent(number, k -> new AtomicInteger()).getAndIncrement();
}
return map;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public Map<Integer, Long> sequence(MyState state) {
return Arrays.stream(state.numbers)
.boxed()
.collect(groupingBy(Function.identity(), counting()));
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public Map<Integer, Long> parallelNoConcurrent(MyState state) {
return Arrays.stream(state.numbers)
.boxed()
.parallel()
.collect(groupingBy(Function.identity(), counting()));
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public ConcurrentMap<Integer, Long> parallelConcurrent(MyState state) {
return Arrays.stream(state.numbers)
.boxed()
.parallel()
.collect(groupingByConcurrent(Function.identity(), counting()));
}
public static void main(String[] args) throws RunnerException, ExecutionException, InterruptedException {
Options opt = new OptionsBuilder()
.include(T03Concurrent.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
// MyState state = new MyState();
// T03Concurrent test = new T03Concurrent();
// System.out.println(test.loop1(state));
// System.out.println(test.loop2(state));
// System.out.println(test.sequence(state));
// System.out.println(test.parallelNoConcurrent(state));
// System.out.println(test.parallelConcurrent(state));
}
}
4. 实际应用
- 数据统计分析(Stream API)
- 异步处理(CompletableFuture)
- 框架设计(函数对象齐上阵)
- 并行计算
- UI事件
1. 统计分析
,event_time,order_id,product_id,category_id,category_code,brand,price,user_id,age,sex,local
0,2020-04-24 11:50:39 UTC,2294359932054536986,1515966223509089906,2.2681054266481713e+18,electronics.tablet,samsung,162.01,1.515915625441994e+18,24.0,女,海南
1,2020-04-24 11:50:39 UTC,2294359932054536986,1515966223509089906,2.2681054266481713e+18,electronics.tablet,samsung,162.01,1.515915625441994e+18,24.0,女,海南
2,2020-04-24 14:37:43 UTC,2294444024058086220,2273948319057183658,2.2681054301629975e+18,electronics.audio.headphone,huawei,77.52,1.5159156254478794e+18,38.0,女,北京
3,2020-04-24 14:37:43 UTC,2294444024058086220,2273948319057183658,2.2681054301629975e+18,electronics.audio.headphone,huawei,77.52,1.5159156254478794e+18,38.0,女,北京
4,2020-04-24 19:16:21 UTC,2294584263154074236,2273948316817424439,2.26810547136784e+18,,karcher,217.57,1.515915625443148e+18,32.0,女,广东
5,2020-04-26 08:45:57 UTC,2295716521449619559,1515966223509261697,2.268105442636858e+18,furniture.kitchen.table,maestro,39.33,1.5159156254503828e+18,20.0,男,重庆
6,2020-04-26 09:33:47 UTC,2295740594749702229,1515966223509104892,2.2681054281665088e+18,electronics.smartphone,apple,1387.01,1.5159156254487665e+18,21.0,男,北京
...
import static java.util.stream.Collectors.*;
public class AnalysisTest {
/*
数据格式
0 1 2 3 4 5 6 7 8 9 10 11
序号 下单时间 订单编号 商品编号 类别编号 类别码 品牌 价格 用户编号 年龄 性别 地区
*/
static final int INDEX = 0;
static final int TIME = 1;
static final int ORDER_ID = 2;
static final int PRODUCT_ID = 3;
static final int CATEGORY_ID = 4;
static final int CATEGORY_CODE = 5;
static final int BRAND = 6;
static final int PRICE = 7;
static final int USER_ID = 8;
static final int USER_AGE = 9;
static final int USER_SEX = 10;
static final int USER_REGION = 11;
static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
public static void main(String[] args) {
case8();
}
// 1. 统计每月的销售量(月增序)
private static void case1() {
// twr语法,会隐式加finally{},关闭流
try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
Map<YearMonth, Long> collect = lines.skip(1)
.map(line -> line.split(","))
.collect(
groupingBy(
array -> YearMonth.from(formatter.parse(array[TIME])), // key
TreeMap::new, // 容器
counting() // value
)
);
for (Map.Entry<YearMonth, Long> e : collect.entrySet()) {
System.out.println(e);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// ------------------------------------------------------------------------
// 2. 统计销售量最高的月份
private static void case2() {
try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
lines.skip(1)
.map(line -> line.split(","))
.collect(groupingBy(array -> YearMonth.from(formatter.parse(array[TIME])), counting()))
.entrySet().stream()
// .max(Comparator.comparingLong(e -> e.getValue()));
.max(Map.Entry.comparingByValue())
.ifPresent(System.out::println);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// ------------------------------------------------------------------------
// 3. 统计销售量最高的商品
private static void case3() {
// `Path.of()`为jdk11方法;`Paths.get()`为jdk8方法
try (Stream<String> lines = Files.lines(Paths.get("./data.txt"))) {
lines.skip(1)
.map(line -> line.split(","))
.collect(groupingBy(array -> array[PRODUCT_ID], counting()))
.entrySet().stream()
.max(Map.Entry.comparingByValue())
.ifPresent(System.out::println);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// ------------------------------------------------------------------------
// 4. 下单最多的前10名用户
private static void case41() {
try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
Map<String, Long> collect = lines.skip(1)
.map(line -> line.split(","))
.collect(groupingBy(array -> array[USER_ID], counting()));
// for (Map.Entry<String, Long> e : collect.entrySet()) {
// System.out.println(e);
// }
collect.entrySet().stream()
// <String, Long> 手动声明泛型
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(10)
.forEach(System.out::println);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void case42() {
try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
Map<String, Long> collect = lines.skip(1)
.map(line -> line.split(","))
.collect(groupingBy(array -> array[USER_ID], counting()));
// 小顶堆
MyQueue<Map.Entry<String, Long>> queue = collect.entrySet().stream()
.collect(
() -> new MyQueue<>(Map.Entry.comparingByValue(), 10),
MyQueue::offer,
AbstractQueue::addAll
);
while (!queue.isEmpty()) {
System.out.println(queue.poll());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 小顶堆。设置堆最大容量
static class MyQueue<E> extends PriorityQueue<E> {
private final int max;
public MyQueue(Comparator<? super E> comparator, int max) {
super(comparator);
this.max = max;
}
@Override
public boolean offer(E e) {
boolean r = super.offer(e);
if (this.size() > max) {
this.poll();
}
return r;
}
}
// ------------------------------------------------------------------------
// 5.1. 每个地区下单最多的用户
private static void case51() {
try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
Map<String, Map<String, Long>> collect = lines.skip(1)
.map(line -> line.split(","))
.collect(
groupingBy(
array -> array[USER_REGION],
groupingBy(
array -> array[USER_ID],
counting()
)
)
);
collect.entrySet().stream()
.map(e -> Map.entry(
e.getKey(),
e.getValue().entrySet().stream().max(Map.Entry.comparingByValue())
))
.forEach(System.out::println);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// ------------------------------------------------------------------------
// 5.2. 每个地区下单最多的前3用户
private static void case52() {
try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
Map<String, Map<String, Long>> collect = lines.skip(1)
.map(line -> line.split(","))
.collect(groupingBy(array -> array[USER_REGION], groupingBy(array -> array[USER_ID], counting())));
collect.entrySet().stream()
.map(e -> Map.entry(
e.getKey(),
e.getValue().entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(3)
.collect(toList())
// e.getValue().entrySet().stream()
// .collect(
// () -> new MyQueue<Map.Entry<String, Long>>(Map.Entry.comparingByValue(), 3),
// MyQueue::offer,
// MyQueue::addAll
// )
))
.forEach(System.out::println);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// ------------------------------------------------------------------------
// 6.1. 按类别统计销量
private static void case61() {
try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
Map<String, Long> collect = lines.skip(1)
.map(line -> line.split(","))
.filter(array -> !array[CATEGORY_CODE].isEmpty())
.collect(groupingBy(array -> array[CATEGORY_CODE], TreeMap::new, counting()));
for (Map.Entry<String, Long> e : collect.entrySet()) {
System.out.println(e);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 6.2. 按一级类别统计销量
private static void case62() {
try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
Map<String, Long> collect = lines.skip(1)
.map(line -> line.split(","))
.filter(array -> !array[CATEGORY_CODE].isEmpty())
.collect(groupingBy(AnalysisTest::firstCategory, TreeMap::new, counting()));
for (Map.Entry<String, Long> e : collect.entrySet()) {
System.out.println(e);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
static String firstCategory(String[] array) {
String c = array[CATEGORY_CODE];
int idx = c.indexOf(".");
return c.substring(0, idx);
}
// ------------------------------------------------------------------------
// 7. 按价格区间统计销量
private static void case7() {
try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
Map<String, Long> collect = lines.skip(1)
.map(line -> line.split(","))
.map(array -> Double.valueOf(array[PRICE]))
.collect(groupingBy(AnalysisTest::priceRange, counting()));
for (Map.Entry<String, Long> e : collect.entrySet()) {
System.out.println(e);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
static String priceRange(Double price) {
if (price < 100) {
return "[0,100)";
} else if (price >= 100 && price < 500) {
return "[100,500)";
} else if (price >= 500 && price < 1000) {
return "[500,1000)";
} else {
return "[1000,∞)";
}
}
// ------------------------------------------------------------------------
// 8. 不同年龄段女性所下不同类别订单
private static void case8() {
try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
Map<String, Map<String, Long>> map = lines.skip(1)
.map(line -> line.split(","))
.filter(array -> array[USER_SEX].equals("女"))
.filter(array -> !array[CATEGORY_CODE].isEmpty())
.collect(groupingBy(
AnalysisTest::ageRange,
groupingBy(AnalysisTest::firstCategory, TreeMap::new, counting())
));
for (Map.Entry<String, Map<String, Long>> e1 : map.entrySet()) {
for (Map.Entry<String, Long> e2 : e1.getValue().entrySet()) {
System.out.printf("%-12s%-15s%d%n", e1.getKey(), e2.getKey(), e2.getValue());
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
static String ageRange(String[] array) {
int age = Double.valueOf(array[USER_AGE]).intValue();
if (age < 18) {
return "[0,18)";
} else if (age < 30) {
return "[18,30)";
} else if (age < 50) {
return "[30,50)";
} else {
return "[50,∞)";
}
}
}
2. 异步处理
1. 线程池
public class C01ExecutorTest {
static final int INDEX = 0;
static final int TIME = 1;
static final int ORDER_ID = 2;
static final int PRODUCT_ID = 3;
static final int CATEGORY_ID = 4;
static final int CATEGORY_CODE = 5;
static final int BRAND = 6;
static final int PRICE = 7;
static final int USER_ID = 8;
static final int USER_AGE = 9;
static final int USER_SEX = 10;
static final int USER_REGION = 11;
static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
static Logger logger = LoggerFactory.getLogger("Test");
/*
同步:没有执行完之前,后续代码不能执行
异步:后续代码的执行,并不受异步操作的干扰
要让一段逻辑异步:
1. 需要有独立线程
2. 逻辑需要封装至函数对象,才能让此逻辑不是立刻执行,而是在新线程中的未来某刻执行
*/
public static void main(String[] args) {
logger.info("开始统计");
monthlySalesReport(); // 1. 同步操作
new Thread(() -> monthlySalesReport()).start(); // 2. 异步操作
logger.info("执行其它操作");
// 3. 线程池
try (ExecutorService service = Executors.newFixedThreadPool(3)) {
logger.info("开始统计");
/*
目标:将处理结果的逻辑放在 `monthlySalesReport()` 之外
做法1:将结果作为方法的返回值返回
*/
service.submit(() -> {
Map<YearMonth, Long> map = monthlySalesReport();
for (Map.Entry<YearMonth, Long> e : map.entrySet()) {
logger.info(e.toString());
}
});
logger.info("执行其它操作");
}
}
private static Map<YearMonth, Long> monthlySalesReport() {
try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
Map<YearMonth, Long> collect = lines.skip(1)
.map(line -> line.split(","))
.collect(
groupingBy(
array -> YearMonth.from(formatter.parse(array[TIME])), // key
TreeMap::new, // 容器
counting() // value
)
);
return collect;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
2. 后处理函数对象
public class C02ExecutorTest {
static final int INDEX = 0;
static final int TIME = 1;
static final int ORDER_ID = 2;
static final int PRODUCT_ID = 3;
static final int CATEGORY_ID = 4;
static final int CATEGORY_CODE = 5;
static final int BRAND = 6;
static final int PRICE = 7;
static final int USER_ID = 8;
static final int USER_AGE = 9;
static final int USER_SEX = 10;
static final int USER_REGION = 11;
static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
static Logger logger = LoggerFactory.getLogger("Test");
public static void main(String[] args) {
Consumer<Map<YearMonth, Long>> tolog = (map) -> {
for (Map.Entry<YearMonth, Long> e : map.entrySet()) {
logger.info(e.toString());
}
};
Consumer<Map<YearMonth, Long>> toFile = (map) -> {
String string = map.entrySet().stream().map(Object::toString).collect(Collectors.joining("\n"));
try {
Files.writeString(Path.of("./result.txt"), string);
} catch (IOException e) {
throw new RuntimeException(e);
}
};
try (ExecutorService service = Executors.newFixedThreadPool(3)) {
logger.info("开始统计");
/*
目标:将处理结果的逻辑放在 monthlySalesReport 之外
做法2:将处理结果的逻辑作为函数对象传递给方法
*/
service.submit(() -> {
monthlySalesReport(toFile);
});
logger.info("执行其它操作");
}
}
private static void monthlySalesReport(Consumer<Map<YearMonth, Long>> consumer) {
try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
Map<YearMonth, Long> map = lines.skip(1).map(line -> line.split(","))
.collect(groupingBy(
array -> YearMonth.from(formatter.parse(array[TIME])),
TreeMap::new,
counting()
));
consumer.accept(map);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
3. CompletableFuture
- 显式使用了线程池
- 函数对象嵌套使用,可读性差
public class C03CompletableFutureTest {
static Logger logger = LoggerFactory.getLogger("Test");
public static void main(String[] args) throws IOException {
// 1. 异步执行任务
// CompletableFuture 为守护线程,主线程结束,其结束
// CompletableFuture.runAsync() 在任务不需要返回结果时
// CompletableFuture.supplyAsync() 在任务需要处理结果时
CompletableFuture.runAsync(() -> logger.info("异步操作1"));
CompletableFuture.supplyAsync(() -> {
logger.info("异步操作2");
return "结果";
})
.thenApply(r -> r + "转换后")
.thenAccept(r -> logger.info(r));
System.in.read(); // 不让主线程立刻结束
// 2. 处理异步任务的结果
/*
thenApply(Function) 转换结果
thenApplyAsync 异步转换结果
thenAccept(Consumer) 消费结果
thenAcceptAsync(Consumer) 异步消费结果
*/
}
}
public class C04CompletableFutureTest {
static final int INDEX = 0;
static final int TIME = 1;
static final int ORDER_ID = 2;
static final int PRODUCT_ID = 3;
static final int CATEGORY_ID = 4;
static final int CATEGORY_CODE = 5;
static final int BRAND = 6;
static final int PRICE = 7;
static final int USER_ID = 8;
static final int USER_AGE = 9;
static final int USER_SEX = 10;
static final int USER_REGION = 11;
static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
static Logger logger = LoggerFactory.getLogger("Test");
// 1. 显式使用了线程池
// 2. 函数对象嵌套使用,可读性差
public static void main(String[] args) throws InterruptedException, IOException {
// 改进后
logger.info("开始统计");
CompletableFuture
.supplyAsync(() -> monthlySalesReport())
.thenAccept(map -> {
for (Map.Entry<YearMonth, Long> e : map.entrySet()) {
logger.info(e.toString());
}
});
logger.info("执行其它操作");
System.in.read();
}
private static Map<YearMonth, Long> monthlySalesReport() {
try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {
Map<YearMonth, Long> map = lines.skip(1)
.map(line -> line.split(","))
.collect(groupingBy(
array -> YearMonth.from(formatter.parse(array[TIME])),
TreeMap::new,
counting()
));
return map;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
3. 框架设计
- 什么是框架?
- 半成品软件,帮助开发者快速构建应用程序
- 框架提供的都是固定不变的、已知的、可以重用的代码
- 而那些每个应用不同的业务逻辑,变化的、未知的部分,则在框架外由开发者自己实现
1. 将未知交给子类
Spring 延迟创建 bean
classDiagram
class DefaultSingletonBeanRegistry {
- singletonObjects: Map
+ getSingleton(name, factory)
}
class AbstractAutowireCapableBeanFactory {
# createBean(name, definition, args)
}
Spring 中的很多类有非常复杂的继承关系,并且它们分工明确,职责都是划分好的。eg:
DefaultSingletonBeanRegistry
是父类,它有个职责是缓存单例 bean,用下面方法实现
public Object getSingleton(String beanName, ObjectFactory<?> factory)
- 但如何创建 bean,这个父类是不知道的,创建 bean 是子类
AbstractAutowireCapableBeanFactory
的职责
Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// ...
}
- 父类中
getSingleton
的内部就要使用singletonFactory
函数对象来获得创建好的对象
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
// ...
Object singletonObject = this.singletonObjects.get(beanName);
if(singletonObject == null) {
// ...
singletonObject = singletonFactory.getObject();
addSingleton(beanName, singletonObject);
}
}
- 最后子类创建单例 bean 时,会把
ObjectFactory
这个函数对象传进去- 创建其它 scope bean,不需要用
getSingleton
缓存
- 创建其它 scope bean,不需要用
protected <T> T doGetBean(...) {
// ...
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
// ...
return createBean(beanName, mbd, args);
});
}
// ...
}
2. 将未知交给用户
1. JdbcTemplate
create table student (
id int primary key auto_increment,
name varchar(16),
sex char(1)
);
insert into student values
(1, '赵一伤', '男'),
(2, '钱二败', '男'),
(3, '孙三毁', '男'),
(4, '李四摧', '男'),
(5, '周五输', '男'),
(6, '吴六破', '男'),
(7, '郑七灭', '男'),
(8, '王八衰', '男');
- 对
query
来讲,建立数据库连接,创建Statement
对象,执行查询这些步骤都是固定的 - 而结果要如何用java对象封装,这对框架代码是未知的,用
RowMapper
接口代表,将来它的lambda实现将结果转换成需要的java对象
// JdbcTemplate
public class C01JdbcTemplate {
record Student(int id, String name, String sex) {
}
private static void jdbc() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("******");
List<Student> list = new ArrayList<>();
try (Connection conn = dataSource.getConnection()) {
PreparedStatement stat = conn.prepareStatement("select * from student");
ResultSet rs = stat.executeQuery();
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String sex = rs.getString("sex");
list.add(new Student(id, name, sex));
}
for (Student student : list) {
System.out.println(student);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("******");
JdbcTemplate template = new JdbcTemplate(dataSource);
List<Student> list = template.query("select * from student",
(rs, row) -> {
int id = rs.getInt("id");
String name = rs.getString("name");
String sex = rs.getString("sex");
return new Student(id, name, sex);
});
for (Student student : list) {
System.out.println(student);
}
}
}
2. ApplicationListener
- 对 Spring 来讲,它并不知道如何处理事件。因此可以提供一个类型为
ApplicationListener
的 lambda 对象
// ApplicationListener
@SpringBootApplication
public class C02ApplicationListener {
public static class MyEvent extends ApplicationEvent {
public MyEvent(Object source) {
super(source);
}
}
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(C02ApplicationListener.class, args);
context.publishEvent(new MyEvent("容器启动"));
// event -> void
}
@Bean
public static ApplicationListener<MyEvent> listener() {
return event -> System.out.println(event);
}
@RestController
static class MyController {
@Autowired
private ApplicationContext context;
@GetMapping("/hello")
public String hello() {
context.publishEvent(new MyEvent("/hello 被访问"));
return "hello";
}
}
}
3. 延迟拼接条件
- MybatisPlus是否加条件
- 在调用 in 等方法添加条件时,第一个参数是 boolean 为 true 才会拼接 SQL 条件,否则不拼接
@SpringBootApplication
@MapperScan
public class TestMyBatisPlus {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(TestMyBatisPlus.class, args);
StudentMapper mapper = context.getBean(StudentMapper.class);
test(mapper, List.of("赵一伤"));
}
static void test(StudentMapper mapper, List<String> names) {
LambdaQueryWrapper<Student> query = new LambdaQueryWrapper<>();
// where name in (?,?,...)
// stu -> stu.getName() 无法获得列名
query.in(!names.isEmpty(), Student::getName, names);
/*
in => in
eq => =
...
Student::getName => 列名
*/
for (Student student : mapper.selectList(query)) {
System.out.println(student);
}
}
}
public abstract class AbstractWrapper<T, R, Children extends AbstractWrapper<T, R, Children>> extends Wrapper<T> implements Compare<Children, R>, Nested<Children, Children>, Join<Children>, Func<Children, R> {
protected final Children typedThis = this;
public Children in(boolean condition, R column, Collection<?> coll) {
return this.maybeDo(condition, () -> {
this.appendSqlSegments(this.columnToSqlSegment(column), SqlKeyword.IN, this.inExpression(coll));
});
}
protected final Children maybeDo(boolean condition, DoSomething something) {
if (condition) {
something.doIt();
}
return this.typedThis;
}
}
4. 方法引用找列名
- 用
LambdaQueryWrapper
拼接 sql 条件时,为了取得列名,采用了这个办法
public class TestSerializable {
public static void main(String[] args) throws Exception {
// 可序列化的函数对象
Type1 lambda = (Type1 & Serializable) Student::getName;
// Type1 lambda = (Type1 & Serializable) stu -> stu.getName();
// 函数对象 <=> 字节码 会额外存储类和方法的信息,运行时就可以根据这些信息找到属性,,从而进一步确定【列名】
for (Method method : lambda.getClass().getDeclaredMethods()) {
System.out.println(method);
}
SerializedLambda invoke = (SerializedLambda) lambda.getClass().getDeclaredMethod("writeReplace").invoke(lambda);
// invoke 是新对象,包含了原始函数对象的字节码,还包含了类和方法的额外信息
System.out.println(invoke.getClass());
System.out.println(invoke.getCapturingClass()); // 哪个类使用了这个函数对象
System.out.println(invoke.getImplClass()); // 哪个类实现了函数对象的逻辑
System.out.println(invoke.getImplMethodName()); // 哪个方法实现了函数对象的逻辑 getName
}
interface Type1 {
String abc(Student student);
}
}
它要做的事很简单,但内部实现却比较复杂
- 必须用
Student::getName
方法引用,而不能用其它 Lambda 对象 - 会实现
Serializable
接口,序列化时会把它变成SerializedLambda
- 想办法拿到
SerializedLambda
对象(反射调用 writeReplace) - 通过
SerializedLambda
能够获得它对应的实际方法,也就是getName()
和所在类Student
- 再通过方法名推导得到属性名(去掉 is,get)即 name
- 所在类
Student
知道了,属性名 name 也有了,就可以进一步确定列名- 属性上的
@TableField
指定的列名优先 - 没有
@TableField
,把属性名当作列名
- 属性上的
P.S.
- 不是很喜欢这种做法,比较恶心
- 但它确实是想做这么一件事:在代码中全面使用 java 的字段名,避免出现数据库的列名
4. 并行
- 统计页面的访问次数
// 统计web页面的访问次数
public class ParallelTest {
static Pattern reg = Pattern.compile("(\\S+) - \\[(.+)] (.+) (.+)");
private static final int FILES = 100;
public static void main(String[] args) throws ExecutionException, InterruptedException {
parallel();
}
private static Map<String, Long> one(int i) {
try (Stream<String> lines = Files.lines(Path.of(String.format("web_server_access_%d.log", i)))) {
return lines
.map(reg::matcher) // line -> reg.matcher(line)
.filter(Matcher::find) // mather -> mather.find()
.map(matcher -> new String[]{matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4)})
.collect(groupingBy(array -> array[2], counting()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 串行
private static void sequence() {
long start = System.currentTimeMillis();
Map<String, Long> m0 = new HashMap<>();
for (int i = 0; i < FILES; i++) {
Map<String, Long> mi = one(i);
m0 = merge(m0, mi);
}
for (Map.Entry<String, Long> e : m0.entrySet()) {
System.out.println(e);
}
System.out.println("cost: " + (System.currentTimeMillis() - start));
}
// 并行
private static void parallel() throws InterruptedException, ExecutionException {
long start = System.currentTimeMillis();
List<CompletableFuture<Map<String, Long>>> futures = new ArrayList<>();
for (int i = 0; i < FILES; i++) {
int k = i;
futures.add(CompletableFuture.supplyAsync(() -> one(k)));
}
CompletableFuture<Map<String, Long>> f0 = futures.getFirst();
for (int i = 1; i < futures.size(); i++) {
CompletableFuture<Map<String, Long>> fi = futures.get(i);
f0 = f0.thenCombine(fi, (m0, mi) -> merge(m0, mi));
}
Map<String, Long> map = f0.get();
for (Map.Entry<String, Long> e : map.entrySet()) {
System.out.println(e);
}
System.out.println("cost: " + (System.currentTimeMillis() - start));
}
/*
/login 2
/product1 1
===> /login 3
/product1 1
/product2 3
/login 1
/product2 3
*/
static Map<String, Long> merge(Map<String, Long> m1, Map<String, Long> m2) {
return Stream.of(m1, m2)
.flatMap(m -> m.entrySet().stream())
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1 + v2));
}
}
5. UI
// 在 UI 设计时的应用
public class UITest {
public static void main(String[] args) {
JFrame frame = new JFrame();
JButton button = new JButton("点我");
button.addActionListener(e -> System.out.println("已点击"));
frame.add(button);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
5. 实现原理
1. Lambda原理
// -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles
public class C01Lambda1 {
// lambda表达式对应的类
static final class MyLambda implements BinaryOperator<Integer> {
private MyLambda() {
}
@Override
public Integer apply(Integer a, Integer b) {
return lambda$main$2(a, b);
}
}
// 当前类中自动生成,lambda表达式对应的方法
private static Integer lambda$main$2(Integer a, Integer b) {
return a + b;
}
// ------------------------------------------------------------------------------------------
public static void main(String[] args) throws Throwable {
BinaryOperator<Integer> lambda = (a, b) -> a + b;
/*
lambda 表达式是一种语法糖,它仍然会被翻译成 类、对象、方法
1. 方法从哪来:编译器发现代码中出现了 lambda,就会在当前类中生成 private static 方法,方法内包含的就是 lambda 的逻辑
实验代码
for (Method method : C01Lambda1.class.getDeclaredMethods()) {
System.out.println(method);
}
2. 类和对象从哪来:运行期间动态生成
*/
/*
3. MethodHandle 铺垫(不同于反射)
MethodHandle 的执行权限与上下文相关
- 原本有权限调用的方法,正常能调用,通过 MethodHandle 反射也能调用
- 原本没权限调用的方法,正常不能调用,MethodHandle 反射也调用不了
*/
// 1. 反射调用静态方法
MethodHandle mh1 = MethodHandles.lookup()
.findStatic(C01Lambda1.class, "lambda$main$2", MethodType.methodType(Integer.class, Integer.class, Integer.class));
System.out.println(mh1.invoke(1, 2)); // 相当于 C01Lambda1.lambda$main$2(1, 2)
// 2. 反射调用非静态方法
MethodHandle mh2 = MethodHandles.lookup()
.findVirtual(MyLambda.class, "apply", MethodType.methodType(Integer.class, Integer.class, Integer.class));
System.out.println(mh2.invoke(new MyLambda(), 3, 4)); // 相当于通过 new MyLambda() 对象执行了 .apply(3, 4)
// 3. 反射调用构造
MethodHandle mh3 = MethodHandles.lookup()
.findConstructor(MyLambda.class, MethodType.methodType(void.class));
System.out.println(mh3.invoke()); // 相当于 new MyLambda()
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle impl = lookup.findStatic(C01Lambda1.class, "lambda$main$2", MethodType.methodType(Integer.class, Integer.class, Integer.class));
// 内部:生成函数对象所需的类 ASM
CallSite cs = LambdaMetafactory.metafactory(
lookup, // 1. lookup
"apply", // 2. 接口方法名
MethodType.methodType(BinaryOperator.class), // 3. 创建函数对象工厂方法长相 BinaryOperator factory()
MethodType.methodType(Object.class, Object.class, Object.class), // 4. 接口方法长相
impl, // 5. 实现方法 (本例就是下面的静态方法 lambda$main$2)
MethodType.methodType(Integer.class, Integer.class, Integer.class) // 6. 函数对象实际长相
);
// BinaryOperator factory() {return new MyLambda()} 伪代码
MethodHandle mh = cs.getTarget(); // 函数对象工厂方法
BinaryOperator<Integer> invoke = (BinaryOperator<Integer>) mh.invoke();
System.out.println(invoke.apply(5, 6));
}
}
- 在 jdk 21 中运行时添加虚拟机参数
-Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles
- 早期 jdk 添加的参数是(没有去进行版本比对了)
-Djdk.internal.lambda.dumpProxyClasses
若想实现在运行期间生成上述 class 字节码,有两种手段
- 一是动态代理,jdk 并没有采用这种办法来生成 Lambda 类
- 二是用 LambdaMetaFactory,它配合 MethodHandle API 在执行时更具性能优势
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.itheima.day5;
import java.util.function.BinaryOperator;
// $FF: synthetic class
final class C01Lambda1$$Lambda implements BinaryOperator {
private C01Lambda1$$Lambda() {
}
public Object apply(Object var1, Object var2) {
return C01Lambda1.lambda$main$0((Integer)var1, (Integer)var2);
}
}
Classfile /Users/listao/Documents/Java函数编程全面精讲/代码/test/target/classes/com/itheima/day5/C01Lambda1.class
Last modified Apr 20, 2024; size 1654 bytes
MD5 checksum 4bcf5f57b1f592e4efd95eb8a44305ce
Compiled from "C01Lambda1.java"
public class com.itheima.day5.C01Lambda1
minor version: 0
major version: 65
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "<init>":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = InvokeDynamic #0:#8 // #0:apply:()Ljava/util/function/BinaryOperator;
#8 = NameAndType #9:#10 // apply:()Ljava/util/function/BinaryOperator;
#9 = Utf8 apply
#10 = Utf8 ()Ljava/util/function/BinaryOperator;
#11 = Methodref #12.#13 // java/lang/Integer.intValue:()I
#12 = Class #14 // java/lang/Integer
#13 = NameAndType #15:#16 // intValue:()I
#14 = Utf8 java/lang/Integer
#15 = Utf8 intValue
#16 = Utf8 ()I
#17 = Methodref #12.#18 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#18 = NameAndType #19:#20 // valueOf:(I)Ljava/lang/Integer;
#19 = Utf8 valueOf
#20 = Utf8 (I)Ljava/lang/Integer;
#21 = Class #22 // com/itheima/day5/C01Lambda1
#22 = Utf8 com/itheima/day5/C01Lambda1
#23 = Utf8 Code
#24 = Utf8 LineNumberTable
#25 = Utf8 LocalVariableTable
#26 = Utf8 this
#27 = Utf8 Lcom/itheima/day5/C01Lambda1;
#28 = Utf8 main
#29 = Utf8 ([Ljava/lang/String;)V
#30 = Utf8 args
#31 = Utf8 [Ljava/lang/String;
#32 = Utf8 lambda
#33 = Utf8 Ljava/util/function/BinaryOperator;
#34 = Utf8 LocalVariableTypeTable
#35 = Utf8 Ljava/util/function/BinaryOperator<Ljava/lang/Integer;>;
#36 = Utf8 Exceptions
#37 = Class #38 // java/lang/Throwable
#38 = Utf8 java/lang/Throwable
#39 = Utf8 lambda$main$2
#40 = Utf8 (Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;
#41 = Utf8 a
#42 = Utf8 Ljava/lang/Integer;
#43 = Utf8 b
#44 = Utf8 lambda$main$0
#45 = Utf8 SourceFile
#46 = Utf8 C01Lambda1.java
#47 = Utf8 NestMembers
#48 = Class #49 // com/itheima/day5/C01Lambda1$MyLambda
#49 = Utf8 com/itheima/day5/C01Lambda1$MyLambda
#50 = Utf8 BootstrapMethods
#51 = MethodType #52 // (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
#52 = Utf8 (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
#53 = MethodHandle #6:#54 // invokestatic com/itheima/day5/C01Lambda1.lambda$main$0:(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;
#54 = Methodref #21.#55 // com/itheima/day5/C01Lambda1.lambda$main$0:(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;
#55 = NameAndType #44:#40 // lambda$main$0:(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;
#56 = MethodType #40 // (Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;
#57 = MethodHandle #6:#58 // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
#58 = Methodref #59.#60 // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
#59 = Class #61 // java/lang/invoke/LambdaMetafactory
#60 = NameAndType #62:#63 // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
#61 = Utf8 java/lang/invoke/LambdaMetafactory
#62 = Utf8 metafactory
#63 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
#64 = Utf8 InnerClasses
#65 = Utf8 MyLambda
#66 = Class #67 // java/lang/invoke/MethodHandles$Lookup
#67 = Utf8 java/lang/invoke/MethodHandles$Lookup
#68 = Class #69 // java/lang/invoke/MethodHandles
#69 = Utf8 java/lang/invoke/MethodHandles
#70 = Utf8 Lookup
{
public com.itheima.day5.C01Lambda1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/itheima/day5/C01Lambda1;
public static void main(java.lang.String[]) throws java.lang.Throwable;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=1
0: invokedynamic #7, 0 // InvokeDynamic #0:apply:()Ljava/util/function/BinaryOperator;
5: astore_1
6: return
LineNumberTable:
line 10: 0
line 66: 6
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 args [Ljava/lang/String;
6 1 1 lambda Ljava/util/function/BinaryOperator;
LocalVariableTypeTable:
Start Length Slot Name Signature
6 1 1 lambda Ljava/util/function/BinaryOperator<Ljava/lang/Integer;>;
Exceptions:
throws java.lang.Throwable
}
SourceFile: "C01Lambda1.java"
Error: unknown attribute
NestMembers: length = 0x4
00 01 00 30
BootstrapMethods:
0: #57 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#51 (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
#53 invokestatic com/itheima/day5/C01Lambda1.lambda$main$0:(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;
#56 (Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;
InnerClasses:
static final #65= #48 of #21; //MyLambda=class com/itheima/day5/C01Lambda1$MyLambda of class com/itheima/day5/C01Lambda1
public static final #70= #66 of #68; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
1. MethodHandle性能
- 正常方法调用、反射、MethodHandle、Lambda 的性能对比
Benchmark | Mode | Cnt | Score | Error | Units |
---|---|---|---|---|---|
Sample2.lambda | thrpt | 5 | 389307532.881 | ± 332213073.039 | ops/s |
Sample2.method | thrpt | 5 | 157556577.611 | ± 4048306.620 | ops/s |
Sample2.origin | thrpt | 5 | 413287866.949 | ± 65182730.966 | ops/s |
Sample2.reflection | thrpt | 5 | 91640751.456 | ± 37969233.369 | ops/s |
2. 方法引用原理
// -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles
public class C02MethodReference {
public static void main(String[] args) throws Throwable {
// 方法引用是一种语法糖,它仍然会被翻译成 类、对象、方法
// 1. 方法从哪来
// 2. 类、对象从哪来
Function<Student, String> func = Student::getName; // stu -> stu.getName()
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle impl = lookup.findVirtual(Student.class, "getName", MethodType.methodType(String.class));
CallSite cs = LambdaMetafactory.metafactory(
lookup,
"apply",
MethodType.methodType(Function.class),
MethodType.methodType(Object.class, Object.class),
impl,
MethodType.methodType(String.class, Student.class)
);
Function<Student, String> invoke = (Function<Student, String>) cs.getTarget().invoke();
Student stu = new Student();
stu.name = "张三";
System.out.println(invoke.apply(stu));
}
static final class MyMethodReference implements Function<Student, String> {
@Override
public String apply(Student student) {
return student.getName();
}
}
static class Student {
private String name;
public String getName() {
return name;
}
}
}
// $FF: synthetic class
final class C02MethodReference$$Lambda implements Function {
private C02MethodReference$$Lambda() {
}
public Object apply(Object var1) {
return ((C02MethodReference.Student)var1).getName();
}
}
3. 闭包原理
// $FF: synthetic class
final class C03Closure$$Lambda implements BinaryOperator {
private final int arg$1;
private C03Closure$$Lambda(int var1) {
this.arg$1 = var1;
}
public Object apply(Object var1, Object var2) {
return C03Closure.lambda$main$0(this.arg$1, (Integer)var1, (Integer)var2);
}
}
1. 局部变量
// -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles
public class C03Closure {
public static void main(String[] args) {
int c = 10;
// 编译期不生成;执行期生成
// invoke dynamic new C03Closure$$Lambda(10)
BinaryOperator<Integer> lambda = (a, b) -> a + b + c;
test(lambda);
for (Method method : C03Closure.class.getDeclaredMethods()) {
System.out.println(method);
}
// 1. 方法
// 2. 类和对象
}
static void test(BinaryOperator<Integer> lambda) {
// 局部变量,无法在这里访问。所以必须记录在lambda中
}
// ----------------------------------------------------------------------
final static class C03Closure$$Lambda implements BinaryOperator {
private final int c;
private C03Closure$$Lambda(int c) {
this.c = c;
}
public Object apply(Object a, Object b) {
return C03Closure.lambda$main$1(this.c, (Integer) a, (Integer) b);
}
}
static private Integer lambda$main$1(int c, Integer a, Integer b) {
return a + b + c;
}
}
2. static变量
// -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles
public class C04Closure {
static int c = 10; // 可以闭包,破坏不变性。直接能用类名定位
public static void main(String[] args) {
BinaryOperator<Integer> lambda = (a, b) -> a + b + c;
test(lambda);
System.out.println(lambda.apply(1, 2));
c = 20;
System.out.println(lambda.apply(1, 2));
for (Method method : C04Closure.class.getDeclaredMethods()) {
System.out.println(method);
}
}
static void test(BinaryOperator<Integer> lambda) {
}
static final class C04Closure$$Lambda implements BinaryOperator {
private C04Closure$$Lambda() {
}
public Object apply(Object a, Object b) {
return C04Closure.lambda$main$1((Integer) a, (Integer) b);
}
}
private static Integer lambda$main$1(Integer a, Integer b) {
return a + b + C04Closure.c;
}
}
public class TestLambda2 {
public static void main(String[] args) throws Throwable {
// int c = 10;
// test((a, b) -> a + b + c);
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType factoryType = MethodType.methodType(BinaryOperator.class, int.class);
MethodType interfaceMethodType = MethodType.methodType(Object.class, Object.class, Object.class);
MethodType implementsMethodType = MethodType.methodType(Integer.class, int.class, Integer.class, Integer.class);
MethodHandle implementsMethod = lookup.findStatic(TestLambda2.class, "lambda$main$1", implementsMethodType);
MethodType lambdaType = MethodType.methodType(Integer.class, Integer.class, Integer.class);
CallSite callSite = LambdaMetafactory.metafactory(lookup,
"apply", factoryType, interfaceMethodType,
implementsMethod,
lambdaType);
BinaryOperator<Integer> lambda = (BinaryOperator) callSite.getTarget().invoke(10);
test(lambda);
}
static Integer lambda$main$1(int c, Integer a, Integer b) {
return a + b + c;
}
static void test(BinaryOperator<Integer> lambda) {
System.out.println(lambda.apply(1, 2));
}
}
不同之处
- factoryType,除了原本的接口类型之外,多了实现方法第一个参数的类型
- 产生 lambda 对象的时候,通过 invoke 把这个参数的实际值传进去
这样产生的 LambdaType 就是这样,并且生成 Lambda 对象时,c 的值被固定为 10
final class LambdaType implements BinaryOperator {
private final int c;
private TestLambda2$$Lambda(int c) {
this.c = c;
}
public Object apply(Object a, Object b) {
return TestLambda2.lambda$main$1(this.c, (Integer)a, (Integer)b);
}
}
3. private变量
// -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles
public class C05Closure {
private int c = 10;
BinaryOperator<Integer> lambda = (a, b) -> a + b + c;
public static void main(String[] args) {
new C05Closure();
}
final class C05Closure$$Lambda implements BinaryOperator {
private final C05Closure c;
private C05Closure$$Lambda(C05Closure c) {
this.c = c;
}
public Object apply(Object a, Object b) {
return this.c.lambda$new$1((Integer)a, (Integer)b);
}
}
private Integer lambda$new$1(Integer a, Integer b) {
return a + b + c;
}
}
4. 引用类型变量
public class TestLambda4 {
static class MyRef {
int age;
public MyRef(int age) {
this.age = age;
}
}
public static void main(String[] args) throws Throwable {
// MyRef ref = new MyRef(10);
// test((a, b) -> a + b + ref.age);
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType factoryType = MethodType.methodType(BinaryOperator.class, MyRef.class);
MethodType interfaceMethodType = MethodType.methodType(Object.class, Object.class, Object.class);
MethodType implementsMethodType = MethodType.methodType(Integer.class, MyRef.class, Integer.class, Integer.class);
MethodHandle implementsMethod = lookup.findStatic(TestLambda4.class, "lambda$main$1", implementsMethodType);
MethodType lambdaType = MethodType.methodType(Integer.class, Integer.class, Integer.class);
CallSite callSite = LambdaMetafactory.metafactory(lookup,
"apply", factoryType, interfaceMethodType,
implementsMethod,
lambdaType);
BinaryOperator<Integer> lambda = (BinaryOperator) callSite.getTarget().bindTo(new MyRef(20)).invoke();
test(lambda);
}
static Integer lambda$main$1(MyRef c, Integer a, Integer b) {
return a + b + c.age;
}
static void test(BinaryOperator<Integer> lambda) {
System.out.println(lambda.apply(1, 2));
}
}
- 与捕获基本类型变量类似,不过除了
callSite.getTarget().invoke(new MyRef(20));
- 还可以
callSite.getTarget().bindTo(new MyRef(20)).invoke();
4. 流的构建、切分
- 并行流原理
// 流的构建与切分
public class C06Spliterator {
static Logger logger = LoggerFactory.getLogger("Test");
public static void main(String[] args) throws IOException {
List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
Integer[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9};
/*
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
*/
Stream<Integer> s1 = list.stream();
/*
public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) {
return StreamSupport.stream(spliterator(array, startInclusive, endExclusive), false);
}
*/
Stream<Integer> s2 = Arrays.stream(array);
// 切分迭代器。单向,消费过的不能再消费
Spliterator<Integer> sp0 = list.spliterator();
sp0.tryAdvance(System.out::println); // 1
sp0.tryAdvance(System.out::println); // 2
sp0.tryAdvance(System.out::println); // 3
System.out.println("===========================================");
sp0.forEachRemaining(System.out::println); // 4, 5, 6, 7, 8, 9
System.out.println("===========================================");
Spliterator<Integer> sp1 = list.spliterator();
Spliterator<Integer> sp2 = sp1.trySplit();
Spliterator<Integer> sp3 = sp2.trySplit();
System.out.println("sp1:");
sp1.forEachRemaining(System.out::println); // [5, 6, 7, 8, 9]
System.out.println("sp2:");
sp2.forEachRemaining(System.out::println); // [3, 4]
System.out.println("sp3:");
sp3.forEachRemaining(System.out::println); // [1, 2]
CompletableFuture.supplyAsync(() -> StreamSupport.stream(sp1, false).reduce(0, Integer::sum))
.thenAccept(x -> logger.info("{}", x));
CompletableFuture.supplyAsync(() -> StreamSupport.stream(sp2, false).reduce(0, Integer::sum))
.thenAccept(x -> logger.info("{}", x));
CompletableFuture.supplyAsync(() -> StreamSupport.stream(sp3, false).reduce(0, Integer::sum))
.thenAccept(x -> logger.info("{}", x));
// 15:05:08.184 [main] INFO Test -- 3
// 15:05:08.184 [ForkJoinPool.commonPool-worker-1] INFO Test -- 35
// 15:05:08.184 [ForkJoinPool.commonPool-worker-2] INFO Test -- 7
System.in.read();
}
}