freyhe

导航

14.JDK8新特性 lambda&Stream&Optional

一、Lambda表达式

1.函数式接口(SAM接口)

1.只包含一个抽象方法的接口,称为函数式接口。(SAM接口 Single Abstract Interface)

​ 函数式接口在以下几种情况下,接口不会把其当作是抽象方法,从而符合函数式接口的定义。

  • 接口中所定义的方法式默认方法,使用default修饰,有其默认实现。(默认方法不可能是抽象方法)

  • 方法是静态方法,因为静态方法不能是抽象方法,而是一个已经实现了的方法。

  • 方法是继承来自 Object 类的public方法,因为任何一个接口或类都继承了 Object 的方法

@FunctionalInterface//Comparator源码
public interface Comparator<T> {
    int compare(T o1, T o2);
    boolean equals(Object obj);//Object类中实现了,不能算是接口的抽象方法
    ...
}

2.可以在接口上使用 @FunctionalInterface 注解,检查它是否是一个函数式接口

3.Lambda表达式就是一个函数式接口的实例

一个(匿名或非匿名)对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示

2. Lambda 表达式:语法

2.1 语法介绍

 (形参)->{lambda体}; 
	  	 -> :lambda操作符 或 箭头操作符
     	 ->左边:lambda形参列表 (其实就是接口中的抽象方法的形参列表)
     	 ->右边:lambda体 (其实就是重写的抽象方法的方法体)

2.2 简写语法

  1. 形参只有一个,小括号可以省略(多形参时不可省略括号)

  2. 类型推导,因此参数类型可以省略

    举例: Comparator comparator = (o1, o2) ->Integer.compare(o1,o2) ;

    解释:interface Comparator 中的唯一抽象方法为 int compare(T o1, T o2);

    ​ 编译器根据接口的泛型 可以推断出o1,o2的数据类型即

  3. 方法体只有一句话(方法体有多个语句要执行,则大括号不可省略)

  • 大括号可以省略

  • 有return,则return也省略不写

2.3 示例

	@Test
    public void test02(){
        Comparator<Integer> comparator = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1,o2);
            }
        };
        int i = comparator.compare(1, 2);
        System.out.println(i);//-1
    }
	@Test
    public void test02_1(){
//        Comparator<Integer> comparator = Integer::compare; //方法引用
        Comparator<Integer> comparator = (o1, o2) ->Integer.compare(o1,o2) ;
        int i = comparator.compare(1, 2);
        System.out.println(i);//-1
    }

3.四大类函数式接口

系统给我们提供了四大类函数式接口:

1、消费型接口

Consumert也是IDK8提供的函数式接口,用于进行获取数据的操作,

其内部定义了一个抽象方法accept、一个默认方法andThen。

消费型接口的抽象方法特点:有形参,但是返回值类型是void

接口名 抽象方法 描述
Consumer void accept(T t) 接收一个对象用于完成功能
BiConsumer<T,U> void accept(T t, U u) 接收两个对象用于完成功能
DoubleConsumer void accept(double value) 接收一个double值
IntConsumer void accept(int value) 接收一个int值
LongConsumer void accept(long value) 接收一个long值
ObjDoubleConsumer void accept(T t, double value) 接收一个对象和一个double值
ObjIntConsumer void accept(T t, int value) 接收一个对象和一个int值
ObjLongConsumer void accept(T t, long value) 接收一个对象和一个long值

代码示例:

消费型 : Consumer 有参数无返回值

  • 对类型为T的对象应用操作,包含方法:void accept(T t)

    //消费型函数式接口的lambda使用方式
    @Test
    public void testConsumer() {
        //形参只有一个的时候,小括号可以省略
        //方法主体如果只有一句话,那么大括号可以省略
        Consumer<String> consumer = s -> System.out.println(s.toUpperCase());
        consumer.accept("hello world!");//hello world!
    }
    

2、供给型接口

Suppliert也是用来进行值获取操作,内部只有一个抽象方法get

这类接口的抽象方法特点:无参,但是无返回值

接口名 抽象方法 描述
Supplier T get() 返回一个对象
BooleanSupplier boolean getAsBoolean() 返回一个boolean值
DoubleSupplier double getAsDouble() 返回一个double值
IntSupplier int getAsInt() 返回一个int值
LongSupplier long getAsLong() 返回一个long值

代码示例:

供给型:Supplier 无参数有返回值

  • 返回类型为T的对象,包含方法:T get()

    @Test
    public void testSupplier() {  
        //方法主体只有一句时,大括号可以省略,另外如果有return,那么return也必须要省略
        Supplier<Date> supplier = () -> new Date();
        System.out.println(supplier.get());//Sat Jul 10 00:34:53 CST 2021
    }
    

3、判断型接口

Predicate:接口是ava8定义的一个函数式接口,属于java.util.function包下,用于进行判断操作

内部定义一个抽象方法test、三个默认方法and,negate,or、一个静态方法isEqual

这个接口的抽象方法特点:有参,但是返回值类型是boolean结果。

接口名 抽象方法 描述
Predicate boolean test(T t) 接收一个对象
BiPredicate<T,U> boolean test(T t, U u) 接收两个对象
DoublePredicate boolean test(double value) 接收一个double值
IntPredicate boolean test(int value) 接收一个int值
LongPredicate boolean test(long value) 接收一个long值

代码示例:

判断型:Predicate 返回boolean类型

  • 确定类型为T的对象是否满足某约束,并返回boolean 值。包含方法:boolean test(T t)

    @Test
    public void test03_2() {
        //Predicate<Integer> predicate = (Integer integer)->{return integer%2==0;};
        Predicate<Integer> predicate = integer -> integer % 2 == 0;
        System.out.println(predicate.test(100));//true
    }
    

4、功能型接口

Function:主要用于进行类型转换的操作

内部提供一个抽象方法apply、两个默认方法compose,andThen、一个静态方法identity

这类接口的抽象方法特点:既有参数又有返回值

接口名 抽象方法 描述
Function<T,R> R apply(T t) 接收一个T类型对象,返回一个R类型对象结果
UnaryOperator T apply(T t) 接收一个T类型对象,返回一个T类型对象结果
DoubleFunction R apply(double value) 接收一个double值,返回一个R类型对象
IntFunction R apply(int value) 接收一个int值,返回一个R类型对象
LongFunction R apply(long value) 接收一个long值,返回一个R类型对象
ToDoubleFunction double applyAsDouble(T value) 接收一个T类型对象,返回一个double
ToIntFunction int applyAsInt(T value) 接收一个T类型对象,返回一个int
ToLongFunction long applyAsLong(T value) 接收一个T类型对象,返回一个long
DoubleToIntFunction int applyAsInt(double value) 接收一个double值,返回一个int结果
DoubleToLongFunction long applyAsLong(double value) 接收一个double值,返回一个long结果
IntToDoubleFunction double applyAsDouble(int value) 接收一个int值,返回一个double结果
IntToLongFunction long applyAsLong(int value) 接收一个int值,返回一个long结果
LongToDoubleFunction double applyAsDouble(long value) 接收一个long值,返回一个double结果
LongToIntFunction int applyAsInt(long value) 接收一个long值,返回一个int结果
DoubleUnaryOperator double applyAsDouble(double operand) 接收一个double值,返回一个double
IntUnaryOperator int applyAsInt(int operand) 接收一个int值,返回一个int结果
LongUnaryOperator long applyAsLong(long operand) 接收一个long值,返回一个long结果
BiFunction<T,U,R> R apply(T t, U u) 接收一个T类型和一个U类型对象,返回一个R类型对象结果
BinaryOperator T apply(T t, T u) 接收两个T类型对象,返回一个T类型对象结果
ToDoubleBiFunction<T,U> double applyAsDouble(T t, U u) 接收一个T类型和一个U类型对象,返回一个double
ToIntBiFunction<T,U> int applyAsInt(T t, U u) 接收一个T类型和一个U类型对象,返回一个int
ToLongBiFunction<T,U> long applyAsLong(T t, U u) 接收一个T类型和一个U类型对象,返回一个long
DoubleBinaryOperator double applyAsDouble(double left, double right) 接收两个double值,返回一个double结果
IntBinaryOperator int applyAsInt(int left, int right) 接收两个int值,返回一个int结果
LongBinaryOperator long applyAsLong(long left, long right) 接收两个long值,返回一个long结果

代码示例:

功能型:Funcation<T,R> 接受T类型参数,返回R类型值

  • 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t)

    @Test
    public void testFuncation() {
        Function<String, Integer> function = str -> Integer.parseInt(str);
        System.out.println(function.apply("123").getClass());//class java.lang.Integer
    }
    

4.自定义函数式接口

只要确保接口中有且仅有一个抽象方法即可:

修饰符 interface 接口名称 {
    public abstract 返回值类型 方法名称(可选参数信息);
    // 其他非抽象方法内容
}

接口当中抽象方法的 public abstract 是可以省略的

可以在接口上加 注解 @FunctionalInterfac 来验证并说明此接口为函数式接口

例如:声明一个计算器Calculator接口,内含抽象方法calc可以对两个int数字进行计算,并返回结果:

public interface Calculator {
    int calc(int a, int b);
}

在测试类中,声明一个如下方法:

    public static void invokeCalc(int a, int b, Calculator calculator) {
        int result = calculator.calc(a, b);
        System.out.println("结果是:" + result);
    }

下面进行测试:

	public static void main(String[] args) {
		invokeCalc(1, 2, (int a,int b)-> {return a+b;});
		invokeCalc(1, 2, (int a,int b)-> {return a-b;});
		invokeCalc(1, 2, (int a,int b)-> {return a*b;});
		invokeCalc(1, 2, (int a,int b)-> {return a/b;});
		invokeCalc(1, 2, (int a,int b)-> {return a%b;});
		invokeCalc(1, 2, (int a,int b)-> {return a>b?a:b;});
	}

5.方法引用与构造器引用

Lambda表达式是可以简化函数式接口的变量与形参赋值的语法。而方法引用和构造器引用是为了简化Lambda表达式的。

当Lambda表达式满足一些特殊的情况时,还可以再简化:

(1)Lambda体只有一句语句,并且是通过调用一个对象的/类现有的方法来完成的

​ 例如:System.out对象,调用println()方法来完成Lambda体

​ Math类,调用random()静态方法来完成Lambda体

(2)并且Lambda表达式的形参正好是给该方法的实参

​ 例如:t->System.out.println(t)

​ () -> Math.random() 都是无参

4.1 方法引用

使用方法引用的要求: (可以将方法引用理解为 Lambda 表达式的另外一种表现形式)

方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致

若Lambda 的参数列表的第一个参数,是实例方法的调用者,

第二个参数(或无参)是实例方法的参数时,

格式:ClassName::MethodName

本质:是否提供了方法的调用者,参数列表中第1个参数为方法调用者或方法原本就是静态方法,则无需外部调用者驱动此方法,可写成 ClassName::MethodName 的样式

格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。

说明:

  • :: 称为方法引用操作符(两个:中间不能有空格,而且必须英文状态下半角输入)
  • Lambda表达式的形参列表,全部在Lambda体中使用上了,要么是作为调用方法的对象,要么是作为方法的实参。
  • 在整个Lambda体中没有额外的数据。

三种主要使用情况

1. 对象的引用 :: 实例方法名
2. 类名 :: 静态方法名
3. 类名 :: 实例方法名

代码示例:

​ 对象 :: 实例方法名

@Test
public void test02(){
    Consumer<String> consumer = System.out::println;
    consumer.accept("hello world!");
}

​ 类 :: 静态方法名

@Test
public void test04(){
    /*
      Comparator<Integer> comparator = (num1,num2)-> Integer.compare(num1, num2);
      Comparator<Integer> comparator = Integer::compare;
      Integer[] arr = {21,19,25,73,81,16};
      Arrays.sort(arr,comparator);
      System.out.println(Arrays.toString(arr));
    */
    Integer[] arr = {21,19,25,73,81,16};
    Arrays.sort(arr,Integer::compare);
    System.out.println(Arrays.toString(arr));
}

​ 类 :: 实例方法名

@Test
public void test06(){
    //BiPredicate接口可以接收两个对象,并判断是否满足某约束,返回boolean值
    BiPredicate<String,String> predicate = String::equals;
    System.out.println(predicate.test("hello","world"));
}

4.2 构造器引用

构造器引用的要求:

要求构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象。

(1)当Lambda表达式是创建一个对象,并且满足Lambda表达式形参,正好是给创建这个对象的构造器的实参列表。

(2)当Lambda表达式是创建一个数组对象,并且满足Lambda表达式形参,正好是给创建这个数组对象的长度

构造器引用的语法格式

  • 类名::new
  • 数组类型名::new

示例代码:

@Test
public void test07(){
    //你给我一个整数,我返回给你一个Person对象
    //Function<Integer,Person> function = (Integer age)->{return new Person(age);};
    //Function<Integer,Person> function = age->new Person(age);
    Function<Integer,Person> function = Person::new; // age->new Person(age);
    System.out.println(function.apply(19));
}

class Person{
    Integer age ;
    public Person(Integer age){
        this.age = age ;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                '}';
    }
}

@Test
public void test08(){
    Function<Integer,String[]> function = String[]::new; // length -> new String[length]; 
    System.out.println(function.apply(19));
}
public class TestMethodReference {
    @Test
	public void teset04() {
		Stream<Integer> stream = Stream.of(1,2,3);
		Stream<int[]> map = stream.map(int[]::new);
	}
    
	
	//这个方法是模仿HashMap中,把你指定的数组的长度纠正为2的n次方的代码
	//createArray()的作用是,创建一个长度为2的n次方的数组
	public <R> R[] createArray(Function<Integer,R[]> fun,int length){
		int n = length - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        length = n < 0 ? 1 : n + 1;
		return fun.apply(length);
	}
	
	@Test
	public void test3(){
		/*
		 * Function是一个函数式接口,可以用Lambda表达式赋值
		 * Function<T,R>的抽象方法   R apply(T t)
		 * 
		 * createArray这个方法中用的是Function<Integer,R[]> fun。说明T类型已经指定为Integer
		 * 说明
		 */
//		Function<Integer,String[]> f = (Integer len) -> new String[len];
		
		//因为Lambda体是在创建一个数组对象完成的,而且Lambda表达式的形参正好是创建数组用的长度
		//通过构造器引用省略
		Function<Integer,String[]> f = String[]::new;
		String[] array = createArray(f, 10);
		
		System.out.println(array.length);//16
	}
       
    
    @Test
	public void teset02() {
		Stream<String> stream = Stream.of("1.0","2.3","4.4");
		
//		Stream<BigDecimal> stream2 = stream.map(num -> new BigDecimal(num));
		
		Stream<BigDecimal> stream2 = stream.map(BigDecimal::new);
	}
	
	@Test
	public void test1(){
//		Supplier<String> s = () -> new String();//通过供给型接口,提供一个空字符串对象
		
		//构造器引用
		Supplier<String> s = String::new;//通过供给型接口,提供一个空字符串对象
	}

}

5.lambda表达式的底层原理

public class SourceDemo {
    public static void demo() {
        String[] language = {"c", "c++",
                "c#",
                "java", "pythgn",
                "go", "hive",
                "php"};
        List<String> list = Arrays.asList(language);
        System.setProperty("jdk.internal.lambda.dumpProxyClasses", "D://");
        list.forEach(s -> System.out.println(s));//本质上是生产了Consumer的匿名内部类并调用accept方法来接收参数
    }

    public static void main(String[] args) {
        SourceDemo.demo();
    }
}

对以上代码的class文件进行 javap -p (显示所有类和成员)

public class com.atguigu.design.structural.proxy.dynamic.SourceDemo {
  public com.atguigu.design.structural.proxy.dynamic.SourceDemo();
  public static void demo();
  public static void main(java.lang.String[]);
  private static void lambda$demo$0(java.lang.String);//lambda表达式生成了此私有静态方法
}

如果想查看内部类里面的内容,可以在lambda表达式执行之前,添加

System.setProperty("jdk.internal.lambda.dumpProxyClasses", "D://");

这个方法会将运行时生成的内部类class文件进行输出。

当该文件生成后,可以通过javap -c -p class文件名查看文件中的内容

  private com.frey.proxy.dynamic.SourceDemo$$Lambda$1();
    Code:
       0: aload_0
       1: invokespecial #10       // Method java/lang/Object."<init>":()V
       4: return

  public void accept(java.lang.Object);
    Code:
       0: aload_1
       1: checkcast     #15      // class java/lang/String
       4: invokestatic  #21      // Method com/frey/proxy/dynamic/SourceDemo.lambda$demo$0:(Ljava/lang/String;)V
       7: return
}

底层调用了如下方法

package java.lang.invoke;
import java.io.Serializable;
import java.util.Arrays;
public class LambdaMetafactory {
    
    public static final int FLAG_SERIALIZABLE = 1 << 0;

    public static final int FLAG_MARKERS = 1 << 1;

    public static final int FLAG_BRIDGES = 1 << 2;

    private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
    private static final MethodType[] EMPTY_MT_ARRAY = new MethodType[0];

    // 每个lambda表达式都会进入此方法来创建一个内部类
    public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType)
            throws LambdaConversionException {
        AbstractValidatingLambdaMetafactory mf;
        // lambda内部类工厂
        mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                             invokedName, samMethodType,
                                             implMethod, instantiatedMethodType,
                                             false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
        mf.validateMetafactoryArgs();
        return mf.buildCallSite();
    }

    public static CallSite altMetafactory(MethodHandles.Lookup caller,
                                          String invokedName,
                                          MethodType invokedType,
                                          Object... args)
            throws LambdaConversionException {
        MethodType samMethodType = (MethodType)args[0];
        MethodHandle implMethod = (MethodHandle)args[1];
        MethodType instantiatedMethodType = (MethodType)args[2];
        int flags = (Integer) args[3];
        Class<?>[] markerInterfaces;
        MethodType[] bridges;
        int argIndex = 4;
        if ((flags & FLAG_MARKERS) != 0) {
            int markerCount = (Integer) args[argIndex++];
            markerInterfaces = new Class<?>[markerCount];
            System.arraycopy(args, argIndex, markerInterfaces, 0, markerCount);
            argIndex += markerCount;
        }
        else
            markerInterfaces = EMPTY_CLASS_ARRAY;
        if ((flags & FLAG_BRIDGES) != 0) {
            int bridgeCount = (Integer) args[argIndex++];
            bridges = new MethodType[bridgeCount];
            System.arraycopy(args, argIndex, bridges, 0, bridgeCount);
            argIndex += bridgeCount;
        }
        else
            bridges = EMPTY_MT_ARRAY;

        boolean isSerializable = ((flags & FLAG_SERIALIZABLE) != 0);
        if (isSerializable) {
            boolean foundSerializableSupertype = Serializable.class.isAssignableFrom(invokedType.returnType());
            for (Class<?> c : markerInterfaces)
                foundSerializableSupertype |= Serializable.class.isAssignableFrom(c);
            if (!foundSerializableSupertype) {
                markerInterfaces = Arrays.copyOf(markerInterfaces, markerInterfaces.length + 1);
                markerInterfaces[markerInterfaces.length-1] = Serializable.class;
            }
        }

        AbstractValidatingLambdaMetafactory mf
                = new InnerClassLambdaMetafactory(caller, invokedType,
                                                  invokedName, samMethodType,
                                                  implMethod,
                                                  instantiatedMethodType,
                                                  isSerializable,
                                                  markerInterfaces, bridges);
        mf.validateMetafactoryArgs();
        return mf.buildCallSite();
    }
}

二、Stream

1.流的实现思想

Stream流是以更高层次的抽象思想,完成了对于集合的复杂操作。

那么它本身就是将集合的操作由外部迭代转换为了内部迭代

1.概述

​ Stream关注的是对数据的运算,与CPU打交道

​ “集合讲的是数据,负责存储数据,Stream流讲的是计算,负责处理数据!”

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。

Stream 不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或l/O channel等

Stream中的操作每一次都会产生新的流,内部不会像普通集合操作一样立刻获取值,而是惰性取值

(终止操作时将流一次性全部处理,称为“惰性求值”。)

使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。

简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

2.外部迭代

在java8之前,在使用集合时,无非就是在集合上进行迭代,然后处理每一个元素。

以统计学生总数为例

public static void main(String[]args) {
    List<Student> studentLis = new ArrayList<>();
    studentList.add(new Student(1, "张三", "M", l9));
    studentList.add(new student(1, "李四", "M", l8));
    studentList.add(new student(1, "王五", "F", 2l));
    studentList.add(new student(1, "赵六", "F", 20));
    int count = 0;
    for (Student student : studentList) {
        count++;
    }
    System.out.println(count);
}

这种方式对于功能实现没有任何问题,但是它也存在一些问题:

​ 1.for循环是串行的,而且必须按照集合中元素的顺序进行依次处理,要想改造成并行的话,需要修改每个for循环

​ 2.使用是及早求值,返回的是另一个值或空。使用性能上存在一点点的瑕疵

​ 3.易读性不好,如果fo中嵌套大量循环与功能代码,阅读起来简直是灾难

image-20220507074041014

根据上图的说明,所有的集合迭代所及都是在我们自己编写的代码中,所以这种显式的方式称之为外部迭代。

其主要关注于数据本身,并且一般都是串行的。

3.内部迭代

而内部迭代来说,它所操作的就是不是一个集合了,而是一个流。

它会将所有的操作融合在流中,由其在内部进行处理,这种隐式的方式称之为内部迭代。

并且内部迭代支持并行处理,更利于集合操作的性能优化。其关注与对数据的计算。

image-20220507074518361

2.Stream的执行流程与注意点

1.Stream 执行流程

stream流会进行内部迭代,对每个元素进行相应的操作 例如:布尔判断然后过滤/映射返回新元素等

​ 1- 创建 Stream:通过一个数据源(如:集合、数组),获取一个流

​ 2- 中间操作:中间操作是个操作链,对数据源的数据进行n次处理,但是在终结操作前,并不会真正执行。(过滤、映射、...)

​ 3- 终止操作:一旦执行终止操作,就执行中间操作链,最终产生结果并结束Stream。

说明

1.一个中间操作链,对数据源的数据进行一次处理

2.一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用

1560769396655

2.注意点

​ ①Stream 自己不会存储元素。

​ ②Stream 不会改变源对象。每次处理都会返回一个持有结果的新Stream。

​ ③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行(等到终止操作时的时候才会执行中间操作链

3.Stream的操作

1.Stream实例化(4种方式)

tip:此外,基于文件,也可构建流

1、创建 Stream方式一:通过集合

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

  • public default Stream stream() : 返回一个顺序流

  • public default Stream parallelStream() : 返回一个并行流

2、创建 Stream方式二:通过数组

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

  • public static Stream stream(T[] array): 返回一个流

重载形式,能够处理对应基本类型的数组:

  • public static IntStream stream(int[] array):返回一个整型数据流
  • public static LongStream stream(long[] array):返回一个长整型数据流
  • public static DoubleStream stream(double[] array):返回一个浮点型数据流

3、创建 Stream方式三:通过Stream的of()

可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。

  • public static Stream of(T... values) : 返回一个顺序流

4、创建 Stream方式四:创建无限流

可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。

  • public static Stream iterate(final T seed, final UnaryOperator f):返回一个无限流
  • public static Stream generate(Supplier s) :返回一个无限流
public class StreamAPITest {

    //创建 Stream方式一:通过集合 
    //集合.stream()/ 集合.parallelStream()
    @Test
    public void test1(){
        List<Employee> employees = EmployeeData.getEmployees();

//        default Stream<E> stream() : 返回一个顺序流
        Stream<Employee> stream = employees.stream();

//        default Stream<E> parallelStream() : 返回一个并行流
        Stream<Employee> parallelStream = employees.parallelStream();

    }

    //创建 Stream方式二:通过数组 Arrays.stream(T[] array)
    @Test
    public void test2(){
        int[] arr = new int[]{1,2,3,4,5,6};
        //调用Arrays类的static <T> Stream<T> stream(T[] array): 返回一个流
        IntStream stream = Arrays.stream(arr);

        Employee e1 = new Employee(1001,"Tom");
        Employee e2 = new Employee(1002,"Jerry");
        Employee[] arr1 = new Employee[]{e1,e2};
        Stream<Employee> stream1 = Arrays.stream(arr1);

    }
    //创建 Stream方式三:通过Stream.of(T... values)
    @Test
    public void test3(){

        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);

    }

    //创建 Stream方式四:创建无限流  
    // Stream.iterate(final T seed, final UnaryOperator<T> f) /Stream.generate(Supplier<T> s)
    @Test
    public void test4(){

//      迭代 seed:种子  UnaryOperator:一元运算函数式接口
//      public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
        //遍历前10个偶数
        Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);


//      生成 Supplier:供给型函数式接口(无参有返回值)
//      public static<T> Stream<T> generate(Supplier<T> s)
        Stream.generate(Math::random).limit(10).forEach(System.out::println);

    }
}

2.Stream的中间操作

Stream中间操作的返回值还是Stream

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!

而在终止操作时一次性全部处理,称为“惰性求值”。

1-筛选与切片

​ filter(Predicate p)——接收 Lambda , 从流中排除某些元素

​ limit(long maxSize)——截断流,使其元素不超过给定数量

​ skip(long n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补

​ distinct()——筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素(底层使用了 LinkedHashSet

	/1-筛选与切片
    @Test
    public void test1(){
        List<Employee> list = EmployeeData.getEmployees();
//        filter(Predicate p)——接收 Lambda , 从流中排除某些元素。
        Stream<Employee> stream = list.stream();
        //练习:查询员工表中薪资大于7000的员工信息
        stream.filter(e -> e.getSalary() > 7000).forEach(System.out::println);

        System.out.println();
//        limit(n)——截断流,使其元素不超过给定数量。
        list.stream().limit(3).forEach(System.out::println);
        System.out.println();

//        skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
        list.stream().skip(3).forEach(System.out::println);

        System.out.println();
//        distinct()——筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素

        list.add(new Employee(1010,"刘强东",40,8000));
        list.add(new Employee(1010,"刘强东",41,8000));
        list.add(new Employee(1010,"刘强东",40,8000));
        list.add(new Employee(1010,"刘强东",40,8000));
        list.add(new Employee(1010,"刘强东",40,8000));

//        System.out.println(list);

        list.stream().distinct().forEach(System.out::println);
    }

2-映 射

tip:此方法有坑,映射后再用收集器转化时,如果转化后的容器能接收null,则 null 也会作为一个元素填入其中

eg:List<String> list = xxxList.stream().map(xx::getName).collect(Collectors.toList());

List可以接收null,那么 尽管 xxxList中500个元素,每个元素name属性为null,

最终 返回的 List<String> list 中会存入500个null元素,即使用一般的集合判空工具来判空,他也不为空(size 不为0,且 不等于 null)

解决方法:xxxList.stream().map(xx::getName).filter(Objects::nonNull).collect(Collectors.toList());

map(Function f) —— 接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素。

mapToDouble(ToDoubleFunction f) —— 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream

mapToInt(ToIntFunction f) —— 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream

mapToLong(ToLongFunction f) —— 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream

flatMap(Function f) —— 将流扁平化:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流(详见下面示例)

flatMap参考 list.add(Object obj) 和 list.addAll(Collection coll) 方法来理解()

flatMap能解决泛型嵌套问题,例如:单纯的使用map会每个元素生成Stream<T>,最终返回的是Stream<Stream<T>>,而使用flatMap能将内层的Stream<T>全部连接起来,最终返回Stream<T>

//映射
@Test
public void test2(){
    // map(Function f)——接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素。
    List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
    list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);

    //        练习1:获取员工姓名长度大于3的员工的姓名。
    List<Employee> employees = EmployeeData.getEmployees();
    Stream<String> namesStream = employees.stream().map(Employee::getName);
    namesStream.filter(name -> name.length() > 3).forEach(System.out::println);
    System.out.println();
    
    //flatMap(Function f)——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
    //StreamAPITest1为静态方法fromStringToStream所在类名 即当前类名
    Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest1::fromStringToStream);
    streamStream.forEach(s ->{
        s.forEach(System.out::println); //s的类型:Stream<Character> 所以还需要forEach才可以取出每个元素
    });
    System.out.println("===================================");
    Stream<Character> characterStream = list.stream().flatMap(StreamAPITest1::fromStringToStream);
    // 如果不用flatMap,而是用map,返回的就是Stream<Stream<Character>> ,flatMap能解决泛型嵌套的问题
    characterStream.forEach(System.out::println);//characterStream已经把每个流都练到一起所以可以直接操作

}

//将字符串中的多个字符构成的集合转换为对应的Stream的实例
public static Stream<Character> fromStringToStream(String str){//aa
    ArrayList<Character> list = new ArrayList<>();
    for(Character c : str.toCharArray()){
        list.add(c);
    }
    return list.stream();

}
image-20220508181232198
public static void main(String[] args) {
    Integer[] arr1 = {1, 2, 3};
    Integer[] arr2 = {4, 5, 6};
    List<Integer> collect = Stream.of(arr1, arr2)
        .flatMap(Arrays::stream)//即:flatMap(arr -> Arrays.stream(arr))
        .collect(Collectors.toList());
    System.out.println(collect);//[1, 2, 3, 4, 5, 6]
}

3-排序

​ sorted()——自然排序 (使用自然排序 实体类必须实现Comparable接口·)

​ sorted(Comparator com)——定制排序 (可以对没有实现Comparable接口的类的对象进行排序操作)

//3-排序
@Test
public void test4(){
//        sorted()——自然排序
    List<Integer> list = Arrays.asList(12, 43, 65, 34, 87, 0, -98, 7);
    list.stream().sorted().forEach(System.out::println);
    //抛异常,原因:Employee没有实现Comparable接口
//        List<Employee> employees = EmployeeData.getEmployees();
//        employees.stream().sorted().forEach(System.out::println);


//        sorted(Comparator com)——定制排序

    List<Employee> employees = EmployeeData.getEmployees();
    employees.stream().sorted( (e1,e2) -> {

       int ageValue = Integer.compare(e1.getAge(),e2.getAge());
       if(ageValue != 0){
           return ageValue;
       }else{
           return -Double.compare(e1.getSalary(),e2.getSalary());
       }

    }).forEach(System.out::println);
}

3.终止操作

方法 描述
boolean allMatch(Predicate p) 检查是否匹配所有元素
boolean anyMatch(Predicate p) 检查是否至少匹配一个元素
boolean noneMatch(Predicate p) 检查是否没有匹配所有元素
Optional findFirst() 返回第一个元素
Optional findAny() 返回当前流中的任意元素
long count() 返回流中元素总数
Optional max(Comparator c) 返回流中最大值
Optional min(Comparator c) 返回流中最小值
void forEach(Consumer c) 迭代
T reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 T
U reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 Optional
R collect(Collector c) 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void。

注意:流进行了终止操作后,不能再次使用

1-匹配与查找

​ allMatch(Predicate p)——检查是否匹配所有元素

​ anyMatch(Predicate p)——检查是否至少匹配一个元素

​ noneMatch(Predicate p)——检查是否没有匹配的元素

​ findFirst()——返回第一个元素

​ findAny()——返回当前流中的任意元素(对于串行流,默认返回第一个元素;对于并行流,才是真正的返回随机元素 --详见源码)

​ count()——返回流中元素的总个数

​ max(Comparator c) ——返回流中最大值

​ min(Comparator c)——返回流中最小值

​ forEach(Consumer c)——内部迭代

stream流会进行内部迭代,对每个元素进行相应的操作 例如:布尔判断然后过滤/映射返回新元素等

Collection 接口 api 实现的迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了


@Test
public void test1(){
    List<Employee> employees = EmployeeData.getEmployees();

//        allMatch(Predicate p)——检查是否匹配所有元素。
//          练习:是否所有的员工的年龄都大于18
    boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
    System.out.println(allMatch);

//        anyMatch(Predicate p)——检查是否至少匹配一个元素。
//         练习:是否存在员工的工资大于 10000
    boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000);
    System.out.println(anyMatch);

//        noneMatch(Predicate p)——检查是否没有匹配的元素。
//          练习:是否存在员工姓“雷”
    boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷"));
    System.out.println(noneMatch);
//        findFirst——返回第一个元素
    Optional<Employee> employee = employees.stream().findFirst();
    System.out.println(employee);
//        findAny——返回当前流中的任意元素 --对于串行流,默认返回第一个元素;对于并行流,才是真正的返回随机元素
    Optional<Employee> employee1 = employees.parallelStream().findAny();
    System.out.println(employee1);

}

@Test
public void test2(){
    List<Employee> employees = EmployeeData.getEmployees();
    // count——返回流中元素的总个数
    long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
    System.out.println(count);
//        max(Comparator c)——返回流中最大值
//        练习:返回最高的工资:
    Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary());
    Optional<Double> maxSalary = salaryStream.max(Double::compare);
    System.out.println(maxSalary);
//        min(Comparator c)——返回流中最小值
//        练习:返回最低工资的员工
    Optional<Employee> employee = employees.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
    System.out.println(employee);
    System.out.println();
//        forEach(Consumer c)——内部迭代
    employees.stream().forEach(System.out::println);

    //使用集合的遍历操作
    employees.forEach(System.out::println);
}




2-归约

​ T reduce(T identity, BinaryOperator accumulator)——可以将流中元素反复结合起来,得到一个值。返回 T (identity为给定的初始值)

​ Optional reduce(BinaryOperator accumulator) ——可以将流中元素反复结合起来,得到一个值。返回 Optional

reduce(BinaryOperator accumulator) 方法 因为没有给定初始值,为了避免空指针,所以返回Optional类型的对象

@Test
public void test3(){
//        reduce(T identity, BinaryOperator)——可以将流中元素反复结合起来,得到一个值。返回 T
//        练习1:计算1-10的自然数的和
    List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    Integer sum = list.stream().reduce(0, Integer::sum);
    System.out.println(sum);


//        reduce(BinaryOperator) ——可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
//        练习2:计算公司所有员工工资的总和
    List<Employee> employees = EmployeeData.getEmployees();
    Stream<Double> salaryStream = employees.stream().map(Employee::getSalary);
//        Optional<Double> sumMoney = salaryStream.reduce(Double::sum);
    Optional<Double> sumMoney = salaryStream.reduce((d1,d2) -> d1 + d2);
    System.out.println(sumMoney.get());

}

3-收集

collect(Collector c)——将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法

收集器 Collector

​ 一般使用工具类 Collectors 实用类提供的静态方法,方便地创建常见收集器实例

tip:可自定义收集器,详见:

https://www.cnblogs.com/webor2006/p/8342427.html

https://www.cnblogs.com/yw0219/p/9589124.html

Collectors 常用静态方法如下:

1.toList List 把流中元素收集到List

List<Employee> emps= list.stream().collect(Collectors.toList());

2.toSet Set 把流中元素收集到Set

Set<Employee> emps= list.stream().collect(Collectors.toSet());

3.toCollection Collection 把流中元素收集到创建的集合

Collection<Employee>emps = list.stream().collect(Collectors.toCollection(ArrayList::new));
//等同于:list.stream().collect(Collectors.toCollection(()->new ArrayList<>()));

4.counting Long 计算流中元素的个数

long count = list.stream().collect(Collectors.counting());

5.summingInt Integer 对流中元素的整数属性求和

int total = list.stream().collect(Collectors.summingInt(Employee::getSalary));

6.averagingInt Double 计算流中元素Integer属性的平均值

double avg = list.stream().collect(Collectors.averagingInt(Employee::getSalary));

7.summrizingInt IntSummaryStatistics 收集流中Integer属性的统计值。如:平均值、最大值、最小值、总和、总数、、、

IntSummaryStatistics iss = list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
//iss.getSum()	iss.getSum()	iss.getMax()	iss.getMin()	iss.getCount()	iss.getAverage()  ...

8.joining String 连接流中每个字符串

String str = list.stream().map(Employee::getName).collect(Collectors.joining());

9.maxBy Optional 根据比较器选择最大值

Optional<Emp> max = list.stream().collect(Collectors.maxBy(Comparator.comparingInt(Employee::getSalary)));

10minBy Optional 根据比较器选择最小值

Optional<Emp> min = liststream().collect(Collectors.minBy(Comparator.comparingInt(Employee::getSalary)));

11.reducing 归约产生的类型 从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值

int total = list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));

12.collectingAndThen 转换函数返回的类型 (包裹另一个收集器,用函数对其结果转换)

int how = list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
// 现将流转为list,再取list的size   
// 此方法可以用来实现:集合元素根据指定字段去重,详见5.1

13.groupingBy Map<K, List<T>> 根据某属性值对流分组,属性为K,结果为V

Map<Emp.Status, List<Emp>> map = list.stream().collect(Collectors.groupingBy(Employee::getStatus));
// 重载方法 groupingBy(Function func,Collector collector)
// 可以多层嵌套 key为外层Function对应元素的属性,内存还可以使用收集器,例如还是用groupingBy(Function func)来按数据分组,内存key为内层func对应元素的属性,value为元素对象

代码示例:

//根据年龄进行分组,获取并汇总人数
Map<Integer, Long> collect = studentList.stream().collect(Collectors.groupingBy(Student:getAge, Collectors.counting()));
//根据年龄与是否及格进行分组,并汇总人数
Map<Integer, Map<Boolean, Long>> collect = studentList.stream().collect(Collectors.groupingBy(Student:getAge, Collectors.groupingBy(Student:getIsPass, Collectors.counting())));

14.partitioningBy Map<Boolean, List<T>> 根据流中元素是否满足条件(true\false)进行分区

Map<Boolean, List<Emp>> map = list.stream()
            .collect(Collectors.partitioningBy(e->e.getSalary()>= 5000));

示例代码:


@Test
public void test4(){
//        练习1:查找工资大于6000的员工,结果返回为一个List或Set

    List<Employee> employees = EmployeeData.getEmployees();
    List<Employee> employeeList = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());

    employeeList.forEach(System.out::println);
    System.out.println();
    Set<Employee> employeeSet = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());

    employeeSet.forEach(System.out::println);

}

@Test
public void test3(){
	List<String> list = emps.stream().map(Employee::getName).collect(Collectors.toList());
	list.forEach(System.out::println);
	
	System.out.println("----------------------------------");
	
	Set<String> set = emps.stream().map(Employee::getName).collect(Collectors.toSet());
	set.forEach(System.out::println);

	System.out.println("----------------------------------");
	
	HashSet<String> hs = emps.stream().map(Employee::getName).collect(Collectors.toCollection(HashSet::new));
	hs.forEach(System.out::println);
}

List<Employee> emps = Arrays.asList(
    new Employee(102, "李四", 79, 6666.66, Status.BUSY),
    new Employee(101, "张三", 18, 9999.99, Status.FREE),
    new Employee(103, "王五", 28, 3333.33, Status.VOCATION),
    new Employee(104, "赵六", 8, 7777.77, Status.BUSY),
    new Employee(104, "赵六", 8, 7777.77, Status.FREE),
    new Employee(104, "赵六", 8, 7777.77, Status.FREE),
    new Employee(105, "田七", 38, 5555.55, Status.BUSY)
);
@Test
public void test4() {
    // 最大值
    Optional<Double> max = emps.stream()
            .map(Employee::getSalary)
            .collect(Collectors.maxBy(Double::compare));

    System.out.println(max.get());

    // Salary最小值的Employee
    Optional<Employee> op = emps.stream()
            .collect(Collectors.minBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));

    System.out.println(op.get());

    // 总和
    Double sum = emps.stream()
            .collect(Collectors.summingDouble(Employee::getSalary));

    System.out.println(sum);
    
    // 平均值
    Double avg = emps.stream()
            .collect(Collectors.averagingDouble(Employee::getSalary));

    System.out.println(avg);

    // 总数
    Long count = emps.stream()
            .collect(Collectors.counting());

    System.out.println(count);

    System.out.println("--------------------------------------------");

    
    DoubleSummaryStatistics dss = emps.stream()
            .collect(Collectors.summarizingDouble(Employee::getSalary));

    System.out.println(dss.getMax());
}

//分组   根据某属性值对流分组,属性为K,结果为V
//Collectors.groupingBy(Function<? super T, ? extends K> classifier)
@Test
public void test5() {
    Map<Status, List<Employee>> map = emps.stream()
            .collect(Collectors.groupingBy(Employee::getStatus));

    System.out.println(map);
}

//多级分组
//Collectors.groupingBy(Function<? super T, ? extends K> classifier,Collector<? super T, A, D> downstream)
@Test
public void test6() {
    Map<Status, Map<String, List<Employee>>> map = emps.stream()
            .collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((e) -> {
                if (e.getAge() >= 60) {
                    return "老年";

                } else if (e.getAge() >= 35) {
                    return "中年";

                } else {
                    return "成年";
                }
            })));

    System.out.println(map);
}

//分区  根据true或false进行分区
@Test
public void test7() {
    Map<Boolean, List<Employee>> map = emps.stream()
            .collect(Collectors.partitioningBy(e -> e.getSalary() >= 5000));

    System.out.println(map);
}

//
@Test
public void test8() {
    String str = emps.stream()
            .map(Employee::getName)
            .collect(Collectors.joining(",", "----", "----"));

    System.out.println(str);
}

@Test
public void test9() {
    Optional<Double> sum = emps.stream()
            .map(Employee::getSalary)
            .collect(Collectors.reducing(Double::sum));

    System.out.println(sum.get());
}

gmall代码示例

List<WareSkuEntity> wareSkuEntities = wareSkuResponseVo.getData();
if (!CollectionUtils.isEmpty(wareSkuEntities)){
    //goods.setSales(wareSkuEntities.stream().mapToLong(WareSkuEntity::getSales).reduce((a, b) -> a + b).getAsLong());
    goods.setSales(wareSkuEntities.stream().mapToLong(WareSkuEntity::getSales).reduce((a,b)->{return a+b;}).getAsLong());
    //goods.setStore(wareSkuEntities.stream().anyMatch(wareSkuEntity -> wareSkuEntity.getStock() - wareSkuEntity.getStockLocked() > 0));
    goods.setStore(wareSkuEntities.stream().anyMatch(wareSkuEntity -> {return wareSkuEntity.getStock() - wareSkuEntity.getStockLocked() >0;}));
                }
boolean flag = paths.stream().allMatch(path -> requestPath.startsWith(path) == false)
Map<Long, String> saleAttr = skuAttrValueEntities.stream()
    .collect(Collectors.toMap(SkuAttrValueEntity::getId, SkuAttrValueEntity::getAttrValue,(k1,k2)->k1));//key冲突时策略
//  .collect(Collectors.toMap(skuAttrValueEntity->{return skuAttrValueEntity.getId();},skuAttrValueEntity->{return skuAttrValueEntity.getAttrValue();}));
map.forEach((attrId,attrValueEntities)->{
            SaleAttrValueVo saleAttrValueVo = new SaleAttrValueVo();
            saleAttrValueVo.setAttrId(attrId);
            saleAttrValueVo.setAttrName(attrValueEntities.get(0).getAttrName());
        });

4.并行流

1.前置知识:java7 Fork/Join 框架

Fork/Join 框架 了解即可

在java8优化成了并行流

Fork/Join 框架与传统线程池的区别

采用 “工作窃取”模式(work-stealing):

当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。

相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上.

在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态.

而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行.

那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程的等待时间,提高了性能

package com.atguigu.java8;
import java.util.concurrent.RecursiveTask;
public class ForkJoinCalculate extends RecursiveTask<Long>{

	private static final long serialVersionUID = 13475679780L;
	
	private long start;
	private long end;
	
	private static final long THRESHOLD = 10000L; //临界值
	
	public ForkJoinCalculate(long start, long end) {
		this.start = start;
		this.end = end;
	}
	
	@Override
	protected Long compute() {
		long length = end - start;
		
		if(length <= THRESHOLD){ // 小于临界值,就循环求和
			long sum = 0;
			
			for (long i = start; i <= end; i++) {
				sum += i;
			}
			
			return sum;
		}else{  // 大于临界值,就把任务拆分成两半接着执行(各自还会判断是否到临界值,递归执行)
			long middle = (start + end) / 2;
			
			ForkJoinCalculate left = new ForkJoinCalculate(start, middle);
			left.fork(); //拆分,并将该子任务压入线程队列
			
			ForkJoinCalculate right = new ForkJoinCalculate(middle+1, end);
			right.fork();
			
			return left.join() + right.join();
		}		
	}
}

2.并行流 parallelStream

并行与并发的区别

在说到并行的时候,相信很多人都会想到并发的慨念。那么并行和并发两者一字之差,有什么区别呢?

并行:多个任务在同一时间点发生,并由不同的cpu进行处理,不互相抢占资源

当在大量数据处理上,数据并行化可以大量缩短任务的执行时间,

将一个数据分解成多个部分,然后并行处理,最后将多个结果汇总,得到最终结果。

image-20220508121251461

并发:多个任务在同一时间点内同时发生了,但由同一个cpu进行处理,互相抢占资源

image-20220508121437270

并行流的原理

对于并行流,其在底层实现中,是沿用了lava7提供的fork/join分解合并框架进行实现。

fork据cpu核数进行数据分块,join对各个fork进行合并。实现过程如下所示:

假设现在的求和操作是运行在一台4核的机器上。

image-20220508122302473

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。

Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。

使用并行流的注意事项

对于并行流,一定不要陷入一个误区:并行一定比串行快。并行在不同的情况下它不一定是比串行快的。

影响并行流性能主要存在5个因素:

1.数据大小:输入数据的大小,直接影响了并行处理的性能。

​ 因为在并行内部实现中涉及到了fork/join操作,它本身就存在性能上的开销。因此只有当数据量很大,使用并行处理才有意义

2.源数据结构:fork时会对源数据进行分割,数据源的特性直接影响了fork的性能。

​ ArrayList、数组或IntStream.range等,可分解性最佳,因为他们的长度一定且支持随机读取,因此可以被任意分割。
​ HashSet、TreeSet,可分解性一般,其虽然可被分解,但因为其内部数据结构,很难被平均分解。
​ LinkedList、Streams.iterate、BufferedReader.lines等,可分解性极差,因为他们长度未知,无法确定在哪里进行分割。

3.装箱拆箱

​ 尽量使用基本数据类型,避免装箱拆箱。

4.CPU核数

​ fork的产生数量是与可用CPU核数相关,可用的核数越多,获取的性能提升就会越大。

5.单元处理开销

​ 花在流中每个元素的时间越长,并行操作带来的性能提升就会越明显。

并行流的使用

Stream API 可以声明性地通过 parallel() 与sequential() 在并行流与顺序流之间进行切换。ps:list/array.parallelStream也可创建并行流

fork-join:需要提供一个线程池来执行fork-join的工作

java8并行流底层也是fork-join,提供了一个公共的线程池来执行任务

3种方法计算 0-100亿求和

public class TestForkJoin {
	
	@Test //ForkJoin
	public void test1(){
		long start = System.currentTimeMillis();
		
		ForkJoinPool pool = new ForkJoinPool();//提供一个线程池来执行fork-join的工作
		ForkJoinTask<Long> task = new ForkJoinCalculate(0L, 10000000000L);
		
		long sum = pool.invoke(task);
		System.out.println(sum);
		
		long end = System.currentTimeMillis();
		
		System.out.println("耗费的时间为: " + (end - start)); //耗时 1206ms
	}
	
	@Test // 普通for循环
	public void test2(){
		long start = System.currentTimeMillis();		
		long sum = 0L;
		
		for (long i = 0L; i <= 10000000000L; i++) {
			sum += i;
		}
		
		System.out.println(sum);		
		long end = System.currentTimeMillis();		
		System.out.println("耗费的时间为: " + (end - start)); //耗时 5223ms
	}
	
	@Test //并行流
	public void test3(){
		long start = System.currentTimeMillis();
		
		Long sum = LongStream.rangeClosed(0L, 10000000000L)
							 .parallel()
							 .sum();
		
		System.out.println(sum);		
		long end = System.currentTimeMillis();		
		System.out.println("耗费的时间为: " + (end - start)); //耗时 713ms
	}

}

三、Optional类

到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。

以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。

受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。

Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

API

1、如何创建Optional对象?或者说如何用Optional来装值对象或null值

(1)static <T> Optional<T> empty() :用来创建一个空的Optional

(2)static <T> Optional<T> of(T value) :用来创建一个非空的Optional

(3)static <T> Optional<T> ofNullable(T value) :用来创建一个可能是空,也可能非空的Optional

2、如何从Optional容器中取出所包装的对象呢?

(1)T get() :要求Optional容器必须非空

T get()与of(T value)使用是安全的

(2)T orElse(T other) :

orElse(T other) 与ofNullable(T value)配合使用,

如果Optional容器中非空,就返回所包装值,如果为空,就用orElse(T other)other指定的默认值(备胎)代替

(3)T orElseGet(Supplier<? extends T> other) :

如果Optional容器中非空,就返回所包装值,如果为空,就用Supplier接口的Lambda表达式提供的值代替(底层调用supplier.get方法)

orElseGet也是用于当Optional中没有值时,返回默认值的方法。

但是它与orElse的区别在于,它是延迟加载的。只有当Optional中没有值是才会被调用。

(4)<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)

如果Optional容器中非空,就返回所包装值,如果为空,就抛出你指定的异常类型代替原来的NoSuchElementException

3、其他方法

(1)boolean isPresent() :判断Optional容器中的值是否存在

(2)void ifPresent(Consumer<? super T> consumer) :

判断Optional容器中的值是否存在,如果存在,就对它进行Consumer指定的操作,如果不存在就什么都不做

(3)<U> Optional<U> map(Function<? super T,? extends U> mapper)

判断Optional容器中的值是否存在,如果存在,就对它进行Function接口指定的操作,如果不存在就什么都不做

代码示例

package com.atguigu.test07;

import java.util.ArrayList;
import java.util.Optional;

import org.junit.Test;

public class TestOptional {
	@Test
	public void test9(){
		String str = "Hello";
		Optional<String> opt = Optional.ofNullable(str);
        //判断是否是纯字母单词,如果是,转为大写,否则保持不变
		String result = opt.filter(s->s.matches("[a-zA-Z]+")).
				map(s -> s.toLowerCase()).
				orElse(str);
		System.out.println(result);
	}
	
	
	@Test
	public void test8(){
		String str = null;
		Optional<String> opt = Optional.ofNullable(str);
		String string = opt.orElseThrow(()->new RuntimeException("值不存在"));
		System.out.println(string);
	}
	
	
	@Test
	public void test7(){
		String str = null;
		Optional<String> opt = Optional.ofNullable(str);
		String string = opt.orElseGet(String::new);
		System.out.println(string);
	}
	
	@Test
	public void test6(){
		String str = "hello";
		Optional<String> opt = Optional.ofNullable(str);
		String string = opt.orElse("atguigu");
		System.out.println(string);
	}
	
	@Test
	public void test5(){
		String str = null;
		Optional<String> opt = Optional.ofNullable(str);
//		System.out.println(opt.get());//java.util.NoSuchElementException: No value present
	}
	
	@Test
	public void test4(){
		String str = "hello";
		Optional<String> opt = Optional.of(str);

		String string = opt.get();
		System.out.println(string);
	}
	
	
	@Test
	public void test3(){
		String str = null;
		Optional<String> opt = Optional.ofNullable(str);
		System.out.println(opt);
	}
	
	@Test
	public void test2(){
		String str = "hello";
		Optional<String> opt = Optional.of(str);
		System.out.println(opt);
	}
	

}

四、补充

比较器

1.Java比较器的使用背景

​ Java中的对象,正常情况下,只能进行比较:== 或 != 。不能使用 > 或 < 的

​ 但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小。

​ 如何实现?使用两个接口中的任何一个:Comparable 或 Comparator

2.自然排序:使用Comparable接口

1.说明
像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方式。

重写compareTo(obj)的规则:(一般)

如果当前对象this大于形参对象obj,则返回正整数,

如果当前对象this小于形参对象obj,则返回负整数,

如果当前对象this等于形参对象obj,则返回零。

2.自定义类实现Comparable接口,重写compareTo(obj)方法。在compareTo(obj)方法中指明如何排序
自定义类代码举例:

public class Goods implements  Comparable{

 private String name;
 private double price;

 //指明商品比较大小的方式:照价格从低到高排序,再照产品名称从高到低排序
 @Override
 public int compareTo(Object o) {
//        System.out.println("**************");
     if(o instanceof Goods){
         Goods goods = (Goods)o;
         //方式一:
         if(this.price > goods.price){
             return 1;
         }else if(this.price < goods.price){
             return -1;
         }else{
//                return 0;
            return -this.name.compareTo(goods.name);
         }
         //方式二:
//           return Double.compare(this.price,goods.price);
     }
//        return 0;
     throw new RuntimeException("传入的数据类型不一致!");
 }
// getter、setter、toString()、构造器:省略
}

3.定制排序:使用Comparator接口

1.说明
当元素的类型没实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,

​ 那么可以考虑使用 Comparator 的对象来排序

2.一般用法:

​ 重写compare(Object o1,Object o2)方法,比较o1和o2的大小:(类似compareTo的比较规则)

如果方法返回正整数,则表示o1大于o2;

如果返回0,表示相等;

返回负整数,表示o1小于o2。

还可以使用:
Arrays.sort(goods,com);
Collections.sort(coll,com);
new TreeSet(com);
已经实现Comparable接口,重写compareTo方法对象的 compareTo 方法    ...

代码举例:

Comparator com = new Comparator() {
 //指明商品比较大小的方式:照产品名称从低到高排序,再照价格从高到低排序
 @Override
 public int compare(Object o1, Object o2) {
     if(o1 instanceof Goods && o2 instanceof Goods){
         Goods g1 = (Goods)o1;
         Goods g2 = (Goods)o2;
         if(g1.getName().equals(g2.getName())){
             return -Double.compare(g1.getPrice(),g2.getPrice());
         }else{
             return g1.getName().compareTo(g2.getName());
         }
     }
     throw new RuntimeException("输入的数据类型不一致");
 }
}

3.内部的一些静态方法(使用lambda表达式定义比较规则)

​ Comparator.comparing 按xx比较

​ Comparator.reverseOrder Comparator.reversed Comparator.naturalOrder 按xx规则 取反/不取反 排序

​ Comparator.nullsFirst Comparator.nullsLast 将null对象排在集合的首位或尾部

​ comparator.thenComparing //默认方法 先按xx比较,再按此规则比较

使用代码示例:

@Data
public class User { 
    String username;    
    int age;    
    List<User> followers;
}

List<User> users = ...
  1. 按年龄从小到大排序
users.sort(Comparator.comparing(User::getAge));
// 上面采用了方法引用,等同于 users.sort((o1,o2)->o1.getAge().compareTo(o2.getAge()));

代码比较容易理解,取出age,根据age进行大小比较。

  1. 按年龄从大到小排序
users.sort(Comparator.comparing(User::getAge).reversed());

和示例1基本一样,只是在比较后,进行reverse操作(反向操作)。

  1. 按关注者数量从小到大排序
users.sort(Comparator.comparingInt(u -> u.getFollowers().size()));

我们不仅可以传 method reference,还可以自定义lambda。

  1. 按关注者从多到少排序,若关注者数量一样,按年龄从小到大排序
users.sort(Comparator.comparing((User u) -> u.getFollowers().size()).reversed()  .thenComparing(User::getAge));

当有多个条件时,使用 thenComparing 进行组合。

有时被排序的对象可能是null的,Comparator 也提供了 nullsFirst 和 nullsLast 方法,方便我们将null对象排在集合的首位或尾部。

另外要注意,对于复杂属性的排序,我们通常用 Comparator.comparing ,而对简单属性( primitive value,如 int、float ),建议用 Comparator.comparingInt、Comparator.comparingFloat 等,后者可避免自动装箱带来的性能损耗

更多的使用方法,大家可参考 Comparator 源码。

forEach错误

JDK8 中新增的 Lambda 表达式对于 for 循环的操作变得非常简洁

但其中的 forEach 和 for 之间存在一定差异,比如 forEach 无法使用 break 和 continue

forEachreturn 可实现和 contiune 一样的效果

正确代码 增强for循环可以return

遍历用户列表获取盐,对用户输入明文密码加盐加密 比较
for (UserEntity userEntity : userEntities) {
    String salt = userEntity.getSalt();
    if (userEntity.getPassword().equals(DigestUtils.md5Hex(password+salt))){
        return userEntity;
    }
}

错误代码 forEach中无法 continue

userEntities.forEach(userEntity -> {
    String salt = userEntity.getSalt();
    if (userEntity.getPassword().equals(DigestUtils.md5Hex(password+salt))){
        return userEntity;
    }else{
        continue;
    }
});

五、实战

1.集合中元素根据指定字段去重

List<String> resultList=oldList
    .stream()
    .collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(f -> f.getUserName()+f.getAge()))),ArrayList::new));
//解释详见下
public static void main(String[] args) {
    List<User> userList = new ArrayList<>();
    userList.add(new User("1", "李大锤", "23", "南京"));
    userList.add(new User("2", "张无忌", "18", "西安"));
    userList.add(new User("3", "刘德华", "26", "苏州"));
    userList.add(new User("4", "郭靖", "33", "上海"));
    userList.add(new User("1", "李大锤", "23", "南京"));    //id相同,其他数据也相同
    userList.add(new User("3", "带头大哥", "36", "杭州"));  //id相同,其他数据不同
    System.out.println("原始数据:" + userList);
    //使用stream流,然后可以使用steam中才能用的API
    userList = userList.stream().collect(
        // collectingAndThen: 转换函数返回的类型 (包裹另一个收集器,用函数对其结果转换)
        //第一个参数是 Collectors(先将流按照此收集器转化),
        //第二个参数是Function,用来处理第一个被转化后的元素
        Collectors.collectingAndThen(
            // toCollection: 将流中元素收集到指定集合new出来的TreeSet中
            //TreeSet可以在构造方法中指定了比较器来去重(HashSet是根据hash值&equals来去重的,此处不使用)
            Collectors.toCollection(
               // 根据userId去重,主要是利用了 TreeSet的有序不重复特性,并且TreeSet能传入比较器,就能根据某一字段去重
                () -> new TreeSet<>( Comparator.comparing(
                    //这里是 User::getUserid 写法的lambda表达式写法
                    user ->  user.getUserid()
                ))
            )
            ,  //这里是 ArrayList::new 的lambda表达式写法,将上面的 TreeSet转为ArrayList
            treeSet -> new ArrayList<>(treeSet)
        )
    );
    System.out.println("去重后数据:"+ userList);
}

2.Steam流+lambda实现分页

page.setRecords(
	dataList.stream
    		.skip((long)(Math.max(1,currentPage)-1) * pageSize)//跳过前面页数的数据
			.limit(pageSize)//只展示当前页数据
    		.collect(Collectors.toList())
);

image-20220504154318177

优化方案:

如果怕dataList太大,撑爆内存,可先查出全部id的集合,把id集合分页,然后在map里根据当前id查出一条数据

page.setRecords(
	idList.stream
    		.skip((long)(Math.max(1,currentPage)-1) * pageSize)//跳过前面页数的数据
			.limit(pageSize)//只展示当前页数据
    		.map(id-> xxxDao.getByid(id))
    		.collect(Collectors.toList())
);

posted on 2022-05-04 15:21  freyhe  阅读(92)  评论(0编辑  收藏  举报