Java进阶学习笔记(十二)Java8重要特性stream详解

参考了Java8新特性之三:Stream API、Java 8系列之Stream的基本语法详解、Java 8 Stream API 实用指南

文章目录

  • 1. 为何要使用Stream
  • 3. 生成Stream
    • 3.1 由集合、数组生成、获取stream
    • 3.2 根据map生成并获取stream
    • 3.3 直接创建Stream
  • 4. Stream中间操作
    • Stream中间操作--筛选与切片
      • 4.1 filter举例
      • 4.2 limit举例
      • 4.3 skip举例
      • 4.4 distinct举例
    • Stream中间操作--映射
      • 4.5 map举例
    • 4.6 flatMap举例
    • 排序
      • 4.7 sort举例
  • 5.Stream 终止操作
    • 查找与匹配
      • 5.1 allMatch
      • 5.2 max
    • 归约
      • 5.3 Stream
    • 收集
      • 5.4 collect 获取person列表中的女性,生成一个新list
      • 5.5 计算平均年龄

1. 为何要使用Stream

我们先上一个简单的案例,比如说我们要输出学生中,性别为G学生的信息,

public class Student {
    int no;
    String name;
    String sex;
    float height;
    //省略getter setter toString
}

public class run {
    public static void main(String[] args) {
        Student stuA = new Student(1, "A", "M", 184);
        Student stuB = new Student(2, "B", "G", 163);
        Student stuC = new Student(3, "C", "M", 175);
        Student stuD = new Student(4, "D", "G", 158);
        Student stuE = new Student(5, "E", "M", 170);
        List<Student> list = new ArrayList<>();
        list.add(stuA);
        list.add(stuB);
        list.add(stuC);
        list.add(stuD);
        list.add(stuE);

		// 老方法,用for遍历
        for (Student student: list) {
            if (student.getSex().equals("G")) {
                System.out.println(student);
            }
        }
    	// 用stream
        list.stream()
                .filter(student -> student.getSex().equals("G"))
                .forEach(System.out::println);
    }
}

可能这还不是很明显,但如果我们提升筛选的要求
假设我们有一批商品, 我们需要将:价格 > 500 & 销量< 200 的商品, 价格最高的 10 件商品, 价格减半

class Good {  
  String name;               // 商品名称  
  long price;               // 价格  
  long sales;               // 销量  
  List<String> categories;  // 类别  
  // ... 省略 constructor、getter / setter , 省略 toString  
}  

void process(List<Good> goods) {  
  //  
  // 筛选 price > 500 & sales < 200 的商品, 价格最高的 10 件商品, 价格减半(双十一来啦!)  
  //  
  goods.stream()  
    .filter(good -> good .getPrice() > 500 && good .getSales() < 200)  
    .sorted(Comparator.comparing(Good::getPrice).reversed())  
    .limit(10)  
    .forEach(good  -> { good.setPrice(good .getPrice() / 2); });  
}  

没有用stream,我们可以用老方法,如 for 遍历, if 判断等,但大家知道,for、if的嵌套会使得代码无法像stream一样,简洁清晰。


# 2. Stream步骤 Stream有如下三个操作步骤:

一、创建Stream
从一个数据源,如集合、数组中获取流。

二、中间操作
一个操作的中间链,对数据源的数据进行操作。如filter、sorted、limit 这种只描述Stream,最终不产生新集合的方法,均为中间操作,其中filter又称为惰性求值

三、终止操作
一个终止操作,执行中间操作链,并产生结果。像count这种最终会从Stream产生值的方法,为终止操作,其中count 也称为及早求值。

我们已刚才商品为例,上文的代码有此示意图
在这里插入图片描述
图中所示,整个过程就是将 goods 元素集合作为一个 “序列”,进行一组 “流水线” 操作,其中:

  • goods 集合提供了元素序列的数据源,通过 stream() 方法获得 Stream
  • filter / sorted / limit 进行数据处理,“连接起来” 构成 “流水线”
  • forEach 最终执行

需要说明,filter / sorted / limit 的返回值均为 Stream(类似于 Builder 模式),但它们并不立即执行,而是构成了 “流水线”,直到 forEach:最终执行,并且关闭 Stream。因此:

  • 将 filter / sorted / limited 等能够 “连接起来”,并且返回 Stream 的方法称为 “中间操作”(Intermediate)
  • 将 forEach 等最终执行,并且关闭 Stream 的方法称为 “终止操作” (Terminal)

特别地,需要记住:Stream 的中间操作并不是立即执行,而是 “延迟的”、“按需计算”;并且,完成 “终止操作” 后,Stream 将被关闭。


3. 生成Stream

Java 8 的 Collection 接口添加了 Stream stream() 方法,由集合生成 Stream,List和set均继承了collection接口,所以List和set可直接调用stream方法。

3.1 由集合、数组生成、获取stream

//list、set、vector相同
void print(List<Good> goods) {  
  goods.stream().forEach(System.out::println);  
}  

// 数组生成stream方法
void print(Good[] goods) {  
  Arrays.stream(goods).forEach(System.out::println);  
}  

上面的代码分为两个步骤,一个stream(),生成stream,forEach()用是stream的终止操作,上面的代码还可以写为以下形式:

void print(List<Good> goods) {  
  Stream<goods> stream = goods.stream();  
  stream..forEach(System.out::println);
}  

3.2 根据map生成并获取stream

map不是collection的子接口,所以获取对应的流需要分为key、value、entry三种情况

Map<String,String> map = new HashMap<>();
 
// 根据键取流
Stream<String> keyStream = map.keySet().stream();

// 根据值取流
Stream<String> valueStream = map.values().stream();

// 根据键值对取流
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();

3.3 直接创建Stream

Stream API 提供了静态方法 Stream.generate(Supplier)Stream.iterator(final T, final UnaryOperator),直接创建 Stream。

//生成number个的商品,并逐一输出
Stream.generate(Good::new).limit(number).forEach().forEach(System.out::println)

//  
// 生成指定数量的序列 1, 2, 4, 8, 16 ... 并输出,个数为number个
//  
Stream.iterate(0, n -> n * 2).limit(number).forEach(System.out::println);  

4. Stream中间操作

接下来让我们使用person类进行举例

class Person {
    private String name;
    private Integer age;
    private String country;
    private char sex;
    // 省略 getter\setter\tostring\constructor\hashCode
}

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

Stream中间操作–筛选与切片

  • filter:接收Lambda,从流中排除某些操作;
  • limit:截断流,使其元素不超过给定对象
  • skip(n):跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
  • distinct:筛选,通过流所生成元素的hashCode()和equals()去除重复元素

4.1 filter举例

需求:获取person列表中的女性,生成一个新list

List<Person> womanList = personList.stream().filter((person) -> person.getSex() == 'F').collect(Collectors.toList());

4.2 limit举例

从pserson列表中,输出两个人的信息

personList.stream().limit(2).forEach(System.out::println);

输出为,可见,是按照添加顺序的先后,取前两个的信息

Person{name='欧阳雪', age=18, country='中国', sex=F}
Person{name='Tom', age=24, country='美国', sex=M}

4.3 skip举例

从person列表中第二个人开始,输出所有人的信息

personList.stream().skip(1).forEach(System.out::println);

4.4 distinct举例

我们获取所有age 为 22的人并且输出,distinct的去重是根据hashCode进行去重,所以需要重写hashCode()方法,或者使用lombok,给类加上@Data注解,类似于如下修改

class Person {
    // ......
    @Override
    public int hashCode() {
        return Objects.hash(name, age, country, sex);
    }
}

执行代码如下

personList.stream().filter((p) -> p.getSex() == 'M').distinct().forEach(System.out::println);

输出如下所示

Person{name='Tom', age=24, country='美国', sex=M}
Person{name='向天笑', age=20, country='中国', sex=M}
Person{name='李康', age=22, country='中国', sex=M}

可发现,男性中有两个李康,但此处仅输出了一个,去掉了一个重复的。

Stream中间操作–映射

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

4.5 map举例

map方法将对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。为了提高处理效率,官方已封装好了,三种变形:mapToDouble,mapToInt,mapToLong。

其实很好理解,如果想将原Stream中的数据类型,转换为double,int或者是long是可以调用相对应的方法。

Stream.of("a", "b", "hello")
        .map(item-> item.toUpperCase())
        .forEach(System.out::println);
        // 打印结果
        // A, B, HELLO

4.6 flatMap举例

flatMap方法与map方法类似,都是将原Stream中的每一个元素通过转换函数转换,不同的是,该换转函数的对象是一个Stream,也不会再创建一个新的Stream,而是将原Stream的元素取代为转换的Stream。
如果转换函数生产的Stream为null,应由空Stream取代。flatMap有三个对于原始类型的变种方法,分别是:flatMapToInt,flatMapToLong和flatMapToDouble。

Stream.of(1, 2, 3)
    .flatMap(integer -> Stream.of(integer * 10))
    .forEach(System.out::println);
    // 打印结果
    // 10,20,30

排序

4.7 sort举例

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

运行结果

Person(name=欧阳雪, age=18, country=中国, sex=F)
Person(name=向天笑, age=20, country=中国, sex=M)
Person(name=小梅, age=20, country=中国, sex=F)
Person(name=何雪, age=21, country=中国, sex=F)
Person(name=Harley, age=22, country=英国, sex=F)
Person(name=李康, age=22, country=中国, sex=M)
Person(name=李康, age=22, country=中国, sex=M)
Person(name=Tom, age=24, country=美国, sex=M)

5.Stream 终止操作

查找与匹配

  • allMatch–检查是否匹配所有元素
  • anyMatch–检查是否至少匹配一个元素
  • noneMatch–检查是否没有匹配所有元素
  • findFirst–返回第一个元素
  • findAny–返回当前流中的任意元素
  • count–返回流中元素的总个数
  • max–返回流中最大值
  • min–返回流中最小值

大多都很简单移动,此处仅对allMatch、max各举一例说明

5.1 allMatch

final boolean adult = personList.stream().allMatch(p -> p.getAge() >= 18);
System.out.println("是否都是成年人:" + adult);//是否都是成年人:true

5.2 max

final Optional<Person> maxAge = personList.stream().max((p1, p2) -> p1.getAge().compareTo(p2.getAge()));
System.out.println("年龄最大的人信息:" + maxAge.get());

归约

5.3 Stream

Stream API的归约操作可以将流中元素反复结合起来,得到一个值。
我们举一个例子,我们需要计算 1 至 100 的和。

        List<Integer> integerList = new ArrayList<>(100);
        for(int i = 1;i <= 100;i++) {
            integerList.add(i);
        }
        Optional<Integer> reduce = integerList.stream().reduce((total, item) -> total + item);
        System.out.println(reduce.get());

求所有人的年龄之和

final Optional<Integer> reduce = personList.stream().map(Person::getAge).reduce(Integer::sum);
System.out.println("年龄总和:" + reduce);

刚加详细的解析请看下面的这个链接
Java 1.8 新特性——Stream 流中 Reduce 操作

收集

collect:将流转换为其他形式

5.4 collect 获取person列表中的女性,生成一个新list

List<Person> womanList = personList.stream().filter((person) -> person.getSex() == 'F').collect(Collectors.toList());

5.5 计算平均年龄

final Double collect1 = personList.stream().collect(Collectors.averagingInt(p -> p.getAge()));
2 System.out.println("平均年龄为:" + collect1);

你可能感兴趣的:(Java学习,stream,java,stream作用,详解,学习笔记)