Java8新特性(Lambda表达式、Stream流、Optional类)等
1. Lambda表达式由来
https://developer.aliyun.com/article/870827 从入门到入土:Lambda完整学习指南,包教包会!(上)
https://developer.aliyun.com/article/870831
https://developer.aliyun.com/article/870834
1.1 什么是Lambda表达式
Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。Lambda表达式还增强了集合库。
Java Lambda表达式的一个重要用法是简化某些匿名内部类(Anonymous Classes)的写法。实际上Lambda表达式并不仅仅是匿名内部类的语法糖,JVM内部是通过invokedynamic指令来实现Lambda表达式的。
1.2 语法
Lambda 表达式在 Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->”,该操作符被称 为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:
-
左侧:指定了 Lambda 表达式需要的所有参数
-
右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能。
1.语法格式一:无参,无返回值,Lambda 体只需一条语句。
Runnable r1 = () -> System.out.println("Hello Lambda!");
2.语法格式二:Lambda 需要一个参数。
Consumer<String> con = (x) -> System.out.println(x);
3.语法格式三:Lambda 只需要一个参数时,参数的小括号可以省略。
Consumer<String> con = x -> System.out.println(x);
4.语法格式四:Lambda 需要两个参数,并且有返回值。
1 Comparator<Integer> com = (x, y) -> { 2 System.out.println("函数式接口"); 3 return Integer.compare(x, y); 4 };
5.语法格式五:当 Lambda 体只有一条语句时,return 与大括号可以省略。
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
6.Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”。
1 Comparator<Integer> com = (Integer x, Integer y) -> { //Integer 类型可以省略 2 System.out.println("函数式接口"); 3 return Integer.compare(x, y); 4 }; 5 System.out.println(com.compare(3, 2)); // 1 6 7 BinaryOperator<Long> add = (Long x, Long y) -> x + y; 8 9 BinaryOperator<Long> addImplicit = (x, y) -> x + y;// 类型推断 10 System.out.println(addImplicit.apply(1L, 99L)); // 100
1.3 使用Lambda表达式的要求
也许你已经想到了,能够使用Lambda的依据是必须有相应的 函数式接口。
函数式接口,是指内部只有一个抽象方法的接口。这一点跟Java是强类型语言吻合,也就是说你并不能在代码的任何地方任性的写Lambda表达式。实际上Lambda的类型就是对应函数接口的类型。Lambda表达式另一个依据是类型推断机制,在上下文信息足够的情况下,编译器可以推断出参数表的类型,而不需要显式指名。
1.3.1 函数式接口
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
函数式接口可以被隐式转换为 lambda 表达式。
Lambda 表达式和方法引用(实际上也可认为是Lambda表达式)上。
如定义了一个函数式接口如下:
package java8; @FunctionalInterface public interface GreetingService { void sayMessage(String message); } class Test { public static void main(String[] args) { // 使用——java 8之前:用匿名类 GreetingService greetService1 = new GreetingService() { @Override public void sayMessage(String message) { System.out.println("匿名类:" + message); } }; greetService1.sayMessage("hello"); // 使用——java 8及之后:可以用Lambda表达式 GreetingService greetService2 = message -> System.out.println("Lambda表达式:" + message); greetService2.sayMessage("helo"); } }
Java8为函数式接口引入了一个新的注解@FunctionalInterface,主要用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口的定义时,编译器会报错。
函数式接口可以对现有的函数友好地支持 lambda。
JDK 1.8 之前已有的函数式接口:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.lang.reflect.InvocationHandler
- java.beans.PropertyChangeListener
- java.awt.event.ActionListener
- javax.swing.event.ChangeListener
JDK 1.8 新增加的函数接口:
- java.util.function
java.util.function 它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口有 https://www.runoob.com/java/java8-functional-interfaces.html
1.3.1.1 多线程&匿名类复习
Thread类就是代表了线程的抽象,由于线程的启动执行等必然要和底层的操作系统打交道,所有在Thread类中有很多native修饰的本地方法。这个类中也包含了很多对于线程的操作。比如通过构造方法我们可以创建线程,通过start方法我们可以启动线程,还有interrupt, sleep, join,yield等常用的方法。
Runnable本身是一个接口,里边有一个无返回值的方法run, 这个接口抽象的是线程任务,就是这个线程要做什么,他仅仅只代表任务,它没有启动线程的能力,因此必须使用Thread类中的start方法才能够启动一个线程。
而Thread本身也实现了Runnable接口,这个类里也有run方法,所以我们(1)可以通过继承Thread类重写run方法的方式来指定我们的线程任务。 而(2)在Thread的有参构造方法中,我们也可以通过外部传入一个Runnable来指定线程任务。(指定线程任务的两种方法)
1 /** 2 这段代码创建了一个实现了Runnable接口的匿名类实例r1,并通过调用r1.run()方法执行了该实例中的run方法。 3 然而,这段代码并没有真正地实现多线程执行。run方法在这里是直接在当前线程中被调用的,而没有在一个新的线程中启动 4 **/ 5 Runnable r1 = new Runnable() { 6 @Override 7 public void run() { 8 System.out.println("**线程任务:匿名类***"); 9 } 10 }; 11 r1.run(); 12 13 14 /** 创建新线程-法1: 15 实现Runnable接口的匿名内部类(实例r2),来创建线程任务 16 new Thread(r2)Thread的构造器传入线程任务r2,并调用start启动一个新的线程 17 **/ 18 Runnable r2 = new Runnable() { 19 @Override 20 public void run() { 21 System.out.println("**线程任务:匿名类***"); 22 } 23 }; 24 25 // 创建一个新线程并启动 26 new Thread(r2).start(); 27 28 29 30 /** 创建新线程-法2: 31 继承Thread类,并重新run方法(线程任务执行逻辑),new该类,并调用start启动新线程 32 **/ 33 class MyThread extends Thread { 34 @Override 35 public void run() { 36 System.out.println("**线程任务:继承于Thread类,并重写run***"); 37 } 38 39 public static void main(String[] args) { 40 // 创建一个新线程并启动 41 new MyThead().start(); 42 } 43 } 44 45 46 /** 创建新线程-法2-new: 47 匿名内部类继承Thread类,并重新run方法(线程任务执行逻辑),并调用start启动新线程 48 **/ 49 new Thread(){ 50 @Override 51 public void run() { 52 System.out.println("线程执行起来了....."); 53 } 54 }.start();
两个易错多线程例子
// ThreadTest.java 源码 class MyThread extends Thread{ private int ticket=10; public void run(){ for(int i=0;i<20;i++){ if(this.ticket>0){ System.out.println(this.getName()+" 卖票:ticket"+this.ticket--); } } } }; public class ThreadTest { public static void main(String[] args) { // 启动3个线程t1,t2,t3;每个线程各卖10张票! MyThread t1=new MyThread(); MyThread t2=new MyThread(); MyThread t3=new MyThread(); t1.start(); t2.start(); t3.start(); } }
运行结果:
Thread-0 卖票:ticket10 Thread-1 卖票:ticket10 Thread-2 卖票:ticket10 Thread-1 卖票:ticket9 Thread-0 卖票:ticket9 Thread-1 卖票:ticket8 Thread-2 卖票:ticket9 Thread-1 卖票:ticket7 Thread-0 卖票:ticket8 Thread-1 卖票:ticket6 Thread-2 卖票:ticket8 Thread-1 卖票:ticket5 Thread-0 卖票:ticket7 Thread-1 卖票:ticket4 Thread-2 卖票:ticket7 Thread-1 卖票:ticket3 Thread-0 卖票:ticket6 Thread-1 卖票:ticket2 Thread-2 卖票:ticket6 Thread-2 卖票:ticket5 Thread-2 卖票:ticket4 Thread-1 卖票:ticket1 Thread-0 卖票:ticket5 Thread-2 卖票:ticket3 Thread-0 卖票:ticket4 Thread-2 卖票:ticket2 Thread-0 卖票:ticket3 Thread-2 卖票:ticket1 Thread-0 卖票:ticket2 Thread-0 卖票:ticket1
结果说明:
(01) MyThread继承于Thread,它是自定义个线程。每个MyThread都会卖出10张票。
(02) 主线程main创建并启动3个MyThread子线程。每个子线程都各自卖出了10张票。
1 // RunnableTest.java 源码 2 class MyThreadTask implements Runnable{ 3 private int ticket=10; 4 public void run(){ 5 for(int i=0;i<20;i++){ 6 if(this.ticket>0){ 7 System.out.println(Thread.currentThread().getName()+" 卖票:ticket"+this.ticket--); 8 } 9 } 10 } 11 }; 12 13 public class RunnableTest { 14 public static void main(String[] args) { 15 MyThreadTask mtt=new MyThreadTask(); 16 17 // 启动3个线程t1,t2,t3(它们共用一个Runnable对象:即线程任务),这3个线程一共卖10张票!才能实现真正的多线程执行 18 Thread t1=new Thread(mtt); 19 Thread t2=new Thread(mtt); 20 Thread t3=new Thread(mtt); 21 t1.start(); 22 t2.start(); 23 t3.start(); 24 } 25 }
运行结果:
Thread-0 卖票:ticket10 Thread-2 卖票:ticket8 Thread-1 卖票:ticket9 Thread-2 卖票:ticket6 Thread-0 卖票:ticket7 Thread-2 卖票:ticket4 Thread-1 卖票:ticket5 Thread-2 卖票:ticket2 Thread-0 卖票:ticket3 Thread-1 卖票:ticket1
结果说明:
(01) 和上面“MyThread继承于Thread”不同;这里的MyThreadTask实现了Runnable接口。
(02) 主线程main创建并启动3个子线程,而且这3个子线程都是基于“mtt这个Runnable线程任务”而创建的。运行结果是这3个子线程一共卖出了10张票。这说明它们是共享了MyThreadTask接口的。
匿名类相关
Java 中可以实现一个类中包含另外一个类,且不需要提供任何的类名直接实例化。
主要是用于在我们需要的时候创建一个对象来执行特定的任务,可以使代码更加简洁。
匿名类是不能有名字的类,它们不能被引用,只能在创建时用 new 语句来声明它们。
匿名类语法格式:
1 class outerClass { 2 3 // 定义一个匿名类 4 Type object1 = new Type(parameterList) { // Type可以是一个类或接口 5 // 匿名类代码 6 }; 7 }
以上的代码创建了一个匿名类对象 object1,匿名类是表达式形式定义的,所以末尾以分号 ; 来结束。
匿名类通常继承一个父类或实现一个接口。
1.3.2 非函数式编程与函数式编程的对比
假设我们有一个使用Python编写的简单函数式编程例子,该例子是对一个列表中的每个元素进行平方操作:
函数式编程:
函数式编程是一种编程范式,它将计算过程视为表达式求值,强调程序的构建块主要是不可变数据和纯函数。纯函数是指这样的函数:给定相同的输入,总是产生相同的输出,而且在执行过程中不产生副作用(即不修改外部状态)。这种编程方式便于理解和推理,也促进了代码的重用和并行化。
1 # 函数式编程示例 2 def square(x): 3 return x * x 4 5 numbers = [1, 2, 3, 4, 5] 6 squared_numbers = list(map(square, numbers))
这个例子中,map
函数配合square
函数以一种声明式(declarative)的方式对列表中的每个元素进行了处理,体现了函数式编程的特点:无副作用、不可变数据和函数作为一等公民。
- 定义的这个
square
函数就是一个纯函数,因为对于任何给定的输入x
,它总是返回相同的结果x*x
,并且执行过程中不改变外部状态。 -
map
函数是一个高阶函数,它接受一个函数和一个可迭代对象作为参数,并返回一个新的迭代器,该迭代器产生的是原可迭代对象的每个元素经过指定函数转换后的结果。 - 最后,使用
sum
函数来计算平方后的数字的总和。
在这个过程中,我们没有直接修改原始数据,而是创建了新的数据结构(平方后的数字列表),并且使用的都是没有副作用的纯函数,这正是函数式编程的核心思想之一。此外,由于函数组合的方式清晰明了,这种编程风格往往使得代码更加简洁、易于理解和维护。
1 # 非函数式(命令式)编程示例 2 numbers = [1, 2, 3, 4, 5] 3 squared_numbers = [] 4 5 for number in numbers: 6 squared_number = number * number 7 squared_numbers.append(squared_number)
在这个非函数式编程的例子中,我们通过一个显式的for
循环遍历列表,对每个元素执行平方操作,并通过append
方法将结果添加到新的列表中。这种方式更加直接地控制了程序的流程,体现了命令式编程的特征:通过显式地指定每一步的操作来改变程序的状态。
总结来说,函数式编程倾向于使用高阶函数和不可变数据结构来表达计算,而命令式或过程式编程则更直接地描述了计算机应如何执行每一步操作来达到目标状态。
1.3.3 JAVA8的新特性(重点为接口)
Java 8在2014年发布,引入了许多重要的新特性,包括但不限于:
-
Lambda表达式:这是Java 8最显著的特性之一,它允许以更简洁的方式表示匿名函数,极大地促进了函数式编程风格在Java中的应用。
-
Stream API:引入了一个新的数据处理模型,可以以声明性方式对集合进行操作,如过滤、映射、排序等,提高了代码的可读性和并行处理能力。
-
默认方法和静态方法在接口中:允许在接口中定义默认实现的方法和静态方法,这为接口的演化提供了更好的兼容性,同时增强了接口的功能性。
-
方法引用:与lambda表达式紧密相关,提供了一种更简洁的语法来引用已有方法,特别是对于那些符合函数描述符的方法。
-
Optional类:用于解决空指针异常的问题,它是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
-
新的日期/时间API:引入了
java.time
包,提供了更丰富、更强大的日期和时间操作类,如LocalDate, LocalTime, LocalDateTime等,替代了旧的java.util.Date
和java.util.Calendar
。
Java 8对接口的定义引入了几个重大的变化,旨在提高接口的灵活性和表达能力。以下是Java 8中接口定义的几个不同点及其例子:
-
默认方法(Default Methods):
Java 8允许在接口中定义带有具体实现的方法,称为默认方法。这通过使用default
关键字实现。默认方法为接口提供了向后兼容的演进方式,同时允许在不改变接口现有实现的情况下添加新功能。 // 需实现该接口,才可使用示例:
1 public interface Printable { 2 void print(); 3 4 default void defaultPrint() { 5 System.out.println("Using default print method."); 6 } 7 }
-
静态方法(Static Methods):
Java 8允许在接口中定义静态方法。这些方法也是有具体实现的,并且通过static
关键字标识。静态方法不依赖于接口实例,可以直接通过接口名调用。示例:
public interface Utility { static int add(int a, int b) { return a + b; } } 使用静态方法: ```java int result = Utility.add(5, 3); //无需实现接口,即可使用
-
函数式接口(Functional Interfaces):
虽然函数式接口的概念不是Java 8中新增的特性,但Java 8对其进行了强调和扩展。函数式接口是只有一个抽象方法的接口,可以用来表示Lambda表达式。Java 8引入了@FunctionalInterface
注解来标记一个接口为函数式接口,虽然这个注解不是必须的,但它可以帮助编译器检查接口是否符合函数式接口的定义。示例:
1 @FunctionalInterface 2 public interface MyFunction { 3 int apply(int value); 4 } 5 6 7 Lambda表达式使用: 8 ....java 9 MyFunction increment = (value) -> value + 1; 10 int result = increment.apply(5); // result为6
总结来说,Java 8通过引入默认方法、静态方法以及对函数式接口的强调,极大地增强了接口的功能性和灵活性,使得接口设计更加贴近实际需求,同时也为支持函数式编程风格铺平了道路。
1.3.4 例子
1 package java8.lam1; 2 3 import java.util.Arrays; 4 import java.util.List; 5 6 public class Test { 7 public static void main(String[] args) { 8 TestStream<String> testStream = new TestStream<String>(); 9 List list = Arrays.asList("11", "22", "33"); 10 testStream.setList(list); 11 12 testStream.myForEach(new ConsumerInterface<String>() { 13 @Override 14 public void accept(String s) { 15 System.out.println("匿名类方式print:" + s); 16 } 17 }); 18 19 testStream.myForEach((str) -> {System.out.println("Lambda表达式方式print:" + str);}); 20 } 21 } 22 23 @FunctionalInterface 24 interface ConsumerInterface<T> { 25 void accept(T t); 26 } 27 28 class TestStream<T> { 29 30 private List<T> list; 31 32 public void setList(List<T> list) { 33 this.list = list; 34 } 35 36 public void myForEach(ConsumerInterface<T> consumer) { 37 for (T t: 38 list) { 39 consumer.accept(t); 40 } 41 } 42 }
1 package java8; 2 3 public class EmployeeTest { 4 public static void main(String[] args) { 5 6 Employee employee1 = new EmployeeImpl(); 7 employee1.getSalary(); 8 System.out.println("****************"); 9 10 11 Employee employee2 = new Employee() { 12 @Override 13 public void getSalary() { 14 System.out.println("Employee匿名内部类"); 15 } 16 }; 17 employee2.getSalary(); 18 System.out.println("****************"); 19 20 /** 21 * lambda表达式可以看作是一个接口的实例,使用lambda是有前提条件的,就是该接口必须是函数式接口。 22 * 那么什么是函数式接口呢?用一句话总结就是有且仅有一个抽象方法的接口(在java提供的类库中,函数式接口上面都有@FunctionalInterface注解) 23 * 24 * lambda表达式可以总结为:一个(), 一个 ->, 一段代码{} 25 * 一个():参数 26 * 一段代码{}:具体业务逻辑 27 */ 28 Employee employee3 = () -> { 29 System.out.println("将上述匿名内部类转为使用lambda表达式"); 30 }; 31 employee3.getSalary(); 32 } 33 34 interface Employee { 35 public void getSalary(); 36 } 37 38 static class EmployeeImpl implements Employee { 39 40 @Override 41 public void getSalary() { 42 System.out.print("Employee的普通实现类"); 43 } 44 } 45 }
2. Stream流
2.1 函数型接口Function定义
1 package java8; 2 3 import java.util.function.Function; 4 5 /** 6 * 1. 函数型接口Function<T,R>:有输入,有输出 7 * 核心抽象方法为 抽象方法 R apply(T t); 8 */ 9 public class FunctionInterface1 { 10 11 public static void main(String[] args) { 12 // 通过lambda实例化一个接口,实现抽象方法R apply(T t); 13 Function<String, Integer> function = (s) -> { 14 if (s == null) { 15 return 0; 16 } 17 return s.length(); 18 }; 19 Integer result = testFunction("hello", function); 20 System.out.println(result); 21 22 System.out.println("****************"); 23 System.out.println(testFunction("hello", s -> { 24 return "更简化的写法".length(); 25 })); 26 } 27 28 29 // interface Function<T, R> 抽象方法 R apply(T t); 30 private static Integer testFunction(String str, Function<String, Integer> function) { 31 // str作为第13行的参数s进行传递 32 return function.apply(str); 33 } 34 }
结果
5
****************
6
2.1 函数型接口Function类型
- 函数型接口Function<T,R>
-
抽象方法 R apply(T t)
-
- 消费型接口Consumer<T>
- 抽象方法:void accept(T t)
- 供给型接口Supplier<T>
- 抽象方法:T get()
- 断言型接口Predicate<T>
- 抽象方法:boolean test(T t)
1 package java8; 2 3 import java.util.function.Function; 4 5 /** 6 * 1. 函数型接口Function<T,R>:有输入,有输出 7 * 核心抽象方法为 抽象方法 R apply(T t); 8 */ 9 public class FunctionInterface1 { 10 11 public static void main(String[] args) { 12 // 通过lambda实例化一个接口,实现抽象方法R apply(T t); 13 Function<String, Integer> function = (s) -> { 14 if (s == null) { 15 return 0; 16 } 17 return s.length(); 18 }; 19 Integer result = testFunction("hello", function); 20 System.out.println(result); 21 22 System.out.println("****************"); 23 System.out.println(testFunction("hello", s -> { 24 return "更简化的写法".length(); 25 })); 26 } 27 28 29 // interface Function<T, R> 抽象方法 R apply(T t); 30 private static Integer testFunction(String str, Function<String, Integer> function) { 31 // str作为第13行的参数s进行传递 32 return function.apply(str); 33 } 34 }
1 package java8; 2 3 import java.util.function.Consumer; 4 5 /** 6 * Consumer<T> 消费性接口有输入,但是没有返回值 7 * 抽象方法:void accept(T t); 8 */ 9 public class ConsuerFunctionInterface { 10 11 public static void main(String[] args) { 12 // s是实例化方法accept的参数,{}内部是accept的方法体 13 testConsumer(s -> {System.out.println(s);}, "消费性接口抽象方法accept实现"); 14 } 15 16 private static void testConsumer(Consumer<String> consumer, String str) { 17 consumer.accept(str); 18 } 19 }
1 package java8; 2 3 4 import java.util.function.Supplier; 5 6 /** 7 * Supplier<T> 供给型接口:无输入、有输出 8 * 抽象方法:T get(); 9 */ 10 public class SupplierFunctionInterface { 11 12 public static void main(String[] args) { 13 14 // ()是实例化方法T get()参数:即无参数,{}是get的方法体 15 testSupplier(()-> { 16 String name = "Supplier<Person>的抽象方法get实现"; 17 System.out.println(name); 18 return new Person(name); 19 }); 20 } 21 22 private static Person testSupplier(Supplier<Person> supplier) { 23 return supplier.get(); 24 } 25 26 } 27 28 class Person { 29 private static String name; 30 31 Person(String name) { 32 this.name = name; 33 } 34 }
1 package java8; 2 3 import java.util.function.Predicate; 4 5 /** 6 * 断言型接口Predicate<T> 7 * 抽象方法:boolean test(T t); 8 */ 9 public class PredicateFunctionInterface { 10 11 public static void main(String[] args) { 12 // b是test方法的参数, {}内部是test方法体 13 boolean res = testPredicate((b) -> { 14 System.out.println("断言型接口Predicate抽象方法boolean test(T t)的实例化"); 15 return b > 99; 16 }, 99); 17 18 System.out.println("b>99, res=" + res); 19 } 20 21 22 private static boolean testPredicate(Predicate<Integer> predicate, int a) { 23 return predicate.test(a); 24 } 25 }
2.3 reduce
https://www.cnblogs.com/flydean/p/java-8-stream-reduce.html
https://www.liaoxuefeng.com/wiki/1252599548343744/1322402971648033
https://blog.csdn.net/qq_33351091/article/details/82494855
1 package mtee3rule; 2 3 import java.util.Arrays; 4 import java.util.List; 5 import java.util.Optional; 6 7 public class ReduceTest { 8 9 public static void main(String[] args) { 10 List<Integer> intList = Arrays.asList(1,2,3,4); 11 12 Optional<Integer> result1 = intList.stream().reduce(Integer::sum); 13 System.out.println("result1=" + result1 + ", sum=" + result1.get()); 14 15 Integer result2=intList.stream().reduce(100, Integer::sum); 16 System.out.println("result2=" + result2 + ", sum=" + result2); 17 18 // 每个线程的初始累加值都是100,最后4个线程加出来的结果就是406 19 // 这里sum方法的identity只能是0。所以这里我们传入100是不对的,因为sum(100+1)!= 1。 20 // 如果我们用0作为identity,则stream和parallelStream计算出的结果是一样的。这就是identity的真正意图。 21 Integer result3=intList.parallelStream().reduce(100, Integer::sum); 22 System.out.println("result3=" + result2 + ", sum=" + result3); 23 24 testMultiReduce1(); 25 } 26 27 private static void testMultiReduce1() { 28 List<String> strings = Arrays.asList("1", "2", "4", "5"); 29 30 System.out.println("********in testMultiReduce1()************"); 31 32 // 非并行流 33 Integer reduce1 = strings.stream().reduce(0, 34 (acc, e) -> acc + Integer.valueOf(e), (u, t) -> { 35 // 非并行流,不会执行第三个参数 36 System.out.println("u----:" + u); 37 // 这里的返回值并没有影响返回结果 38 return null; 39 }); 40 System.out.println("reduce1:" + reduce1); 41 42 // 并行流 43 //System.out.println("out_thread=" + Thread.currentThread().getName()); 44 Integer reduce2 = strings.parallelStream().reduce(0, 45 (acc, e) -> acc + Integer.valueOf(e), (u, t) -> { 46 // u,t分别为并行流每个子任务的结果 47 System.out.println("inner_thread=" + Thread.currentThread().getName() + ", u=" + u + ", t=" + t); 48 49 return u + t; 50 }); 51 System.out.println("out_thread=" + Thread.currentThread().getName()); 52 System.out.println("reduce2:" + reduce2); 53 } 54 55 }
结果:
1 result1=Optional[10], sum=10 2 result2=110, sum=110 3 result3=110, sum=410 4 ********in testMultiReduce1()************ 5 reduce1:12 6 inner_thread=ForkJoinPool.commonPool-worker-2, u=1, t=2 // 第一个线程执行 7 inner_thread=main, u=4, t=5 // 第二个线程执行 8 inner_thread=main, u=3, t=9 // u为第6行执行结果3,t为第7行执行结果9 9 out_thread=main 10 reduce2:12 // 第8行执行结果 = 3 + 9
2.4 创建Stream流的方式
2.4.1 ::(双冒号)使用
https://www.jianshu.com/p/96b4815d629e

1 package java8; 2 3 import java.util.Arrays; 4 import java.util.Comparator; 5 import java.util.List; 6 import java.util.Optional; 7 8 public class DoubleMaoHao { 9 10 public static void main(String[] args) { 11 List<String> listStr = Arrays.asList("adnm", "p", "admmt", "pot", "xbangd", "weoujgsd"); 12 13 // 最大的串:实例化Comparator的抽象方法int compare(T o1, T o2) 14 // (o1, o2)有两个参数,o1作为当前对象,o2作为参数,都是String;返回值为int —— 其它方法满足这样条件的也可以 15 Optional<String> optionalMax1 = listStr.stream().max(((o1, o2) -> { return o1.compareTo(o2);} )); 16 17 // 简写 18 // 而String::compareTo方法是指int compareTo(String anotherString)--即参数为string,返回值为int的 19 // 其它方法 public int indexOf(String str) --即参数为string,返回值为int的 也可以,但是int length() 无参数的方法就不行 20 Optional<String> optionalMax2 = listStr.stream().max(String::compareTo); // String::length报错 21 22 System.out.println("optionalMax1=" + optionalMax1.get() + ", optionalMax2=" + optionalMax2.get()); // optionalMax3=adnm, optionalMax4=w3 23 24 25 List<String> listStr3 = Arrays.asList("adnm", "dn", "n"); 26 List<String> listStr4 = Arrays.asList("adnm", "w3", "3"); 27 Optional<String> optionalMax3 = listStr3.stream().max(String::indexOf); // adnm.indexOf("dn")>0, "dn".indexOf("n")>0 28 Optional<String> optionalMax4 = listStr4.stream().max(String::indexOf); // adnm.indexOf("w3")<0, "w3".indexOf("3")>0 29 System.out.println("optionalMax3=" + optionalMax3.get() + ", optionalMax4=" + optionalMax4.get()); // optionalMax3=adnm, optionalMax4=w3 30 31 Optional<String> optionalMax5 = listStr.stream().max((o1,o2) -> {return 1;}); // >=0:adnm 即两两比较 前面最大; <0,则最后一个最大 32 System.out.println("optionalMax5=" + optionalMax5.get()); // max=adnm (第0个) 33 34 Optional<String> optionalMax6 = listStr.stream().max((o1,o2) -> {return -1;}); // >=0:adnm 即两两比较 前面最大; <0,则最后一个最大 35 System.out.println("optionalMax6=" + optionalMax6.get()); // max=weoujgsd (最后一个) 36 37 38 /** 39 * max()方法中需要一个函数式接口Comparator<T> 40 * comparing方法中需要一个函数型接口Function<T, R>,唯一的抽象方法为R apply(T t); 41 * x作为Function方法的参数,到了Function方法体里 x作为对象来调用 无参数方法(这里x是String类型,则可以调用String的所有无参方法且返回值为int) 42 */ 43 Optional<String> max1 = listStr.stream().max(Comparator.comparing((x) -> { 44 return x.length(); 45 })); 46 Optional<String> max2 = listStr.stream().max(Comparator.comparing((x) -> { 47 return 1; 48 })); 49 Optional<String> max3 = listStr.stream().max(Comparator.comparing((x) -> { 50 return -1; 51 })); 52 // 要看comparing内部的实现Function keyExtractor (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2)); 53 System.out.println("max1=" + max1.get() + ", max2=" + max2.get() + ", max3=" + max3.get()); // max1=weoujgsd, max2=adnm, max3=adnm 54 55 // 简写形式——使用了静态方法Comparator.comparing,比较的是length属性, 56 Optional<String> max1_1 = listStr.stream().max(Comparator.comparing(String::length)); // String::compareTo报错 57 System.out.println("简写_最长的字符串 max1_1:" + max1_1.get()); 58 } 59 }
结果:
optionalMax1=xbangd, optionalMax2=xbangd optionalMax3=adnm, optionalMax4=w3 optionalMax5=adnm optionalMax6=weoujgsd max1=weoujgsd, max2=adnm, max3=adnm 简写_最长的字符串 max1_1:weoujgsd
参考文献:
1.https://zhuanlan.zhihu.com/p/340538961
2.https://blog.csdn.net/yczz/article/details/50896975
3.https://blog.csdn.net/love905661433/article/details/86422169
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
2020-11-16 JVM-JAVA基本类型
2020-11-16 JVM指令分析