14.JDK8新特性 limbda&Stream


一、Lambda表达式

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

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

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

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

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

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

@FunctionalInterface//Comparator源码
public interface Comparator {
    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 comparator = new Comparator() {
            @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 comparator = Integer::compare; //方法引用
        Comparator comparator = (o1, o2) ->Integer.compare(o1,o2) ;
        int i = comparator.compare(1, 2);
        System.out.println(i);//-1
    }

3.四大类函数式接口

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

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

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

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

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

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

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

      	@Test
          public void test03_2() {
              //Predicate predicate = (Integer integer)->{return integer%2==0;};
              Predicate predicate = integer -> integer % 2 == 0;
              System.out.println(predicate.test(100));//true
          }
      
  4. 功能型:Funcation 接受T类型参数,返回R类型值

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

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

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

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 表达式的另外一种表现形式)

  1. 对象的引用 :: 实例方法名

  2. 类名 :: 静态方法名

  3. 类名 :: 实例方法名

 注意:
 	 ①方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致!
 	 ②若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式:ClassName::MethodName

使用方法引用的要求:

实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!

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

说明:

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

如下三种主要使用情况:

? 对象 :: 实例方法名

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

? 类 :: 静态方法名

@Test
public void test04(){
    /*
      Comparator comparator = (num1,num2)-> Integer.compare(num1, num2);
      Comparator 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 predicate = String::equals;
    System.out.println(predicate.test("hello","world"));
}

4.2 构造器引用

构造器引用 :构造器的参数列表,需要与函数式接口中参数列表保持一致!

类名 :: new

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

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

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

构造器引用的语法格式:

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

示例代码:

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

}

5.总结

5.1 何时使用lambda表达式?
当需要对一个函数式接口实例化的时候,可以使用lambda表达式。
5.2 何时使用给定的函数式接口?
如果我们开发中需要定义一个函数式接口,首先看看在已有的jdk提供的函数式接口是否提供了
能满足需求的函数式接口。如果有,则直接调用即可,不需要自己再自定义了。

4.3数组引用

? 类型[] :: new;

//数组引用
	@Test
	public void test8(){
		Function fun = (args) -> new String[args];
		String[] strs = fun.apply(10);
		System.out.println(strs.length);
		
		System.out.println("--------------------------");
		
//		Function fun2 = Employee[] :: new;
		Function fun2 = args->new Employee[args];
		Employee[] emps = fun2.apply(20);
		System.out.println(emps.length);
	}

二、Stream

1.Stream说明与注意点

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

集合关注的是数据的存储,与内存打交道

2.注意点:

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

? ②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。

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

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

? ① Stream的实例化

? ② 一系列的中间操作(过滤、映射、...)

? ③ 终止操作

4.说明:

? 4.1 一个中间操作链,对数据源的数据进行处理

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

2.Stream的操作

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

public class StreamAPITest {

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

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

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

    }

    //创建 Stream方式二:通过数组 Arrays.stream(T[] array)
    @Test
    public void test2(){
        int[] arr = new int[]{1,2,3,4,5,6};
        //调用Arrays类的static  Stream 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 stream1 = Arrays.stream(arr1);

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

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

    }

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

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


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

    }

}

2.Stream 的中间操作

1-筛选与切片

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

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

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

? distinct()——筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素

	/1-筛选与切片
    @Test
    public void test1(){
        List list = EmployeeData.getEmployees();
//        filter(Predicate p)——接收 Lambda , 从流中排除某些元素。
        Stream 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-映 射

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) 方法来理解

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

    //        练习1:获取员工姓名长度大于3的员工的姓名。
    List employees = EmployeeData.getEmployees();
    Stream 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> streamStream = list.stream().map(StreamAPITest1::fromStringToStream);
    streamStream.forEach(s ->{
        s.forEach(System.out::println); //s的类型:Stream 所以还需要forEach才可以取出每个元素
    });
    System.out.println("===================================");
    Stream characterStream = list.stream().flatMap(StreamAPITest1::fromStringToStream);
    characterStream.forEach(System.out::println);//characterStream已经把每个流都练到一起所以可以直接操作

}

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

}

3-排序

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

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

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


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

    List 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.终止操作

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

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 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 = employees.stream().findFirst();
    System.out.println(employee);
//        findAny——返回当前流中的任意元素
    Optional employee1 = employees.parallelStream().findAny();
    System.out.println(employee1);

}

@Test
public void test2(){
    List employees = EmployeeData.getEmployees();
    // count——返回流中元素的总个数
    long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
    System.out.println(count);
//        max(Comparator c)——返回流中最大值
//        练习:返回最高的工资:
    Stream salaryStream = employees.stream().map(e -> e.getSalary());
    Optional maxSalary = salaryStream.max(Double::compare);
    System.out.println(maxSalary);
//        min(Comparator c)——返回流中最小值
//        练习:返回最低工资的员工
    Optional 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 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
//        练习2:计算公司所有员工工资的总和
    List employees = EmployeeData.getEmployees();
    Stream salaryStream = employees.stream().map(Employee::getSalary);
//        Optional sumMoney = salaryStream.reduce(Double::sum);
    Optional sumMoney = salaryStream.reduce((d1,d2) -> d1 + d2);
    System.out.println(sumMoney.get());

}

3-收集

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

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

toList List 把流中元素收集到List

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

toSet Set 把流中元素收集到Set

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

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

Collectionemps=list.stream().collect(Collectors.toCollection(ArrayList::new));

counting Long 计算流中元素的个数

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

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

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

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

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

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

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

joining String 连接流中每个字符串

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

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

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

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

Optional min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));

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

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

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

int how = list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));

groupingBy Map> 根据某属性值对流分组,属性为K,结果为V

Map> map = list.stream().collect(Collectors.groupingBy(Employee::getStatus));

partitioningBy Map> 根据true或false进行分区

Map> vd = list.stream().collect(Collectors.partitioningBy(Employee::getManage));

示例代码:


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

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

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

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

}

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

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

List 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 max = emps.stream()
            .map(Employee::getSalary)
            .collect(Collectors.maxBy(Double::compare));

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

    // Salary最小值的Employee
    Optional 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> 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>> 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> 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 sum = emps.stream()
            .map(Employee::getSalary)
            .collect(Collectors.reducing(Double::sum));

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

gmall代码示例

List 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 saleAttr = skuAttrValueEntities.stream()
    .collect(Collectors.toMap(SkuAttrValueEntity::getId, SkuAttrValueEntity::getAttrValue));
//  .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());
        });

3.并行流

java7 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{

	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();
		}		
	}
}

并行流 parallelStream

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

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

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 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
	}

}

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中无法return

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

comparator比较器

1.一般用法:

重写compare方法,可以使用compareTo

2.内部的一些方法(使用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 followers;
}
List users = ...
  1. 按年龄从小到大排序
users.sort(Comparator.comparing(User::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 源码。

相关