集合详解四、Java8新特性详解

一、lambda详解:

1、简介:

 
Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历其他集合操作中,可以极大地优化代码结构。

JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。
 
 

1.1、函数式接口:

 
学习lambda表达式就要先知道函数式接口是什么?

函数式接口(Functional Interfaces):如果一个接口定义个唯一 一个抽象方法,那么这个接口就成为函数式接口。同时,引入了一个新的注解:@FunctionalInterface。可以把他它放在一个接口前,表示这个接口是一个函数式接口。这个注解是非必须的,只要接口是:只包含一个方法的接口,虚拟机会自动判断,不过最好在接口上使用注解 @FunctionalInterface 进行声明。在接口中添加了 @FunctionalInterface 的接口,只允许有一个抽象方法,否则编译器也会报错。

示例:

/**
  * 函数式接口
  */
    @FunctionalInterface
    interface Sum{
        int add(int value);
    }

 
 

1.2、lambda接口的要求:

  • 虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法。**即:结构必须是函数式接口。
  • jdk 8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用。
  • @FunctionalInterface:修饰函数式接口的,要求接口中的抽象方法只有一个。 这个注解往往会和 lambda 表达式一起出现。
  • 只定义一个抽象方法的接口。比如:Comparator和Runnable接口。

Lambda表达式:可以让你的代码更加的简洁。ambda无法单独出现,需要一个函数式接口来盛放,可以说lambda表达式方法体是函数式接口的实现,lambda实例化函数式接口,可以将函数作为方法参数,或者将代码作为数据对待。

 
 

1.3、lambda特点:

“Lambda 表达式”(lambdaexpression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名。我们可以把Lambda表达式理解为简洁地可传递的匿名函数的一种方式:它没有名称,但它有参数列表,函数主体,返回类型,可能还有一个可以抛出的异常列表

  • 匿名——匿名这里的意思是无需写函数名字也就是方法名称。
  • 函数——它也是一个函数,因为Lambda函数不像方法那样属于特定的类。但和方法一样,都会有参数列表,函数主体,返回类型,还可能有可以跑出的异常。
  • 传递——以前java不支持传递方法,现在有了Lambda表达式以后可以把其作为参数传递给方法。
  • 简洁——无需像匿名类那样写很多模板代码。

 
举个例子:jdk7和jdk8中的排序功能:


JAVA7:
Comparator<Apple> byWeight = new Comparator<Apple>() {
            public int compare(Apple a1, Apple a2){
                return a1.getWeight().compareTo(a2.getWeight());
            }
        };

JAVA8:
Comparator<Apple> byWeight =(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

上面描述两个苹果比较重量的例子,这里我们可以看出我们的Lambda表达式的代码更加小并且精巧。

 
 

2、lambda语法:

 

2.1、基本语法:


parameters) -> expression :  自带return,后面跟表达式

(parameters) -> { statements; } :不带return 需要手动设置,后面跟语句

 

2.2、Lambda表达组成:

  • 参数列表 :类似方法中的形参列表,这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明也可不声明而由JVM隐含的推断,当只有一个推断类型时可以省略掉圆括号。
  • 箭头 -->: 用于把参数列表和Lambda分离开。
  • Lambda主体(方法体):可以是表达式也可以是代码块,实现函数式接口中的方法。这个方法体可以有返回值也可以没有返回值;比如:a1.getWeight().compareTo(a2.getWeight())。

 
示例1:


根据上述语法规则,以下哪个不是有效的Lambda表达式?
(1) () -> {}
(2) () -> "Raoul"
(3) () -> {return "Mario";}
(4) (Integer i) -> return "Alan" + i;
(5) (String s) -> {"IronMan";}

答案:只有45是无效的Lambda(1) 这个Lambda没有参数,并返回void。 它类似于主体为空的方法: public void run() {}(2) 这个Lambda没有参数,并返回String作为表达式。
(3) 这个Lambda没有参数,并返回String(利用显式返回语句)。
(4) return是一个控制流语句。要使此Lambda有效,需要使花括号,如下所示:
(Integer i) -> {return "Alan" + i;}(5)Iron Man”是一个表达式,不是一个语句。要使此Lambda有效,你可以去除花括号
和分号,如下所示: (String s) -> "Iron Man"。或者如果你喜欢,可以使用显式返回语
句,如下所示: (String s)->{return "IronMan";}

 
 
示例2:


1.不接受参数,直接返回1
    ()->1
2.接受两个int类型的参数,返回这两个参数的和
    (int x,int y )-> x+y
3.接受x,y两个参数,JVM根据上下文推断参数的类型,返回两个参数的和
    (x,y)->x+y
4.接受一个字符串,打印该字符串,没有返回值
    (String name)->System.out.println(name)
5.接受一个参数,JVM根据上下文推断参数的类型,打印该参数,没有返回值,只有一个参数可以省略圆括号
   name->System.out.prinln(name)
6.接受两个String类型参数,分别输出,没有返回值
    (String name,String sex)->{System.out.println(name);System.out.println(sex)}
7.接受呀一个参数,返回它本身的2倍
    x->2*x
    

 
 

3、lambda应用:

3.1、使用lambda表达式获取list的 交集/ 并集 /差集/ 去重复并集

package com.liupy.collection.list;
import com.liupy.collection.po.Student2;
import java.util.*;
import static java.util.stream.Collectors.toList;

/**
 * 采用java8 lambda表达式 实现java list 交集/并集/差集/去重并集
 * 注意:
 *      1、一般有filter 操作时,不用并行流parallelStream ,如果用的话可能会导致线程安全问题
 */
public class ArrayListTest3 {

    /**
     * 基本数据类型处理
     */
    public static void showSimpleDeal() {
        List<String> list1=new ArrayList();
        list1.add("1111");
        list1.add("2222");
        list1.add("3333");

        List<String> list2=new ArrayList();
        list2.add("3333");
        list2.add("4444");
        list2.add("5555");

        //取两个集合的交集
        List<String> intersection =list1.stream().filter(item -> list2.contains(item)).collect(toList());
        System.out.println("================得到交集 intersection====================");
        intersection.parallelStream().forEach(System.out::println);
        System.out.println("intersection====="+intersection.toString());

        //取两个集合的差集:(list1 - list2)
        List<String> reduce1=list1.stream().filter(item -> !list2.contains(item)).collect(toList());
        System.out.println("================得到差集 reduce1====================");
        reduce1.parallelStream().forEach(System.out::println);
        System.out.println("reduce1====="+reduce1.toString());


        //取两个集合的差集:(list2 - list1)
        List<String> reduce2=list2.stream().filter(item -> !list1.contains(item)).collect(toList());
        System.out.println("================得到差集 reduce2====================");
        reduce2.parallelStream().forEach(System.out::println);
        System.out.println("reduce2====="+reduce2.toString());


        //取两个集合的并集
        List<String> listAll = list1.parallelStream().collect(toList());
        List<String> listAll2 = list2.parallelStream().collect(toList());
        listAll.addAll(listAll2);
        System.out.println("================得到并集 listAll====================");
        listAll.parallelStream().forEach(System.out :: println);
        System.out.println("listAll====="+listAll.toString());


        //去重后的并集
        List<String> listAllDistinct  = listAll.stream().distinct().collect(toList());
        System.out.println("================得到去重并集 listAllDistinct================");
        listAllDistinct.parallelStream().forEach(System.out :: println);
        System.out.println("listAll222====="+listAllDistinct.toString());
    }

    /**
     * Student2对象数据类型处理
     */
    public static void showObjectDeal(){
        List<Student2> list1=new ArrayList<>();
        list1.add(new Student2("name1","1001"));
        list1.add(new Student2("name2","1002"));
        list1.add(new Student2("name3","1003"));



        List<Student2> list2=new ArrayList<>();
        list2.add(new Student2("name3","1003"));
        list2.add(new Student2("name4","1004"));
        System.out.println("原list1的值======"+list1.toString());
        System.out.println("原list2的值======"+list2.toString());

        Set<Student2> list1Set = new HashSet<>(list1);

        Set<Student2> list2Set = new HashSet<>(list2);


        // 交集
        List<Student2> intersection  = list1.stream().filter(list2Set::contains).collect(toList());
        System.out.println("=================得到交集 intersection=================");
        intersection.parallelStream().forEach(System.out::println);


        // 差集 (list1 - list2)
        List<Student2> reduce1 = list1.stream().filter(item -> !list2Set.contains(item)).collect(toList());
        System.out.println("=================得到差集 reduce1 (list1 - list2)=================");
        reduce1.parallelStream().forEach(System.out::println);


        // 差集 (list2 - list1)
        List<Student2> reduce2 = list2.stream().filter(item -> !list1Set.contains(item)).collect(toList());
        System.out.println("=================得到差集 reduce2 (list2 - list1)=================");
        reduce2.parallelStream().forEach(System.out::println);


        // 并集
        List<Student2> listAll = list1.parallelStream().collect(toList());
        List<Student2> listAll2 = list2.parallelStream().collect(toList());
        listAll.addAll(listAll2);
        System.out.println("=================得到并集 listAll=================");
        listAll.parallelStream().forEach(System.out::println);



        // 去重并集
        list1Set.addAll(list2Set);
        List<Student2> listDistinctAll = new ArrayList<>(list1Set);
        System.out.println("=================得到去重并集 listDistinctAll=================");
        listDistinctAll.parallelStream().forEach(System.out::println);

        System.out.println("---原来的List1---");
        list1.parallelStream().forEach(System.out::println);
        System.out.println("---原来的List2---");
        list2.parallelStream().forEach(System.out::println);

    }


    //测试方法
    public static void main(String[] args) {
        //基本类型处理方法
        showSimpleDeal();
        //对象类型处理方法
        showObjectDeal();
    }
}


 
 
 
 

二、stream详解:

 
 

1、概述:

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式
 
 

1.1、什么是流?

Stream不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的Iterator。原始版本的Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的Stream,用户只要给出需要对其包含的元素执行什么操作,比如,“过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream会隐式地在内部进行遍历,做出相应的数据转换。Stream就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。

而和迭代器又不同的是,Stream可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个item读完后再读下一个item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream的并行操作依赖于Java7中引入的Fork/Join框架(JSR166y)来拆分任务和加速处理过程。

Stream 的另外一大特点是,数据源本身可以是无限的。

 
 

1.2、流的构成

当我们使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换 → 执行操作获取想要的结果。每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道,如下图所示:
在这里插入图片描述

 
 

1.3、为什么需要Stream ?

Stream作为Java8的一大亮点,它与java.io包里的InputStream和OutputStream是完全不同的概念。它也不同于StAX对XML解析的Stream,也不是Amazon Kinesis对大数据实时处理的Stream。Java8中的Stream是对容器对象功能的增强,它专注于对容器对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API借助于同样新出现的Lambda表达式,极大的提高编程效率和程序可读性。同时,它提供串行并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用fork/join并行方式来拆分任务和加速处理过程。通常,编写并行代码很难而且容易出错, 但使用Stream API无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。所以说,Java8中首次出现的 java.util.stream是一个函数式语言+多核时代综合影响的产物。

 
 

1.4、什么是聚合操作?

在传统的J2EE应用中,Java代码经常不得不依赖于关系型数据库的聚合操作来完成诸如:

  • 客户每月平均消费金额
  • 本周完成的有效订单(排除了无效的)
  • 最昂贵的在售商品
  • 取十个数据样本作为首页推荐

这类的操作。但在当今这个数据大爆炸的时代,在数据来源多样化、数据海量化的今天,很多时候不得不脱离 RDBMS,或者以底层返回的数据为基础进行更上层的数据统计。而Java的集合API中,仅仅有极少量的辅助型方法,更多的时候是程序员需要用Iterator来遍历集合,完成相关的聚合应用逻辑,这是一种远不够高效、笨拙的方法
 
 
示例一:
在Java7中,如果要发现type为grocery的所有交易,然后返回以交易值降序排序好的交易ID集合,我们需要这样写:

List<Transaction> groceryTransactions = new Arraylist<>();
for(Transaction t: transactions){
 if(t.getType() == Transaction.GROCERY){
 groceryTransactions.add(t);
 }
}

Collections.sort(groceryTransactions, new Comparator(){
 public int compare(Transaction t1, Transaction t2){
 return t2.getValue().compareTo(t1.getValue());
 }
});

List<Integer> transactionIds = new ArrayList<>();
for(Transaction t: groceryTransactions){
 transactionsIds.add(t.getId());
}

而在 Java 8 使用 Stream,代码更加简洁易读;而且使用并发模式,程序执行速度更快。


List<Integer> transactionsIds = transactions.parallelStream()
.filter(t -> t.getType() == Transaction.GROCERY)
.sorted(comparing(Transaction::getValue).reversed())
.map(Transaction::getId).collect(toList());

 
 
示例二:
 
定义一pojo对象


public class Student3 {
    int no;
    String name;
    String sex;
    float height;


    public Student3(int no, String name, String sex, float height) {
        this.no = no;
        this.name = name;
        this.sex = sex;
        this.height = height;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public float getHeight() {
        return height;
    }

    public void setHeight(float height) {
        this.height = height;
    }

    @Override
    public String toString() {
        return "Student3{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", height=" + height +
                '}';
    }
}

现有一个List list里面有5个Studeng对象,假如我们想获取Sex=“G”的Student,并打印出来。如果按照我们原来的处理模式,必然会想到一个for循环就搞定了,而在for循环其实是一个封装了迭代的语法块。在这里,我们采用Iterator进行迭代:


public class StreamTest1 {
    public static void main(String[] args) {
        Student3 stuA = new Student3(1, "A", "M", 184);
        Student3 stuB = new Student3(2, "B", "G", 163);
        Student3 stuC = new Student3(3, "C", "M", 175);
        Student3 stuD = new Student3(4, "D", "G", 158);
        Student3 stuE = new Student3(5, "E", "M", 170);

        List<Student3> list = new ArrayList<>();
        list.add(stuA);
        list.add(stuB);
        list.add(stuC);
        list.add(stuD);
        list.add(stuE);



        //for循环-迭代器方式获取
        Iterator<Student3> iterator = list.iterator();
        while(iterator.hasNext()) {
            Student3 stu = iterator.next();
            if (stu.getSex().equals("G")) {
                System.out.println(stu.toString());
            }
        }
}

整个迭代过程是这样的:首先调用iterator方法,产生一个新的Iterator对象,进而控制整
个迭代过程,这就是外部迭代 迭代过程通过显式调用Iterator对象的hasNext和next方法完成迭代

而在Java 8中,我们可以采用聚合操作:


public class StreamTest1 {
    public static void main(String[] args) {
        Student3 stuA = new Student3(1, "A", "M", 184);
        Student3 stuB = new Student3(2, "B", "G", 163);
        Student3 stuC = new Student3(3, "C", "M", 175);
        Student3 stuD = new Student3(4, "D", "G", 158);
        Student3 stuE = new Student3(5, "E", "M", 170);

        List<Student3> list = new ArrayList<>();
        list.add(stuA);
        list.add(stuB);
        list.add(stuC);
        list.add(stuD);
        list.add(stuE);


        //java8中,可以采用聚合操作:stream
        list.stream()
                .filter(student -> student.getSex().equals("G"))
                .forEach(student ->System.out.println(student.toString()));

    }
}
  • 首先,通过stream方法创建Stream,然后再通过filter方法对源数据进行过滤,最后通过foeEach方法进行迭代。在聚合操作中,与Labda表达式一起使用,显得代码更加的简洁。这里值得注意的是,我们首先是stream方法的调用,其与iterator作用一样的作用一样,该方法不是返回一个控制迭代的 Iterator 对象,而是返回内部迭代中的相应接口: Stream,其一系列的操作都是在操作Stream,直到feach时才会操作结果,这种迭代方式称为内部迭代。
  • 外部迭代和内部迭代(聚合操作)都是对集合的迭代,但是在机制上还是有一定的差异:
    • 迭代器提供next()、hasNext()等方法,开发者可以自行控制对元素的处理,以及处理方式,但是只能顺序处理;
    • stream()方法返回的数据集无next()等方法,开发者无法控制对元素的迭代,迭代方式是系统内部实现的,同时系统内的迭代也不一定是顺序的,还可以并行,如parallelStream()方法。并行的方式在一些情况下,可以大幅提升处理的效率。

 
 

2、分类:

在这里插入图片描述

  • 无状态:指元素的处理不受之前元素的影响;
  • 有状态:指该操作只有拿到所有元素之后才能继续下去。
  • 非短路操作:指必须处理所有元素才能得到最终结果;
  • 短路操作:指遇到某些符合条件的元素就可以得到最终结果,如 A || B,只要A为true,则无需判断B的结果。

 
 

3、Stream的操作步骤:

 
Stream有如下三个操作步骤:

  • 一、创建Stream:从一个数据源,如集合、数组中获取流。
  • 二、中间操作:一个操作的中间链,对数据源的数据进行操作。
  • 三、终止操作:一个终止操作,执行中间操作链,并产生结果。

 
假设有一个Person类和一个Person列表,现在有两个需求:
1)找到年龄大于18岁的人并输出;
2)找出所有中国人的数量。

List<Person> personList = new ArrayList<>();
 personList.add(new Person("欧阳雪",18,"中国",'F'));
 personList.add(new Person("Tom",24,"美国",'M'));
 personList.add(new Person("Harley",22,"英国",'F'));
 personList.add(new Person("向天笑",20,"中国",'M'));
 personList.add(new Person("李康",22,"中国",'M'));
 personList.add(new Person("小梅",20,"中国",'F'));
 personList.add(new Person("何雪",21,"中国",'F'));
 personList.add(new Person("李康",22,"中国",'M'));
 

在JDK8以前,我们可以通过遍历列表来完成。但是在有了Stream API后,可以这样来实现:

 public static void main(String[] args) {
  
      // 1)找到年龄大于18岁的人并输出;
      personList.stream().filter((p) -> p.getAge() > 18).forEach(System.out::println);
  
      System.out.println("-------------------------------------------");
  
      // 2)找出所有中国人的数量
      long chinaPersonNum = personList.stream().filter((p) -> p.getCountry().equals("中国")).count();
     System.out.println("中国人有:" + chinaPersonNum + "个");
 
}

在这个例子中,personList.stream()是创建流,filter()属于中间操作,forEach、count()是终止操作。

 
 

4、Stream的操作详解:

 

4.1、创建Stream流的方式:

  1. 使用Collection下的 stream() 和 parallelStream() 方法(常用):

		List<String> list=new ArrayList<>();
        //获取一个顺序流:单线程
        Stream<String> stream  = list.stream();
        //获取一个并行流:多线程
        Stream<String> parallelStream = list.parallelStream();
        
  1. 使用Arrays 中的 stream() 方法,将数组转成流:

		Integer[] nums = new Integer[10];
        String[] str = {"1","2","3"};
        Stream<Integer> stream1 = Arrays.stream(nums);
        Stream<String> stream2 = Arrays.stream(str);
        
  1. 使用Stream中的静态方法:of()、iterate()、generate():

		Stream<Integer> stream3 = Stream.of(1,2,3,4,5,6);
        Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2).limit(6);
        stream2.forEach(System.out::println); // 0 2 4 6 8 10
        Stream<Double> stream5 = Stream.generate(Math::random).limit(2);
        
  1. 使用 BufferedReader.lines() 方法,将每行内容转成流:
 		
 		BufferedReader reader = new BufferedReader(new FileReader("F:\\test_stream.txt"));
            Stream<String> lineStream = reader.lines();
            lineStream.forEach(System.out::println);
            
  1. 使用 Pattern.splitAsStream() 方法,将字符串分隔成流:

		Pattern pattern = Pattern.compile(",");
        Stream<String> stringStream = pattern.splitAsStream("a,b,c,d");
        stringStream.forEach(System.out::println);
        

 

4.2、流的中间操作:

 

1、筛选与切片:
  • filter:过滤流中的某些元素;
  • limit(n):获取n个元素;从前面获取;
  • skip(n):skip方法将过滤掉原Stream中的前N个元素,返回剩下的元素所组成的新Stream。如果原Stream的元素个数大于N,将返回原Stream的后(原Stream长度-N)个元素所组成的新Stream;如果原Stream的元素个数小于或等于N,将返回一个空Stream。配合limit(n)可实现分页;
  • distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素。
/**
     * 中间操作
     * @param
     */
    public static void StreamHandle(){
        Stream<Integer> stream = Stream.of(6, 4, 6, 7, 3, 9, 8, 10, 12, 14, 14);
        Stream<Integer> newStream = stream.filter(s -> s > 5)
                                          .distinct()
                                          .skip(2)
                                          .limit(2);

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

 
limit举例:
需求:从Person列表中取出两个女性。


personList.stream().filter((p) -> p.getSex() == 'F').limit(2).forEach(System.out::println);

 
 
skip举例

需求:从Person列表中从第2个女性开始,取出所有的女性。


personList.stream().filter((p) -> p.getSex() == 'F').skip(1).forEach(System.out::println);

 
 

2、映射:
  • map:接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
  • flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

 
映射-map

		
		/**
         * 映射-map
         */
         
		//需求1:对集合去掉逗号
        List<String> list = Arrays.asList("a", "b", "c", "1", "2", "3");
        //将每个元素转成一个新的且不带逗号的元素
        Stream<String> newList = list.stream().map(s -> s.replaceAll(",", ""));
        newList.forEach(System.out::println);
        

        //需求2:我们用一个PersonCountry类来接收所有的国家信息:
		@Data
 		public class PersonCountry {
     		private String country;
 		}
 		
		personList.stream().map((p) -> {
         	PersonCountry personName = new PersonCountry();
         	personName.setCountry(p.getCountry());
        	return personName;
 		}).distinct().forEach(System.out::println);

 
需求:假如有一个字符列表,需要提出每一个字符:

	
	List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","ddd");
 	final Stream<Stream<Character>> streamStream= list.stream().map(TestStreamAPI::getCharacterByString);
 	streamStream.forEach(System.out::println);
 	
运行结果为:

java.util.stream.ReferencePipeline$Head@3f91beef
java.util.stream.ReferencePipeline$Head@1a6c5a9e
java.util.stream.ReferencePipeline$Head@37bba400
java.util.stream.ReferencePipeline$Head@179d3b25
java.util.stream.ReferencePipeline$Head@254989ff

从输出结果及返回结果类型(Stream<Stream>)可以看出这是一个流中流,要想打印出我们想要的结果,需要对流中的每个流进行打印:

 streamStream.forEach(sm -> sm.forEach(System.out::print));

运行结果::aaabbbcccdddddd

但我们希望的是返回的是一个流,而不是一个包含了多个流的流,而flatMap可以帮助我们做到这一点。
那么我们可以采用flatMap,如下:

 
映射-flatMap

		/**
         * 映射-flatMap
         */
        Stream<String> stream3 = list.stream().flatMap(s -> {
            //将每个元素转换成一个stream
            String[] split = s.split(",");
            Stream<String> stream2 = Arrays.stream(split);
            return stream2;
        });
        stream3.forEach(System.out::println);
        

 
 
map和flatMap的图解

map图解:
在这里插入图片描述
map在接收到流后,直接将Stream放入到一个Stream中,最终整体返回一个包含了多个Stream的Stream。

flatMap图解:
在这里插入图片描述
flatMap在接收到Stream后,会将接收到的Stream中的每个元素取出来放入一个Stream中,最后将一个包含多个元素的Stream返回。
 
 

3、排序:
  • sorted():自然排序,流中元素需实现Comparable接口;
  • sorted(Comparator com):定制排序,自定义Comparator排序器 。

	private static void test3(){
        List<String> list = Arrays.asList("aa", "ff", "dd");
        //方式一:String 类自身已实现Compareable接口
        Stream<String> s1 = list.stream().sorted();
        s1.forEach(System.out::println);
        System.out.println("===============================");

        //方式二:自定义排序:先按姓名升序,姓名相同则按年龄升序
        Student4 stu1 = new Student4("aa", 10);
        Student4 stu2 = new Student4("bb", 20);
        Student4 stu3 = new Student4("aa", 30);
        Student4 stu4 = new Student4("dd", 40);

        List<Student4> studentList = Arrays.asList(stu1, stu2, stu3, stu4);
        Stream<Student4> stream = studentList.stream().sorted(
                (o1, o2) -> {
                    if (o1.getName().equals(o2.getName())) {
                        return o1.getAge() - o2.getAge();
                    } else {
                        return o1.getName().compareTo(o2.getName());
                    }
                }
        );

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

 
 

4、消费:

peek:如同于map,能得到流中的每一个元素。但map接收的是一个Function表达式,有返回值;而peek接收的是Consumer表达式,没有返回值。


	private static void test4(){
        Student4 s1 = new Student4("aa", 10);
        Student4 s2 = new Student4("bb", 20);
        List<Student4> list = Arrays.asList(s1, s2);

        Stream<Student4> stream = list.stream().peek(o -> o.setAge(100));
        stream.forEach(System.out::println);
    }
    

 
 

4.3、流的终止操作:

 

1、匹配、聚合(查找)操作:
  • allMatch:allMatch操作用于判断Stream中的元素是否全部满足指定条件。如果全部满足条件返回true,否则返回false。
  • anyMatch:anyMatch操作用于判断Stream中的是否有满足指定条件的元素。如果最少有一个满足条件返回true,否则返回false。
  • noneMatch:noneMatch方法将判断Stream中的所有元素是否满足指定的条件,如果所有元素都不满足条件,返回true;否则,返回false.
  • findFirst:返回流中第一个元素。
  • findAny:返回流中的任意元素
  • count:count方法将返回Stream中元素的个数。
  • max:返回流中元素最大值;
  • min:返回流中元素最小值。

	public static void overHandle(){
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

        boolean allMatch = list.stream().allMatch(a -> a > 2);
        System.out.println("allMatch=============="+allMatch);
        boolean anyMatch = list.stream().anyMatch(b -> b > 2);
        System.out.println("anyMatch=============="+anyMatch);
        boolean noneMatch = list.stream().noneMatch(c -> c > 5);
        System.out.println("noneMatch=============="+noneMatch);

        Integer findFirst = list.stream().findFirst().get();
        System.out.println("findFirst=============="+findFirst);

        Integer findAny = list.stream().findAny().get();
        System.out.println("findAny=============="+findAny);

        long count = list.stream().count();
        System.out.println("count=============="+count);

        Integer max = list.stream().max(Integer::compareTo).get();
        System.out.println("max=============="+max);

        Integer min = list.stream().min(Integer::compareTo).get();
        System.out.println("min=============="+min);
    }
    

 
 

2、规约:

reduc可以将流中元素反复结合起来,得到一个值;
reduc()方法有三种表现形式,如下:

  • Optional reduce(BinaryOperator accumulator):
    第一次执行时,accumulator函数的第一个参数为流中的第一个元素,第二个参数为流中元素的第二个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第三个元素;依次类推。
  • T reduce(T identity, BinaryOperator accumulator):
    流程跟上面一样,只是第一次执行时,accumulator函数的第一个参数为identity,而第二个参数为流中的第一个元素。
  • < U > U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator< U > combiner):
    在串行流(stream)中,该方法跟第二个方法一样,即第三个参数combiner不会起作用。在并行流(parallelStream)中,我们知道流被fork join出多个线程进行执行,此时每个线程的执行流程就跟第二个方法reduce(identity,accumulator)一样,而第三个参数combiner函数,则是将每个线程的执行结果当成一个新的流,然后使用第一个方法reduce(accumulator)流程进行规约。

 
示例1

需求: 求一个1到100的和

/**
     * 求一个1到100的和
     */
    private static void test6(){
        List<Integer> list = new ArrayList<>(100);
        for (int i = 0; i <= 100; i++) {
            list.add(i);
        }
        System.out.println("list================"+list.toString());

        final Integer reduce = list.stream().reduce(0, (x, y) -> x + y);
        System.out.println("和为======="+reduce);
    }
    

结果为:5050

这个例子用到了reduce第二个方法:T reduce(T identity, BinaryOperator accumulator)
把这个动作拆解一下,其运算步骤模拟如下:

    0  (1,2) -> 1 + 2 + 0
    3  (3,4) -> 3 + 4 + 3
    10 (5,6) -> 5 + 6 + 10
    .
    .
    .

其运算步骤是,每次将列表的两个元素相加,并将结果与前一次的两个元素的相加结果进行累加,因此,在开始时,将identity设为0,因为第1个元素和第2个元素在相加的时候,前面还没有元素操作过。
 
 
示例2

需求: 求所有人的年龄之和

/**
     * 求一个1到100的和
     */
    private static void test7(){
        Student4 s1 = new Student4("张三", 20);
        Student4 s2 = new Student4("李四", 30);
        Student4 s3 = new Student4("王五", 48);

        List<Student4> list = Arrays.asList(s1, s2, s3);
        Optional<Integer> reduce = list.stream().map(Student4::getAge).reduce(Integer::sum);
        System.out.println("所有年龄之和=============="+reduce);
    }
    

 
 
示例3

经过测试,当元素个数小于24时,并行时线程数等于元素个数,当大于等于24时,并行时线程数为16,如下:

//经过测试,当元素个数小于24时,并行时线程数等于元素个数,当大于等于24时,并行时线程数为16
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24);
 
 //求所有元素之和,第一次执行,第一个元素为0
Integer v = list.stream().reduce((x1, x2) -> x1 + x2).get();
System.out.println(v);   // 300
 
 //求所有元素之和,第一次执行,第一个元素为10
Integer v1 = list.stream().reduce(10, (x1, x2) -> x1 + x2);
System.out.println(v1);  //310
 
 //求所有元素之和,第一次执行,第一个元素为0
Integer v2 = list.stream().reduce(0,
        (x1, x2) -> {
            System.out.println("stream accumulator: x1:" + x1 + "  x2:" + x2);
            return x1 - x2;
        },
        (x1, x2) -> {
            System.out.println("stream combiner: x1:" + x1 + "  x2:" + x2);
            return x1 * x2;
        });
		System.out.println(v2); // -300
 
Integer v3 = list.parallelStream().reduce(0,
        (x1, x2) -> {
            System.out.println("parallelStream accumulator: x1:" + x1 + "  x2:" + x2);
            return x1 - x2;
        },
        (x1, x2) -> {
            System.out.println("parallelStream combiner: x1:" + x1 + "  x2:" + x2);
            return x1 * x2;
        });
		System.out.println(v3); //197474048

 
 

3、收集:
  • collect:接收一个Collector实例,将流中元素收集成另外一个数据结构。
  • collect:将流转换为其他形式,接收一个Collector接口实现 ,用于给Stream中汇总的方法。
  • collect不光可以将流转换成其他集合等形式,还可以进行归约等操作,具体实现也很简单,主要是与Collectors类搭配使用。
     
     

1、Collector 工具库:Collectors:


Student s1 = new Student("aa", 10,1);
Student s2 = new Student("bb", 20,2);
Student s3 = new Student("cc", 10,3);
List<Student> list = Arrays.asList(s1, s2, s3);
 
//装成list
List<Integer> ageList = list.stream().map(Student::getAge).collect(Collectors.toList()); // [10, 20, 10]
 
//转成set
Set<Integer> ageSet = list.stream().map(Student::getAge).collect(Collectors.toSet()); // [20, 10]
 
//转成map,注:key不能相同,否则报错
Map<String, Integer> studentMap = list.stream().collect(Collectors.toMap(Student::getName, Student::getAge)); // {cc=10, bb=20, aa=10}
 
//字符串分隔符连接
String joinName = list.stream().map(Student::getName).collect(Collectors.joining(",", "(", ")")); // (aa,bb,cc)
 
//聚合操作
//1.学生总数
Long count = list.stream().collect(Collectors.counting()); // 3
//2.最大年龄 (最小的minBy同理)
Integer maxAge = list.stream().map(Student::getAge).collect(Collectors.maxBy(Integer::compare)).get(); // 20
//3.所有人的年龄
Integer sumAge = list.stream().collect(Collectors.summingInt(Student::getAge)); // 40
//4.平均年龄
Double averageAge = list.stream().collect(Collectors.averagingDouble(Student::getAge)); // 13.333333333333334
// 带上以上所有方法
DoubleSummaryStatistics statistics = list.stream().collect(Collectors.summarizingDouble(Student::getAge));
System.out.println("count:" + statistics.getCount() + ",max:" + statistics.getMax() + ",sum:" + statistics.getSum() + ",average:" + statistics.getAverage());
 
//分组
Map<Integer, List<Student>> ageMap = list.stream().collect(Collectors.groupingBy(Student::getAge));
//多重分组,先根据类型分再根据年龄分
Map<Integer, Map<Integer, List<Student>>> typeAgeMap = list.stream().collect(Collectors.groupingBy(Student::getType, Collectors.groupingBy(Student::getAge)));
 
//分区
//分成两部分,一部分大于10岁,一部分小于等于10岁
Map<Boolean, List<Student>> partMap = list.stream().collect(Collectors.partitioningBy(v -> v.getAge() > 10));
 
//规约
Integer allAge = list.stream().map(Student::getAge).collect(Collectors.reducing(Integer::sum)).get(); //40


 
 
2、Collectors.toList() 解析:

//toList 源码
public static <T> Collector<T, ?, List<T>> toList() {
    return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
            (left, right) -> {
                left.addAll(right);
                return left;
            }, CH_ID);
}
 
//为了更好地理解,我们转化一下源码中的lambda表达式
public <T> Collector<T, ?, List<T>> toList() {
    Supplier<List<T>> supplier = () -> new ArrayList();
    BiConsumer<List<T>, T> accumulator = (list, t) -> list.add(t);
    BinaryOperator<List<T>> combiner = (list1, list2) -> {
        list1.addAll(list2);
        return list1;
    };
    Function<List<T>, List<T>> finisher = (list) -> list;
    Set<Collector.Characteristics> characteristics = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
 
    return new Collector<T, List<T>, List<T>>() {
        @Override
        public Supplier supplier() {
            return supplier;
        }
 
        @Override
        public BiConsumer accumulator() {
            return accumulator;
        }
 
        @Override
        public BinaryOperator combiner() {
            return combiner;
        }
 
        @Override
        public Function finisher() {
            return finisher;
        }
 
        @Override
        public Set<Characteristics> characteristics() {
            return characteristics;
        }
    };
 
}

 
 
3、完整测试代码

   import lombok.Data;
   import java.util.ArrayList;
   import java.util.Arrays;
   import java.util.List;
   import java.util.Optional;
   import java.util.stream.Collectors;
   import java.util.stream.Stream;
   
   public class TestStreamAPI {
  
      public static void main(String[] args) {
          List<Person> personList = new ArrayList<>();
          personList.add(new Person("欧阳雪",18,"中国",'F'));
          personList.add(new Person("Tom",24,"美国",'M'));
          personList.add(new Person("Harley",22,"英国",'F'));
          personList.add(new Person("向天笑",20,"中国",'M'));
          personList.add(new Person("李康",22,"中国",'M'));
          personList.add(new Person("小梅",20,"中国",'F'));
          personList.add(new Person("何雪",21,"中国",'F'));
          personList.add(new Person("李康",22,"中国",'M'));
  
          // 1)找到年龄大于18岁的人并输出;
          personList.stream().filter((p) -> p.getAge() > 18).forEach(System.out::println);
  
          System.out.println("-------------------------------------------");
  
          // 2)找出所有中国人的数量
          long chinaPersonNum = personList.stream().filter((p) -> p.getCountry().equals("中国")).count();
          System.out.println("中国人有:" + chinaPersonNum);
  
          // limit
          personList.stream().filter((p) -> p.getSex() == 'F').limit(2).forEach(System.out::println);
          System.out.println();
          // skip
          personList.stream().filter((p) -> p.getSex() == 'F').skip(1).forEach(System.out::println);
  
          // distinct
          personList.stream().filter((p) -> p.getSex() == 'M').distinct().forEach(System.out::println);
  
          // map
          personList.stream().map((p) -> {
              PersonCountry personName = new PersonCountry();
              personName.setCountry(p.getCountry());
              return personName;
          }).distinct().forEach(System.out::println);
  
          // map2
          List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","ddd");
  
          final Stream<Stream<Character>> streamStream
                  = list.stream().map(TestStreamAPI::getCharacterByString);
  //        streamStream.forEach(System.out::println);
          streamStream.forEach(sm -> sm.forEach(System.out::print));
  
          // flatMap
          final Stream<Character> characterStream = list.stream().flatMap(TestStreamAPI::getCharacterByString);
          characterStream.forEach(System.out::print);
  
          // sort
          final Stream<Person> sorted = personList.stream().sorted((p1, p2) -> {
  
              if (p1.getAge().equals(p2.getAge())) {
                  return p1.getName().compareTo(p2.getName());
              } else {
                  return p1.getAge().compareTo(p2.getAge());
              }
          });
          sorted.forEach(System.out::println);
  
          // allMatch
          final Stream<Person> stream = personList.stream();
          final boolean adult = stream.allMatch(p -> p.getAge() >= 18);
          System.out.println("是否都是成年人:" + adult);
  
          final boolean chinaese = personList.stream().allMatch(p -> p.getCountry().equals("中国"));
          System.out.println("是否都是中国人:" + chinaese);
  
          // max min
          final Optional<Person> maxAge = personList.stream().max((p1, p2) -> p1.getAge().compareTo(p2.getAge()));
          System.out.println("年龄最大的人信息:" + maxAge.get());
  
          final Optional<Person> minAge = personList.stream().min((p1, p2) -> p1.getAge().compareTo(p2.getAge()));
          System.out.println("年龄最小的人信息:" + minAge.get());
  
          // reduce
          List<Integer> integerList = new ArrayList<>(100);
          for(int i = 1;i <= 100;i++) {
              integerList.add(i);
          }
          final Integer reduce = integerList.stream().reduce(0, (x, y) -> x + y);
          System.out.println("结果为:" + reduce);
  
          final Optional<Integer> totalAge = personList.stream().map(Person::getAge).reduce(Integer::sum);
          System.out.println("年龄总和:" + totalAge);
 
         // collect
          final List<String> collect = personList.stream().map(p -> p.getCountry()).distinct().collect(Collectors.toList());
          System.out.println(collect);
 
          final Double collect1 = personList.stream().collect(Collectors.averagingInt(p -> p.getAge()));
          System.out.println("平均年龄为:" + collect1);
 
          final Optional<Integer> maxAge2 = personList.stream().map(Person::getAge).collect(Collectors.maxBy(Integer::compareTo));
          System.out.println(maxAge2.get());
  
         try(final Stream<Integer> integerStream = personList.stream().map(Person::getAge)) {
             final Optional<Integer> minAge2 = integerStream.collect(Collectors.minBy(Integer::compareTo));
            System.out.println(minAge2.get());
         }
     }
 
     public static Stream<Character> getCharacterByString(String str) {
 
         List<Character> characterList = new ArrayList<>();
 
         for (Character character : str.toCharArray()) {
             characterList.add(character);
         }
 
         return characterList.stream();
     }
 }
 
 @Data
 class PersonCountry {
    private String country;
 }
 
 @Data
 class Person {
     private String name;
     private Integer age;
     private String country;
     private char sex;
 
     public Person(String name, Integer age, String country, char sex) {
         this.name = name;
         this.age = age;
         this.country = country;
         this.sex = sex;
     }
 }

 
 

三、stream应用场景:

1、场景一:采用stream().filter(),删除List中的元素

查询数据库,返回票据列表,需要将返回的列表中的数据,剔除不符合要求的元素。

 public static void main(String[] args) {
        List<BillInfo> list = new ArrayList<>();
        BillInfo b = new BillInfo();
        b.setCdNo("123456789");
        b.setCdCurStart("11");
        b.setCdCurEnd("55");

        BillInfo b2 = new BillInfo();
        b2.setCdNo("123456780");
        b2.setCdCurStart("22");
        b2.setCdCurEnd("66");

        BillInfo b3 = new BillInfo();
        b3.setCdNo("123456711");
        b3.setCdCurStart("33");
        b3.setCdCurEnd("77");

        list.add(b);
        list.add(b2);
        list.add(b3);
        System.out.println("list1========"+list.toString());

        /**
         * 剔除票据开始区间 > 11,结束区间 > 66的数据。
         */
        List<BillInfo> list2 = list.stream()
                .filter(bill -> (new Long(bill.getCdCurStart()) > new Long("11")))
                .filter(bill -> (new Long(bill.getCdCurEnd()) > new Long("66")))
                .collect(Collectors.toList());

        System.out.println("list2========"+list2.toString());

    }

 
 
 
 

致谢:

参考:https://blog.csdn.net/qq_36561697/article/details/80847812
参考:https://blog.csdn.net/gzt19881123/article/details/78327465
参考:https://blog.csdn.net/justloveyou_/article/details/79562574
参考:https://blog.csdn.net/y_k_y/article/details/84633001

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值