java8总结

java8

一、lambda

① 什么是lambda ?

Lambda表达式可以理解为是一段可以传递的代码(将代码像参数一样进行传递,称为行为参数化)。Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)如下所示:

 Comparator<Integer> comparable = (x,y) ->Integer.compare(x,y)

② lamda基础语法

​ 在Java8中引入了一个新的操作符"->"该操作符成为lambda操作符,箭头将lambda表达式分为2部分:

(T … object) ->{ doSomething } [ () 里的为参数列表,每个参数以","分割,**{}**中的则为所需要执行功能 ,T表示类型]

​ **I.**左侧为lambda 表达式的参数列表

​ **II.**右侧为lambda 表达式所需要执行的功能,即 lambda体

什么东西可以省略不写?

I.Lambda表达式的参数列表的数据类型可以省略不写,因为在JVM编译器可以通过上下文推断出数据类型,即"类型推断"

Comparator<Integer> comparable = (Integer x, Integer y) -> {
            System.out.println("x:" + x + ",y:" + y);
            return Integer.compare(x, y);
        };
 //省略后
Comparator<Integer> comparable = ( x, y) -> {
            System.out.println("x:" + x + ",y:" + y);
            return Integer.compare(x, y);
        }; 

​ **II.**参数只有一个时,可以省略(),lambda体只有一行时,可以省略{}

 str -> System.out.println(str)

a.无参数无返回值

Runnable r1 = () ->{ System.out.println("这是一个无参无返回值的lambda表达式")};

b.有参无返回值

Consumer<String> consumer =(str) -> {System.out.println(str)};

c.无参有返回值

  Supplier supplier=()-> {
          String str = String.valueOf("这是一个无参,但有返回值的方法");
          return str;
      };

d.有参有返回值

Comparator<Integer> comparable = (x, y) -> {
            System.out.println("x:" + x + ",y:" + y);
            return Integer.compare(x, y);
        };

e.参数只有一个,lambda体只有一行

x->System.out.println(x)

③ 为啥要用lambda

​ 举个例子,假设现在有一批学生,需要按照如下进行需求,进行过滤掉部分不满足条件的学生:

  • 1.获取StuList中性别为女的学生

  • 2.将StuList中年龄大于18的获取出来

    后面要用到的student实体类

    package com.example.java8.model;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.ToString;
    import lombok.experimental.Accessors;
    
    /**
     * 学生实体对象
     * @author Y
     */
    @Data
    @Accessors(chain=true)
    @AllArgsConstructor
    @NoArgsConstructor
    @ToString
    public class Student {
        /**
         * 姓名
         */
        private String name;
        /**
         * 性别
         */
        private String sex;
        /**
         * 年龄
         */
        private Integer age;
    
        /**
         * 比较年龄大小
         * @param student1 学生1
         * @param student2 学生2
         * @return 学生1的年龄大于学生2的年龄则返回1,反之返回-1  相等返回0
         */
        public static int compareAsc(Student student1, Student student2) {
            return student1.getAge().compareTo(student2.getAge());
        }
    
        /**
         * 比较年龄大小
         * @param student1 学生1
         * @param student2 学生2
         * @return 学生12的年龄大于学生1的年龄则返回1,反之返回-1  相等返回0
         */
        public static int compareDesc(Student student1, Student student2) {
            return student2.getAge().compareTo(student1.getAge());
        }
    }
    
    

    先初始化一个学生List,用于后面要讲解的3种不同方式提供数据:

    private static final String SEX_WOMEN = "女";
    public static final Integer AGE_18 = 18;
    //初始化
    private static List<Student> studentList = Arrays.asList(
            new Student("张三", "男", 16),
            new Student("李四", "男", 15),
            new Student("王五", "男", 17),
            new Student("张二麻子", "男", 18),
            new Student("大乔", "女", 18),
            new Student("小乔", "女", 17)
    );
    

    基于传统的java代码编写(不含有设计模式、java8等等),方式一:

    按照需求,分别写2个方法:按照性别过滤、按照年龄过滤

    /**
         * 按照性别为女的过滤   与sex相同保留,反之丢弃
         * @param studentList 要过滤的学生
         * @param sex   性别
         * @return 过滤完后的学生实体
         */
        private static List<Student> filterBySex(List<Student> studentList,String sex) {
            List<Student> resultList = new ArrayList<>(studentList.size());
            for (Student student : studentList) {
                if (sex.equals(student.getSex())) {
                    resultList.add(student);
                }
            }
            return resultList;
        }
    
        /**
         * 按照年龄过滤学生 --与age相等则保留 反之丢弃
         * @param studentList 要过滤的学生实体
         * @param age         过滤年龄
         * @return  已经过滤后的学生实体
         */
        private static List<Student> filterByAge(List<Student> studentList,Integer age) {
            List<Student> resultList = new ArrayList<>(studentList.size());
            for (Student student : studentList) {
                if (age.equals(student.getAge())) {
                    resultList.add(student);
                }
            }
            return resultList;
        }
    //方式一:  按照需求定制化不同的方法
         @Test
         private void test01(){
             //打印性别为女的
             System.out.println("方式一调用:");
             for (Student filterBySex : filterBySex(studentList,SEX_WOMEN)) {
                 System.out.println("性别为女的:" + filterBySex);
             }
             //打印年龄为18的
             for (Student filterByAge : filterByAge(studentList,AGE_18)) {
                 System.out.println("龄为18的:" + filterByAge);
             }
         }  
    

基于匿名内部类进行编写:

​ 新建一个用于条件判断的接口IMypredicate

public interface IMypredicate {
    /**
     * 用于用户自定义过滤条件
     *
     * @param student 需要进行断言的学生
     * @return 断言的结果
     */
    boolean filter(Student student);
}

然后写一个filter方法,用于按用于自定义的predicate进行过滤studentList

  /**
    * 自定义学生条件过滤方法
    * @param studentList  要过滤的学生实体
    * @param predicate     过滤条件
    * @return  过滤完的学生
    */
   private static List<Student> filter(List<Student> studentList, IMypredicate predicate) {
       List<Student> resultList = new ArrayList<>(studentList.size());
       for (Student student : studentList) {
           if (predicate.filter(student)) {
               resultList.add(student);
           }
       }
       return resultList;
   }
//方式二:
   //提一个公有接口IMypredicate出来,定义一个方法filter,后面要按照某种条件直接实现内部类即可
   @Test
   private void test02(){
       System.out.println("方式二调用:");

       List<Student> filterBySex = filter(studentList, new IMypredicate() {
           @Override
           public boolean filter(Student student) {
               return SEX_WOMEN.equals(student.getSex());
           }
       });
       for (Student bySex : filterBySex) {
           System.out.println("性别为女的:" + bySex);
       }
       //获取年龄为18的
       List<Student> filterByAge = filter(studentList, new IMypredicate() {
           @Override
           public boolean filter(Student student) {
               return AGE_18.equals(student.getAge());
           }
       });
       for (Student byByAge : filterByAge) {
           System.out.println("年龄为18的:" + byByAge);
       }
   }

基于lambda表达式:


   //方式三:
   //lambda表达式写法
   @Test
   private void test03() {
       //方式三(lambda):
       //过滤性别
       filter(studentList, student -> SEX_WOMEN.equals(student.getSex()));
       //获取年龄大于15
       filter(studentList, student -> student.getAge() > 15);
       //获取年龄大于15 且为女的学生
       filter(studentList, student -> student.getAge() > 15 && student.getSex().equals(SEX_WOMEN));
   }

二、函数式接口

① 函数式接口是什么?

a.只包含一个抽象方法的接口,称为函数式接口

​ **b.**你可以通过Lambda表达式来创建该接口的对象。(若Lambda表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。

c.我们可以在任意函数式接口上使用@FunctionalInterface注解,这样做可以检查它是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。

@FunctionalInterface
public interface MyNumber{
    public double getValue();
}

② 四大核心函数式接口

Java内置四大核心函数式接口:
函数式接口参数类型返回类型用途
Consumer 消费型接口Tvoidvoid accept(T t),接收一个参数进行消费,但无需返回结果
Supplier 提供型接口TT get(),无参数,有返回值。
Function<T, R> 函数型接口TR对类型为T的对象应用操作,并返回结果为R类型的对象:R apply(T t)
Predicate 断言型接口Tboolean确定类型为T的对象是否满足某约束,并返回boolean值:boolean test(T t)
Consumer 消费型接口
方法备注
void accept(T t)接受一个入参,并且无返回值

void accept(T t)

@Test
public void  consumerTest(){
    handle("2021",(x-> System.out.println(String.format("今年是%s年",x))) );
}

public  <T>  void handle(T value, Consumer<T> consumer) {
    consumer.accept(value);
}
//输出结果 今年是2021年
Supplier供给型接口
方法备注
T get()无入参,但提供一个返回值
@Test
public void  supplierTest(){
    //调用方法 生成10个随机数
    List<Integer> lists = handle(10, () ->  (int) (Math.random()*100));
    System.out.println(lists);
}

//返回一个长度为num 的List集合
public   List<Integer> handle(int num,Supplier<Integer> supplier) {
    List<Integer>lists=new ArrayList<>();

    for (int i = 0; i < num; i++) {
        lists.add(supplier.get());
    }

    return lists;
}
//输出[55, 33, 12, 79, 93, 46, 51, 44, 69, 0]
Function<T,R> 函数型接口
方法备注
R apply(T t)将参数T应用到函数中,返回一个R
@Test
public  void  testFunction(){
    //入参为String类型 返回参数为String类型
    String s1 = handleFunction("\t\t\t张三、李四    ", (x) -> x.trim());
    System.out.println(s1);//张三、李四

    //入参为int类型 返回参数为String类型
    String s = handleFunction(123456, x -> String.format("数字转为字符串:%d",x));
    System.out.println(s);  //数字转为字符串:123456
}
private <T,R>  R handleFunction(T value, Function<T,R> function){
    return function.apply(value);
}

Predicate断言型接口
方法备注
boolean test(T t)接受一个输入参数T,返回一个布尔值结果
@Test
public  void  testPredicate(){
    //筛选年龄大于30的人
    List<Student> valid = valid(studentList, stu -> stu.getAge() < 16);
    valid.forEach(person -> System
                  .out
                  .println("姓名:"+person.getName()+
                           ",年龄:"+person.getAge()));
}

//校验方法
private List<Student> valid(List<Student> studentList, Predicate<Student> predicate){
    List<Student> stuList=new ArrayList<>();
    for (Student student : studentList) {
        if (predicate.test(student)){
            stuList.add(student);
        }
    }
    return  stuList;
}

三、方法引用

​ 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用放方法的参数列表保持一致)

方法引用: 使用操作符"::"将方法名和对象或类的名字分割开来.

主要有如下四种格式:

类型语法Lambda表达式
静态方法引用类名::静态方法名(args) -> 类名.静态方法名(args) [注这个args可以为多个,在这只是泛指]
实例方法引用实例::实例方法名(args) -> 实例.实例方法名(args)
对象方法引用类名::对象方法名(args) -> 类名.对象方法名(args)
构建方法引用类名::new(args) -> new 类名(args)
① 对象::实例方法名
package com.example.java8;

import com.example.java8.model.Student;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;

@SpringBootTest
class FunctionTests {
   @Test
    public  void  testMethodReferences(){
       Student student = studentList.get(0);
        //无参时
       //lambda写法
       Supplier<String> supplierLambda=()->student.getName();
       //方法引用写法
       Supplier<String> supplier= student::getName;
       System.out.println("student name:"+supplierLambda.get());
       System.out.println("student name:"+supplier.get());
       System.out.println("__________________________________________");
        //2个参数时
       //按照年龄从大到小的排序
       //lambda格式:
       studentList.sort((student1,student2)->student.compareDesc(student1,student2));
       //方法引用格式:
       studentList.sort(student::compareDesc);
       for (Student stu : studentList) {
           System.out.println(stu);
       }
   }
}
② 类::静态方法名
@Test
public  void  test02(){
    //按照年龄从小往大的排序
    //lambda格式:
    studentList.sort((student1,student2)->Student.compareAsc(student1,student2));
    //方法引用格式:
    studentList.sort(Student::compareAsc);
    for (Student stu : studentList) {
        System.out.println(stu);
    }
}
③ 类::实例方法名

​ 这里备注一下,类无法::普通方法

 @Test
    public void test03() {
        //得到学生名字并输出
        //lambda:
        studentList
                .stream()
                .map(student -> student.getName())
                .forEach(name -> System.out.println(name));
        //方法引用:
        studentList
                .stream()
                .map(Student::getName)
                .forEach(System.out::println);
    }
**④ **类::new
@Test
public void test04() {
    //如果studentList为null 则new一个List
    //lambda:
    List<Student> studentList = Optional.ofNullable(FunctionTests.studentList).orElseGet(() -> new ArrayList<>());
    //方法引用:
    studentList = Optional.ofNullable(FunctionTests.studentList).orElseGet(ArrayList::new);
}

四、 Stream流

① 什么是Stream(流)?

可以理解为是一个数据管道,用于操作数据所生成的元素序列

a. Stream自己不会存储元素(流是基于数据源的对象,它本身不存储数据元素,而是通过管道将数据源的元素传递给操作。)

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

c. Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行

② 流的三大操作

1.创建Stream流

2.通过Stream流对象执行中间操作

③.执行最终操作,得到结果

​ 可以用生产玩具车的流水线举例:将原材料放到流水线上(创建流),流水线上的工人对这个原材料不断加工,如拼装轮子然后将这个玩具,传递给下一个流水线的工人进行贴图纸,然后再传递给下一个流水线的工人做其他操作(流的一系列中间操作,返回结果仍然是流),然后流水线到头,将玩具车装起来(流水线到头了,也就是我们说的终止操作,返回是一个不是流的实际的对象 或者void)

image-20210407214649002

a.创建Stream

最常用的直接是集合创建流

/**
 * 创建流
 */
@Test
public void testCreateStream() {
    /*
      集合流
       - Collection.stream() 穿行流
       - Collection.parallelStream() 并行流
     */
    Stream<Student> stream = studentList.stream();
    Stream<Student> parallelStream = studentList.parallelStream();

    // 从数组创建
    IntStream stream3 = Arrays.stream(new int[]{2, 3, 5});

    //Stream 静态方法
    //Stream.of(...)
    Stream<Integer> stream4 = Stream.of(1, 2, 3);
}
b.中间操作

​ 中间操作可以理解为对Stream流一系列操作,比如过滤、映射等,中间操作返回的结果都是Stream流,因此我们可以链式调用,但如果不执行终止操作,中间操作是不会执行任何操作的,可以理解为惰性求值

中间操作

​ 下面的代码是用于检验流的懒加载,如果将**nameStream.collect(Collectors.toList())**这一行代码注释掉,你会发现,当整个方法运行完,都没有执行打印。原因也很简单,就是上面所诉说的,流在未执行终止操作前,不会进行任何操作。


/**
 * 中间操作
 */
@Test
public void testStreamFeatures() {
   //创建流
    Stream<Student> stream = studentList.stream();
    //过滤年龄小于15的
    Stream<Student> ageStream = stream.filter(stu -> stu.getAge() > 15);
    //使用map得到学生姓名,并打印学生信息
    Stream<String> nameStream = ageStream.map(stu -> {
        System.out.println(stu);
        return stu.getName();
    });
    //执行终止操作
    List<String> stuNameList = nameStream.collect(Collectors.toList());
}

常用的中间操作

方法描述
filter(Predicate predicate)接收一个函数作为参数,根据提供的筛选条件predicate进行过滤
map(Function f)接收一个函数作为参数,该函数会作用到每个元素上,映射成新的元素
flatMap(Function f)接收一个函数作为参数,将流中的每个值都转换成另外一个流,然后所有流连接为一个流
sorted()按照自然顺序进行排序,返回一个新流
sorted(Comparator comp)自定义排序顺序,返回一个新流
skip(long n)将前n个元素进行丢弃,返回一个新流
limit(long maxSize)截取到maSize个数的元素值,返回一个新流
  • map操作

    将R类型的参数,转换为T类型(R与T可以一样),最终转换返回一个新的流

    image-20210410113203429

    /**
     * 中间操作 map
     */
    @Test
    public void mapTest() {
        Stream<Student> studentStream = studentList
            .stream();
        //提取学生的姓名  将Student类型转换String类型
        Stream<String> stringStream = studentList
            .stream()
            .map(Student::getName);
        //打印输出
        stringStream.forEach(System.out::println);
    }
    
  • flatMap 操作

    ​ 将R类型的参数转换为T类型,如果Stream流中的元素是被Stream流所包裹,会将这个元素取出来,重新构成一个新的Stream流(其实flatmap只是比map厉害一点可以多转一层)

    image-20210410122639486

    @Test
    public void testFlatMap() {
        //创建一个字符串集合
        List<String> list = Arrays.asList("AB", "CD", "EFG");
        Stream<Character> stream1= list
            .stream()
            .flatMap(this::stringToChar);
        //打印
        System.out.println("入参是String,出参是Stream<Character>:");
        stream1.forEach(System.out::println);
    }
    
  • filter过滤操作

    ​ filter方法就是基于我们传入的Predicate ,进行过滤数据,过滤后会产生一个新的Stream,不会对源数据产生影响

    @Test
    public void  testFilter(){
        //打印过滤前的学生集合长度
        System.out.println("过滤前学生长度:"+studentList.size());
    
        //过滤年龄小于16岁的学生
        List<Student> filterList = studentList
            .stream()
            .filter(stu -> stu.getAge() > 15)
            .collect(Collectors.toList());
        //打印过滤后的学生集合长度
        System.out.println("过滤前学生长度:"+filterList.size());
        System.out.println("过滤后,源数据源的长度:"+studentList.size());
    }
    
  • sorted排序操作
    @Test
    public void sortedTest() {
        //学生按照年龄从小到大的排序
        System.out.println("按照年龄从小到大排序:");
        studentList
                .stream()
                .sorted(Comparator.comparing(Student::getAge))
                .forEach(System.out::println);
        //学生按照年龄从大到小的排序
        System.out.println("按照年龄从大到小排序:");
        studentList
                .stream()
                .sorted((stu1,stu2)->stu2.getAge().compareTo(stu1.getAge()))
                .forEach(System.out::println);
    }
    
  • skip 操作

    skip用于跳过前n个元素,从第n+1个开始获取值

    @Test
    public void skipTest() {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        list
            .stream()
            .skip(3)
            .forEach(System.out::println);
    }
    
  • limit 操作

    截取第一个到第n个元素,n+1个元素及其以后的元素都抛弃

    @Test
    public void limitTest() {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        list
            .stream()
            .limit(3)
            .forEach(System.out::println);
    }
    
c.终止操作

​ 终止在从流的流水线中(中间操作)生成最终的结果,其结果可以是任何不是流的值,且执行过终止操作的流是不可再使用的

  • 查找与匹配
方法简述
allMatch(Predicate p)检查是否匹配所有元素
anyMatch(Predicate )检查是否至少匹配一个元素
noneMatch(Predicate p)检查是否没有匹配所有元素
findFirst()返回第一个元素
findAny()返回当前流中的任意元素
count()返回流中元素总数
max(Comparator c)返回流中最大值
min(Comparator c)返回流中最小值
forEach(Consumer c)stream API 使用内部迭代(默认做了外部迭代)
@Test
public void testTermination() {
    //allMatch 检查是否匹配所有元素
    boolean allMatch = studentList
        .stream()
        .allMatch(e -> e.getName().equals("张三"));
    System.out.println("allMatch:" + allMatch);
    System.out.println("___________________________________________________________________");
    boolean anyMatch = studentList
        .stream()
        .anyMatch(e -> e.getName().equals("李四"));
    System.out.println("anyMatch:" + anyMatch);
    System.out.println("___________________________________________________________________");
    //noneMatch 检查是否没有匹配元素
    boolean noneMatch = studentList
        .stream()
        .noneMatch(e -> e.getName().equals("java"));
    System.out.println("noneMatch:" + noneMatch);
    System.out.println("___________________________________________________________________");
    //findFirst 返回第一个元素
    Optional<Student> findAny = studentList
        .stream()
        .findAny();
    System.out.println("findAny:" + findAny);
    System.out.println("___________________________________________________________________");
    //findAny 返回流数据中的任意元素
    Optional<Student> findFirst = studentList
        .stream()
        .findFirst();
    System.out.println("findFirst:" + findFirst);
    System.out.println("___________________________________________________________________");
    //count 返回流中总个数
    long count = studentList
        .stream()
        .count();
    System.out.println("count:" + count);
    System.out.println("___________________________________________________________________");
    //max 返回流中最大的一个值
    Optional<Student> max = studentList
        .stream()
        .max(Comparator.comparing(Student::getAge));
    System.out.println("max:" + max);
    System.out.println("___________________________________________________________________");
    //min 返回流中最小值
    Optional<Student> min = studentList.stream().min(Comparator.comparing(Student::getAge));
    System.out.println("min:" + max);
}
  • 归约
方法简述
reduce(T iden, BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 T
reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 Optional

方法1返回的是一个对象T,而方法二返回的是Optional,原因是方法1给了一个初始值iden

@Test
public void test3() {

    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
    Integer reduce = list.stream()
            .reduce(45, Integer::sum);

    Optional<Integer> reduce1 = list
                                    .stream()
                                    .reduce(Integer::sum);
}
  • 收集器
方法简述
collect将流转换为其他形式。接收一个Collection接口的实现,用于Stream中元素做汇总的方法

collect常与Collectors进行搭配使用,Collectors中提供了大量的方法可以使用,详见下面表(只是常用的):

方法简述
Collector<T, ?, List> toList它将输入元素 List到一个新的 List 。
Collector<T, ?, Set> toSet将输入元素 Set到一个新的 Set 。
Collector<T, ?, Map<K,U>> toMap它将元素累加到一个 Map ,其键和值是将所提供的映射函数应用于输入元素的结果。
Collector<T, ?, C> toCollection按照遇到的顺序将输入元素累加到一个新的 Collection中
Collector<CharSequence, ?, String> joining返回一个 Collector ,将输入元素连接到一个 String ,按照顺序。
groupingBy返回Collector “由基团”上的类型的输入元件操作实现T ,根据分类功能分组元素,并且在返回的结果Map 。
  • toList
@Test
public void  toListTest(){
    //跳过2个学生,收集一个新的学生集合
    List<Student> students = studentList
                                    .stream()
                                    .skip(2)
                                    .collect(Collectors.toList());
    //打印学生信息
    students.stream().forEach(System.out::println);
}
  • toSet
@Test
public void toSetTest() {
    //收集学生的年龄 转换为set
    Set<Integer> ageSet = studentList
        .stream()
        .map(Student::getAge)
        .collect(Collectors.toSet());
    //打印年龄
    ageSet.forEach(System.out::println);
}
  • tomap (F标识Function,BO标识BinaryOperator)
    • Collector toMap(F keyMapper,F valueMapper)

      分别传入keyMapper、valueMapper的函数式方法,将执行后的结果放入Map中

      @Test
      public void toMapTest() {
          //按照姓名转map
          Map<String, Integer> nameMap = studentList
                  .stream()
                  .collect(
                          Collectors.toMap(Student::getName, Student::getAge)
                  );
          for (Map.Entry<String, Integer> map : nameMap.entrySet()) {
              System.out.println(map.getKey()+","+map.getValue());
          }
      }
      
    • Collector toMap(F keyMapper,F valueMapper, BO mergeFunction)

      ​ 分别传入keyMapper、valueMapper的函数式方法,将执行后的结果放入Map中,其中如果出现key值重复,则使用mergeFunction进行处理

      @Test
      public void toMapTest() {
        //使用学生的年龄作为key值,key重复则保留第一个
          Map<Integer, String> ageMap = studentList
                .stream()
                  .collect(
                        Collectors.toMap(Student::getAge, Student::getName, (v1, v2) -> v1)
                  );
          //打印map值
          for (Map.Entry<Integer, String> map : ageMap.entrySet()) {
              System.out.println(map.getKey()+","+map.getValue());
          }
      }
      
    • Collector toMap(F keyMapper, F valueMapper,BO mergeFunction,Supplier mapSupplier)

      这个方法相比于上面方法多了一个mapSupplier,它可以用于选择哪个Map

      @Test
      public void toMapTest() {
          LinkedHashMap<Integer, String> linkedHashMap = studentList
                  .stream()
                  .collect(
                          Collectors.toMap(Student::getAge, Student::getName, (v1, v2) -> v2, LinkedHashMap::new)
                  );
          TreeMap<Integer, String> treeMap = studentList
                  .stream()
                  .collect(
                        Collectors.toMap(Student::getAge, Student::getName, (v1, v2) -> v2, TreeMap::new)
                  );
      

    }

    
    - ##### join操作 
    
    joining(CharSequence delimiter),将delimiter加入到流中的每个元素中间,拼接成一个字符串进行返回
    
    ```java
    @Test
    public void joinTest(){
        String str = studentList
                .stream()
                .map(Student::getName)
            	//将 、 加入到每个元素中间
                .collect(Collectors.joining("、"));
        System.out.println(str);
    }
    
    
    
    
  • groupingBy

    根据某个属性值,对流进行分组,它有3个不同的方法:

    • groupingByConcurrent(Function)

      基于传入的function执行得到相应的key,基于这个key值进行分组

    • groupingBy(Function, Collector)

      基于传入的function执行得到相应的key,基于这个key值进行分组,collector则是基于已经分好组的value,再次做一些操作

    • groupingBy(Function, Supplier, Collector)

      这个相比于上面方法,多了一个Supplier,它表示你分组使用什么样的容器存储,默认是HashMap

    public void groupByTest() {
        //按照学生姓名进行分组
        Map<String, List<Student>> nameMap = studentList
                                                        .stream()
                                                        .collect(
                                                                //groupingByConcurrent(Function)
                                                          Collectors.groupingBy(Student::getName));
    
        //按照学生年龄进行分组.然后再统计每个组的个数
        Map<Integer, Long> ageCountMap = studentList
                                                    .stream()
                                                    .collect(
                                                            // groupingBy(Function, Collector)
                                                            Collectors.groupingBy(Student::getAge, 		                                                                                    Collectors.counting()));
    
        //按照学生年龄进行分组.然后再统计每个组的个数 使用TreeMap进行存储
        TreeMap<Integer, Long> ageCountTreeMap = studentList
                .stream()
                .collect(
                        // groupingBy(Function, Collector)
                        Collectors.groupingBy(Student::getAge, TreeMap::new, Collectors.counting())
                );
        for (Map.Entry<Integer, Long> integerLongEntry : ageCountMap.entrySet()) {
            System.out.println(integerLongEntry.getKey()+","+integerLongE
                               ntry.getValue());
        }
    
    }
    public void groupByTest() {
        //按照学生姓名进行分组
        Map<String, List<Student>> nameMap = studentList
                                                        .stream()
                                                        .collect(
                                                                //groupingByConcurrent(Function)
                                                                Collectors.groupingBy(Student::getName));
    
        //按照学生年龄进行分组.然后再统计每个组的个数
        Map<Integer, Long> ageCountMap = studentList
                                                    .stream()
                                                    .collect(Collectors.groupingBy(Student::getAge,                                                                                         Collectors.counting()));
    
        //按照学生年龄进行分组.然后再统计每个组的个数 使用TreeMap进行存储
        TreeMap<Integer, Long> ageCountTreeMap = studentList
                .stream()
                .collect(
                        // groupingBy(Function, Collector)
                        Collectors.groupingBy(Student::getAge, TreeMap::new, Collectors.counting())
                );
        for (Map.Entry<Integer, Long> integerLongEntry : ageCountMap.entrySet()) {
            System.out.println(integerLongEntry.getKey()+","+integerLongE
                               ntry.getValue());
        }
    
    }
    

五、Optional

​ Optional可以让避免空指针的代码简洁且具高可读

① 为何要用Optional?

新建person、car、Insurance 3个对象,用于空指针讲解

public class Person {
    private Car car;
    public Car getCar() { return car; }
}
public class Car {
    private Insurance insurance;
    public Insurance getInsurance() { return insurance; }
}
public class Insurance {
    private String name;
    public String getName() { return name; }
}

由上可知,三者的关系是:人类拥有汽车,汽车上绑定了保险,通过一下代码可知,很容易会产生空指针异常问题:

@Test
public  void testNullPointerException() {
    //nullDataPerson()直接返回一个null
    Person person = nullDataPerson();
    //获取保险的名称
    String insuranceName = person.getCar().getInsurance().getName();
    //打印名称
    System.out.println("汽车保险的名称:"+insuranceName);
}

使用if避免空指针异常

@Test
public void testCheckNullPointerByIf() {
    //nullDataPerson()直接返回一个null
    Person person = nullDataPerson();
    if (person != null) {
        Car car = person.getCar();
        if (car != null) {
            Insurance insurance = car.getInsurance();
            if (insurance != null) {
                System.out.println("汽车保险的名称:" + insurance.getName());
            }
        }
    }
}

这里先埋个坑,放在第五章第③节中的map方法中讲解.

② 创建Optional对象
  • 创建一个空的Optional

    Optional<Car> car = Optional.empty();
    
  • 使用of创建Optional对象

    注:使用of创建Optional对象时,这个obj一定不能为null,否则会报空指针

    Optional<Person> person = Optional.of(obj);
    
  • 使用ofNullable创建Optional对象

    注:见名知意使用ofNullable创建,obj可以为null

    Optional<Person> person = Optional.ofNullable(obj);
    
③ Optional中常用方法
方法说明
boolean isPresent()判断对象是否不为null
void ifPresent(Consumer consumer)如果对象存在,则执行这个consumer
T get()获取Optional中的值(若值为null,会抛一个空指针)
T orElse(T other)当值为空时,使用other替代
T orElseGet(Supplier other)当值为空时,使用Supplier执行产生的对象进行替代
T orElseThrow (Supplier exceptionSupplier)当值为空时,使用Supplier抛一个异常出来,当值不为空时,返回值
Optional map(Function mapper)和stream一样,对对象进行映射成一个新的值,返回Optional
  • 用于模拟数据库查库的方法

    /**
     * 用于测试使用 产生一个空的person
     * @return null
     */
    private Person nullDataPerson() {
        return null;
    }
    
    /**
     * 用于测试使用 产生一个空的person
     * @return null
     */
    private Person nullDataCar(String personName) {
        return new Person(personName,null);
    }
    private Person getPerson(){
        System.out.println("调用getPerson获取一个person");
        return new Person("person",null);
    }
    /**
     *返回一个全量数据的Person
     * @return 返回一个具有保险名称的person
     */
    private Person nullDataCar() {
        return  new Person("法外狂徒张三",new Car(new Insurance("太平洋车辆保险")));
    }
    
  • isPresent()

    判断对象是否为空,正常情况下都不会用这个东西,要不然和if没啥区别

    @Test
    public  void  test03(){
        //isPresent() 判断对象是否存在,如果存在着为true,反之false
            boolean present = Optional
                                    .ofNullable(nullDataPerson())
                                    .isPresent();
    }
    
  • void ifPresent(Consumer<? super T> consumer)

    如果对象存在,则执行这个consumer进行一些操作

    @Test
    public  void  test03(){
        //如果对象存在 打印对象
         Optional       
             .ofNullable(nullDataCar("法外狂徒张三"))
             .ifPresent(System.out::print);
    }
    
  • T get()

    获取被Optional所包裹的对象,如果被包裹的对象为null,则会抛出一个空指针异常

    @Test
    public  void  test03(){
        //get()   获取被Optional所包裹的对象 如果为null则抛空指针异常
        Person person = Optional
                                .ofNullable(nullDataCar("法外狂徒张三"))
                                .get();
    }
    
  • T orElse(T other)|T orElseGet(Supplier supplier )| T orElseThrow(Supplier supplier) throws X

    orElse当对象为null时,会直接将other当做默认值,然后返回(如果other传入的是一个方法,即使值不为空,也会执行这个方法)

    @Test
    public  void  test03(){
        //获取学生的部门名称
        Student stu =  findOne("张三");
        //有值情况下
        Student student = Optional
                .ofNullable(stu)
                .orElse(getStudent());
        //无值情况下
        Student studentNull = Optional
                .ofNullable(findOneWithNull())
                .orElse(getStudent());
    }
    private Student getStudent(){
        System.out.println("执行了getStudent方法");
        return new Student().setName("默认值");
    }
    /**
     * 模拟数据库查询操作 返回null
     * @return Student
     */
    private Student findOneWithNull(){
        return null;
    }
    private Student findOne(String name){
        return new Student().setName(name);
    }
    //输出
    //执行了getStudent方法
    //执行了getStudent方法
    

    orElseGet当对象为null时,会执行supplier产生一个值进行返回,如果值不为空,则不会执行supplier

    @Test
    public  void  test05(){
        //orElseGet(Supplier supplier )
        person = Optional
            .ofNullable(nullDataCar("李四"))
            .orElse(this::getPerson);
        System.out.println("打印人名:"+person.getName());
    }
    

    orElseThrow当对象为null时,会执行supplier然后抛出一个异常,当值不为空时,返回值

    @Test
    public  void  test06(){
        Student student = Optional
            .ofNullable(stu)
            .orElseThrow(()->new ResourceNotFoundException("根据:"+name+"未找到学生实体"));
      
    }
    
  • Optional map(Function<? super T, ? extends U> mapper)

    ​ 和stream一样,对对象进行映射成一个新的值,返回Optional,这个可以用来避免多次if判断导致代码冗余

    @Test
    public void testMethod(){
        String name = Optional
                            .of(allDataPerson())
                            .map(Person::getCar)
                            .map(Car::getInsurance)
                            .map(Insurance::getName)
                            .orElse("默认值");
        System.out.println("打印名称:"+name);
    }
    

六、并行流

并行流就是把一个内容分成多个数据块,并用不同的现场处理每个数据块的流,如果是单核CPU,只会存在并发而不会并行。

Fork/Join 框架

​ Fork/Join框架是将一个大任务划分为若干个子任务,各自执行,最后将执行结果进行汇总,然后得到大任务的结果的框架。

图片来源于:《java8实战》

ForkJoin.png

工作窃取算法

​ 分支/合并框架工程用一种称为工作窃取( work stealing)的技术来解决这个问题。在实际应用中,这意味着这些任务差不多被平均分配到ForkJoinPool中的所有线程上。每个线程都为分配给它的任务保存一个双向链式队列,每完成一个任务,就会从队列头上取出下一个任务开始执行。基于前面所述的原因,某个线程可能早早完成了分配给它的所有任务,也就是它的队列已经空了,而其他的线程还很忙。这时,这个线程并没有闲下来,而是随机选了一个别的线程,从队列的尾巴上“偷走”一个任务。这个过程一直继续下去,直到所有的任务都执行完毕,所有的队列都清空。这就是为什么要划成许多小任务而不是少数几个大任务,这有助于更好地在工作线程之间平衡负载。

工作窃取图解
工作窃取图

​ 并行流内部使用了默认的ForkJoinPool( 7.2节会进一步讲到分支/合并框架),它默认的线 程 数 量 就 是 你 的 处 理 器 数 量 , 这 个 值 是 由 Runtime.getRuntime().availableProcessors()得到的。但 是 你 可 以 通 过 系 统 属 性 java.util.concurrent.ForkJoinPool.common.parallelism来改变线程池大小 .

并行流开启方式

以下2种都可以开启并行流

@Test
public void testMethod(){
    //第一种 直接.parallelStream()
    Stream<Student> studentStream = studentList
        .parallelStream();
    //第二种 .parallel()
    Stream<Long> parallel = Stream.iterate(1L, i -> i + 1)
        .limit(n)
        .parallel();

}

并行流存在的问题

并行流一定比顺序流更快?
测量对前n个自然是求和函数的性能

​ 首先准备以下3个方法:parallelSum并行迭代器求和、iterativeSum迭代器求和、parallelRangedSum 使用LongStream参数的数值求和


public long parallelSum(long n) {
    return Stream.iterate(1L, i -> i + 1)
            .limit(n)
            .parallel()
            .reduce(0L, Long::sum);
}
public long iterativeSum(long n) {
    long result = 0;
    for (long i = 1L; i <= n; i++) {
        result += i;
    }
    return result;
}
public  long parallelRangedSum(long n) {
    return LongStream.rangeClosed(1, n)
            .parallel()
            .reduce(0L, Long::sum);
}

在提供一个用于加载以上三者不同求和方式的方法,用于测试3种方法求和耗时,这个方法会对方法采样十次,输出计算和、计算耗时

public void measureSumPerf(Function<Long, Long> adder, long n) {
    for (int i = 0; i < 10; i++) {
        long start = System.currentTimeMillis();
        long sum = adder.apply(n);
        long duration = (System.currentTimeMillis() - start) ;
        System.out.println("sum: " + sum+",time:"+duration);
    }
}
样本数据

以下是执行求和方法代码

@Test
public void testParallelSum() {
    measureSumPerf(this::parallelSum, 10000000L);
}

@Test
public void testIterativeSum() {
    measureSumPerf(this::iterativeSum, 10000000L);

}
@Test
public void testRangedSum() {
    measureSumPerf(this::parallelRangedSum, 10000000L);

}

以下是上述3种不同方式求和耗时样本数据

parallelSumiterativeSumparallelRangedSum
10551955
1576101
435102
28192
213111
33115
22395
145113
173125
32732

​ 通过以上数据发现,并行版本比顺序版本要慢很多,因此可以得出,盲目的使用并行流并不一定能够让程序更快,可能会让程序更慢,那么为啥上述程序并行流比顺序版本慢? 但使用parallelRangedSum 进行并行求和就会很快呢?

1.parallelSum使用的是Stream.iterate来产生数据,而生产的都是装箱数据,在计算的时候需要将数据进行拆箱,而iterativeSum产生的数据则不需要做任何装箱或者拆箱操作

2.iiterate很难分割成能够独立执行的小块,因为每次应用这个函数都要依赖前一次应用的结
果 (上述阐述过,并行流采用的是Fork/Join,需要将任务拆分再合并)

3.LongStream.rangeClosed直接产生原始类型的long数字,没有装箱拆箱的开销。
4.LongStream.rangeClosed会生成数字范围, 很容易拆分为独立的小块

并行流中的安全问题

提供一个用于测试的计算类Accumulatoradd方法用于进行累加

public class Accumulator {
    public long total = 0;

    public void add(long value) {
        total += value;
    }
}

使用上述类,进行5次测试,观测一下每次计算的结果是否一致

@Test
public  void testThread(){
    for (int i = 0; i < 5; i++) {
        System.out.println("打印每次计算和:"+sideEffectSum(100000L));
    }
}
public  long sideEffectSum(long n) {
    Accumulator accumulator = new Accumulator();
    LongStream
            .rangeClosed(1, n)
            .parallel()
            .forEach(accumulator::add);
    return accumulator.total;
}

样本数据

打印每次计算和:1356102899
打印每次计算和:1000593908
打印每次计算和:1206185264
打印每次计算和:1258260474
打印每次计算和:1486204215

​ 明显发现每次执行的结果都不一样,原因也很简单,由于多个多线程同时访问累加器,执行total += value,在操作共享变量时,非原子性操作

正确使用并行流

1.确认数据量在使用并行流前,是否达到了并行流比顺序流快的标准线,如果不确定写demo验证,因为并行流在数据量未达到的情况下,可能比顺序流还慢(并行存在在额外的资源消耗)

2.留意操作的对象是否存在装箱、拆箱操作,因为会自动装箱与拆箱与大大降低性能,可以使用java8提供的( IntStream、LongStream、 DoubleStream)来避免这种操作,但凡有可能都应该用这些流

3.要考虑流背后的数据结构是否易于分解。例如, ArrayList的拆分效率比LinkedList高得多,因为前者用不着遍历就可以平均拆分,而后者则必须遍历

可分界性
ArrayList极佳
LinkedList
IntStream.range极佳
Stream.iterate
HashSet
TreeSet

类型的long数字,没有装箱拆箱的开销。
4.LongStream.rangeClosed会生成数字范围, 很容易拆分为独立的小块

并行流中的安全问题

提供一个用于测试的计算类Accumulatoradd方法用于进行累加

public class Accumulator {
    public long total = 0;

    public void add(long value) {
        total += value;
    }
}

使用上述类,进行5次测试,观测一下每次计算的结果是否一致

@Test
public  void testThread(){
    for (int i = 0; i < 5; i++) {
        System.out.println("打印每次计算和:"+sideEffectSum(100000L));
    }
}
public  long sideEffectSum(long n) {
    Accumulator accumulator = new Accumulator();
    LongStream
            .rangeClosed(1, n)
            .parallel()
            .forEach(accumulator::add);
    return accumulator.total;
}

样本数据

打印每次计算和:1356102899
打印每次计算和:1000593908
打印每次计算和:1206185264
打印每次计算和:1258260474
打印每次计算和:1486204215

​ 明显发现每次执行的结果都不一样,原因也很简单,由于多个多线程同时访问累加器,执行total += value,在操作共享变量时,非原子性操作

正确使用并行流

1.确认数据量在使用并行流前,是否达到了并行流比顺序流快的标准线,如果不确定写demo验证,因为并行流在数据量未达到的情况下,可能比顺序流还慢(并行存在在额外的资源消耗)

2.留意操作的对象是否存在装箱、拆箱操作,因为会自动装箱与拆箱与大大降低性能,可以使用java8提供的( IntStream、LongStream、 DoubleStream)来避免这种操作,但凡有可能都应该用这些流

3.要考虑流背后的数据结构是否易于分解。例如, ArrayList的拆分效率比LinkedList高得多,因为前者用不着遍历就可以平均拆分,而后者则必须遍历

可分界性
ArrayList极佳
LinkedList
IntStream.range极佳
Stream.iterate
HashSet
TreeSet

4.在中间、终止操作时要考虑是否存在不安全的线程操作,避免在浪费性能的基础上,产出错误的结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值