1.什么是 Stream

流( Stream)到底是什么呢?
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列
“集合讲的是数据,流讲的是计算!”

Stream的操作三个步骤

  • 创建操作

一个数据源(如: 集合, 数组), 获取一个流

  • 中间操作

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

  • 终止操作(终端操作)

一个终止操作, 执行中间操作链, 并产生结果
image.png

注意:
① Stream自己不会存储元素。
② Stream不会改变源对象。相反,他们会返回一个持有结果的面stam
③ Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。


2.创建流

  1. public class TestStreamAPI1 {
  2. public void test1(){
  3. //1.可以通过Collection 系列集合提供的stream() 或parallelStream()
  4. ArrayList<String> list = new ArrayList<>();
  5. Stream<String> stream1 = list.stream();
  6. //2.通过 Arrays中的静态方法 stream()获取数组流
  7. Employee[] employees = new Employee[10];
  8. List<Employee[]> stream2 = Arrays.stream(employees);
  9. //3. 通过Stream 类中的静态方法of()
  10. Stream<String> stream3 = Stream.of("aa", "bb", "cc");
  11. //4.创建无限流
  12. //迭代
  13. Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
  14. stream4.forEach(System.out::println);
  15. //生成
  16. Stream.generate(() -> Math.random())
  17. .limit(5)
  18. .forEach(System.out::println);
  19. }
  20. }

2.1 通过Collection 系列集合提供的stream() 或parallelStream()

首先,java.util.Collection接口中加入了default方法stream用来获取流,所以其所有实现类均可获取流

  1. default Stream<E> stream() {
  2. return StreamSupport.stream(spliterator(), false);
  3. }

java.util.Collection<E>新添加了两个默认方法

  • default Stream stream() : 返回串行流
  • default Stream parallelStream() : 返回并行流

可以发现,stream()和parallelStream()方法返回的都是java.util.stream.Stream<E>类型的对象,说明它们在功能的使用上是没差别的。唯一的差别就是单线程和多线程的执行

并行流的问题

发现一个查询返回一会是3,一会是4

  1. for (int i = 0; i < 100; i++) {
  2. List<String> list1 = new ArrayList<>();
  3. List<String> list2 = new ArrayList<>();
  4. list1.add("a");
  5. list1.add("b");
  6. list1.add("c");
  7. list1.add("d");
  8. list1.parallelStream().forEach(list -> list2.add(list));
  9. System.out.println(list2.size());
  10. }

循环100次,会出现3,分析原因:ArrayList是线程不安全的,在并行流时,会出现并发问题。
所以项目中不要动不动就用ArrayList,在高并发的情况下可能会有问题

Arraylist本身底层是一个数组,多线程并发下线程并不安全,操作出现的原因无非就是多个线程赋值可能同时操作同一个地址,后赋值的把先赋值的给覆盖掉了,才会出现这种问题**

2.2. 通过 Arrays中的静态方法 stream()获取数组流

  1. //2.通过 Arrays中的静态方法 stream()获取数组流
  2. Employee[] employees = new Employee[10];
  3. List<Employee[]> stream2 = Arrays.stream(employees);

2.3. 通过Stream 类中的静态方法of()

  1. //3. 通过Stream 类中的静态方法of()
  2. Stream<String> stream3 = Stream.of("aa", "bb", "cc");

2.4. 创建无限流

  1. //4.创建无限流
  2. //迭代
  3. Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
  4. stream4.forEach(System.out::println);
  5. //生成
  6. Stream.generate(() -> Math.random())
  7. .limit(5)
  8. .forEach(System.out::println);

2.5 组合concat

两个流合并为一个流,与java.lang.String中的concat方法不同

  1. public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

3.操作流

多个中间操作可以连接起来形成一个流水线, 除非流水线上触发终止操作, 否则中间操作不会执行任何的处理!
而在终止操作时一次性全部处理, 称为”惰性求值”

操作分类

a.筛选与切片**

filter

—接收Lambda,从流中排除某些操作;

limit

—截断流,使其元素不超过给定对象

skip

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

distinct

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

举个简单的例子:
假设有一个Person类和一个Person列表,现在有个需求:
1)找到年龄大于18岁的人并输出;

  1. @Data
  2. class Person {
  3. private String name;
  4. private Integer age;
  5. private String country;
  6. private char sex;
  7. public Person(String name, Integer age, String country, char sex) {
  8. this.name = name;
  9. this.age = age;
  10. this.country = country;
  11. this.sex = sex;
  12. }
  13. }
  1. List<Person> personList = new ArrayList<>();
  2. personList.add(new Person("欧阳雪",18,"中国",'F'));
  3. personList.add(new Person("Tom",24,"美国",'M'));
  4. personList.add(new Person("Harley",22,"英国",'F'));
  5. personList.add(new Person("向天笑",20,"中国",'M'));
  6. personList.add(new Person("李康",22,"中国",'M'));
  7. personList.add(new Person("小梅",20,"中国",'F'));
  8. personList.add(new Person("何雪",21,"中国",'F'));
  9. personList.add(new Person("李康",22,"中国",'M'));
  1. public static void main(String[] args) {
  2. //1.filter,找到年龄大于18岁的人并输出;
  3. personList.stream()
  4. .filter((p) -> p.getAge() > 18)
  5. .forEach(System.out::println);
  6. //2.limit,只取前两个
  7. personList.stream()
  8. .filter((p) -> p.getSex() == 'F')
  9. .limit(2)
  10. .forEach(System.out::println);
  11. //3.skip跳过第一个,从第2个女性开始,取出所有女性
  12. personList.stream()
  13. .filter((p) -> p.getSex() == 'F')
  14. .skip(1)
  15. .forEach(System.out::println);
  16. //4.distinct, 取出所有男性,并取出重复的人
  17. personList.stream()
  18. .filter((p) -> p.getSex() == 'M')
  19. .distinct()
  20. .forEach(System.out::println);
  21. }

b.映射

map

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

例1:比如,我们用一个PersonCountry类来接收所有的国家信息:

  1. @Data
  2. class PersonCountry {
  3. private String country;
  4. }
  5. personList.stream().map((p) -> {
  6. PersonCountry personName = new PersonCountry();
  7. personName.setCountry(p.getCountry());
  8. return personName;
  9. }).distinct().forEach(System.out::println);
  10. //---------
  11. //输出结果为:
  12. //PersonName(country=中国)
  13. //PersonName(country=美国)
  14. //PersonName(country=英国)

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

//根据字符串获取字符方法:
public static Stream<Character> getCharacterByString(String str) {

    List<Character> characterList = new ArrayList<>();

    for (Character character : str.toCharArray()) {
        characterList.add(character);
    }

    return characterList.stream();
}


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>)可以看出这是一个流中流,
要想打印出我们想要的结果,需要对流中的每个流进行打印:

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

运行结果为:
aaabbbcccdddddd

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

flatMap

—接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

改写上面的方法,将map改成flatMap:

final Stream<Character> characterStream = list.stream()
    .flatMap(TestStreamAPI::getCharacterByString);

characterStream.forEach(System.out::print);

//运行结果为:
//aaabbbcccdddddd

使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。
所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流
1.png

一言以蔽之,flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接 起来成为一个流。
2.png

c.排序

sorted()

—自然排序(Comparable)

**

sorted(Comparator com)

—定制排序(Comparator)

**
自然排序比较好理解,这里只讲一下定制排序,
对前面的personList按年龄从小到大排序,年龄相同,则再按姓名排序:

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

4.终止流

a.查找&匹配

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

anyMatch

—检查是否有匹配至少一个元素

final boolean chinaese = personList.stream()
            .anyMatch(p -> p.getCountry().equals("中国"));
        System.out.println("是否有中国人:" + chinaese);

noneMatch

—检查是否没有匹配的元素

final boolean chinaese = personList.stream()
            .noneMatch(p -> p.getCountry().equals("中国"));
        System.out.println("没有中国人?:" + chinaese);

findFirst

—返回第一个元素

Optional<Person> first = personList.stream().findFirst();

findAny

—返回当前流中的任意一元素

Optional<Person> first = personList.stream().findAny();

count

—返回流中元素的总数

List<Integer> list = Arrays.asList(1,3,2,6,8,3,9);
long count = list.stream().count();
System.out.println(count);

-----------------输出----------------
7

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

forEach

—遍历流中的元素

 List<Integer> list = Arrays.asList(1,3,2,6,8,3,9);
 list.stream().forEach(System.out::println);

b.归约

reduce(BinaryOpreator)

—-可以将流中元素反复结合起来,返回Optional< T >

第一次执行时,accumulator函数的第一个参数为流中的第一个元素,第二个参数为流中元素的第二个元素;
第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第三个元素;依次类推

 List<Integer> list = Arrays.asList(1,3,2,6,8,3,9);
 Optional<Integer> reduce = list.stream().reduce((x, y) -> x + y);
 System.out.println(reduce.get());
  -----------------输出----------------
 32

reduce(T identity,BinaryOperator)

—可以将流中元素反复结合起来得到一个值,返回T

流程跟上面一样,只是第一次执行时,accumulator函数的第一个参数为identity,
而第二个参数为流中的第一个元素

 List<Integer> list = Arrays.asList(1,3,2,6,8,3,9);
 Integer reduce = list.stream().reduce(0, (x, y) -> x + y);
 System.out.println(reduce);
 -----------------输出----------------
 32

c.收集

collect

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

Colloector 接口中方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map中)
但是Collectots实用类提供了很多静态方法,可以方便的创建常见收集器实例

接下来进行详细介绍
首先创建一个实体类

public class User {
    private String name;
    private Integer age;
    private double salary;

    public User(String name, Integer age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Double.compare(user.salary, salary) == 0 &&
                Objects.equals(name, user.name) &&
                Objects.equals(age, user.age);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, salary);
    }
}

在测试类中准备好数据

public class StreamTest {
     List<User> user = Arrays.asList(new User("张三",12,1000.00),
                                    new User ("李四",32,4000),
                                    new User ("王五",40,4000),
                                    new User ("王五",40,4000));
}
  1. 根据名称生成一个新的List
    List<String> list = user.stream().map(User::getName).collect(Collectors.toList());
    list.forEach(System.out::println);
    -----------------输出--------------
    张三
    李四
    王五
    王五
    

2.根据名称生成一个新的Set

 Set<String> set = user.stream().map(User::getName).collect(Collectors.toSet());
 set.forEach(System.out::println);
 -----------------输出--------------
 张三
 李四
 王五
  1. 根据名称生成一个新的HashSet

    HashSet<String> hashSet = user.stream()
      .map(User::getName)
      .collect(Collectors.toCollection(HashSet::new));
    
    hashSet.forEach(System.out::println);
    -----------------输出--------------
    李四
    张三
    王五
    

4.获取流中的元素总数

Long count = user.stream().collect(Collectors.counting());
System.out.println(count);
-----------------输出--------------
4

5.根据工资获取平均值

Double avg = user.stream()
    .collect(Collectors.averagingDouble(User::getSalary));
System.out.println(avg);
-----------------输出--------------
3250.0

6.根据工资获取总和

Double sum = user.stream().collect(Collectors.summingDouble(User::getSalary));
System.out.println(sum);
-----------------输出--------------
13000.0
  1. 根据工资获取组函数

    DoubleSummaryStatistics sum = user.stream()
     .collect(Collectors.summarizingDouble(User::getSalary));
    System.out.println(sum);
    -----------------输出--------------
    DoubleSummaryStatistics{count=4, sum=13000.000000, min=1000.000000, average=3250.000000, max=4000.000000}
    
  2. 根据工资获取最大值

    Optional<User> max = user.stream()
      .collect(Collectors.maxBy(Comparator.comparingDouble(User::getSalary)));
    System.out.println(max.get());
    -----------------输出--------------
    User{name='李四', age=32, salary=4000.0}
    

9.根据工资获取最小值

 Optional<User> min = user.stream()
     .collect(Collectors.minBy(Comparator.comparingDouble(User::getSalary)));
 System.out.println(min.get());
 -----------------输出--------------
 User{name='张三', age=12, salary=1000.0}

10.分组

//按薪水分组
Map<Double, List<User>> map = user.stream()
    .collect(Collectors.groupingBy(User::getSalary));
System.out.println(map);
-----------------输出--------------
{4000.0=[User{name='李四', age=32, salary=4000.0}, 
         User{name='王五', age=40, salary=4000.0}, 
         User{name='王五', age=40, salary=4000.0}], 
 1000.0=[User{name='张三', age=12, salary=1000.0}]}

11.多级分组

//按薪水和年龄多级分组
Map<Double, Map<String, List<User>>> collect = user.stream()
    .collect(Collectors.groupingBy(User::getSalary, Collectors.groupingBy(
                u -> {
                    if ( u.getAge() <= 12) {
                        return "青年";
                    } else if ( u.getAge() <= 32) {
                        return "中年";
                    } else {
                        return "老年";
                    }
                }
        )));
System.out.println(collect);    
-----------------输出--------------    
{4000.0={老年=[User{name='王五', age=40, salary=4000.0}, 
             User{name='王五', age=40, salary=4000.0}], 
         中年=[User{name='李四', age=32, salary=4000.0}]}, 
 1000.0={青年=[User{name='张三', age=12, salary=1000.0}]}}

12.分区

//按薪水分区
Map<Boolean, List<User>> collect1 = user.stream()
    .collect(Collectors.partitioningBy(e -> e.getSalary() > 3000));
System.out.println(collect1);
-----------------输出--------------   
{false=[User{name='张三', age=12, salary=1000.0}], 
 true=[User{name='李四', age=32, salary=4000.0}, 
       User{name='王五', age=40, salary=4000.0}, 
       User{name='王五', age=40, salary=4000.0}]}

13.连接

//返回所有人的名字并中间加上"--"来连接
String s = user.stream().map(User::getName).collect(Collectors.joining("--"));
System.out.println(s);
-----------------输出--------------
张三--李四--王五--王五