Java8新特性之流式操作

什么是流式操作

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

1.流式操作举例

1.1创建实体类

public class Person {

    private String name;
    private Integer age;
    private Integer score;

    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 Integer getScore() {
        return score;
    }

    public void setScore(Integer score) {
        this.score = score;
    }

    public Person() {
    }

    public Person(String name, Integer age, Integer score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

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

1.2 传统的对象初始化方式

public class Program {
    public static void main(String[] args) {
        //使用构造器设置对象信息
//        Person xiaomign = new Person("小明", 28, 90);

        //使用getter、setter方式设置对象信息
        Person xiaoming = new Person();
        xiaoming.setName("小明");
        xiaoming.setAge(18);
        xiaoming.setScore(90);
    }
}

1.3 使用流式操作初始化对象

1.3.1 修改实体类

public class Person {

    private String name;
    private Integer age;
    private Integer score;

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public Integer getScore() {
        return score;
    }

    public Person setScore(Integer score) {
        this.score = score;
        return this;
    }

    public Person() {
    }

    public Person(String name, Integer age, Integer score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

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

1.3.2 使用流式操作

//流式操作
xiaoming.setName("小明").setAge(20).setScore(100);

2.集合的流式操作

集合的流式操作是Java8的一个新特性,流式操作不是一个数据结构,不负责任何的数据存储,它更像是一个迭代器,可以有序的获取数据源中的每一个数据,并且可以对这些数据进行一些操作。 流式操作的每一个方法的返回值都是这个流的本身

2.1 流式操作的三个步骤

2.1.1 获取数据源:集合、数组

  • 设置数据源

    public class Data {
    
        /**
   */
  public static ArrayList getData() {
      ArrayList list = new ArrayList();

      list.add(new Person("小明", 18, 100));
      list.add(new Person("小丽", 19, 70));
      list.add(new Person("小王", 22, 85));
      list.add(new Person("小张", 20, 90));
      list.add(new Person("小黑", 21, 95));
      return list;
  }

}


- 获取数据源的方式

public class Program {

  public static void main(String[] args) {

      // 获取数据源方式1
      Stream stream = Data.getData().stream();

      // 获取数据源方式2
      Stream.of(Data.getData());
      
      // 获取数据源方式3
          //数据源为数组
  }

}


#### 2.1.2 对数据进行处理的过程:过滤、排序、映射等(中间操作)

#### 中间操作1:filter

- 使用filter自定义条件过滤数据

// 中间操作1: filter
// filter是一个过滤器,可以自定义一个过滤条件,将流中满足条件的元素保留
// 查找集合中成绩小于80的学生
List list = Data.getData().stream()

  .filter(ele -> ele.getScore() < 80)
  .collect(Collectors.toList());

System.out.println(list);



![](https://user-gold-cdn.xitu.io/2020/1/9/16f89ea9733ff8cb?w=611&h=170&f=png&s=29061)

#### 中间操作2:distinct

- 使用distinct实现去重操作

在数据源中添加重复的数据

list.add(new Person("小黑", 21, 95)); //此时list中有两个小黑


在实体类中重写hashCode()和equals()方法

@Override
public boolean equals(Object o) {

  if (this == o) return true;
  if (o == null || getClass() != o.getClass()) return false;
  Person person = (Person) o;
  return Objects.equals(name, person.name) &&
      Objects.equals(age, person.age) &&
      Objects.equals(score, person.score);

}

@Override
public int hashCode() {

  return Objects.hash(name, age, score);

}


去重规则:

- 先判断对象的hashCode()

- 如果hashCode()相同再判断equals()

// 中间操作2: distinct
// distinct: 取出集合中不同的元素
// 去重规则:
// 1.先判断对象的hashCode()
// 2.如果hashCode()相同再判断equals()
Data.getData().stream().distinct().forEach(System.out::println);



![](https://user-gold-cdn.xitu.io/2020/1/9/16f89eb1e7184f38?w=636&h=258&f=png&s=65653)

**注意**:如果小黑的数据相同却要保存两份,可以在`hashCode()`方法中返回一个随机数,随机数很小概率会相同,为了确保稳定性,可以将`equals()`方法改为返回`false`,这样可以保留两个信息相同的小黑。

#### 中间操作3:sorted

- 使用`sorted()`方法以成绩进行升序排序

要求实体类实现Comparable接口并重写方法

// 中间操作3: sorted
// sorted: 对返回的元素进行排序
// sorted(): 要求实体类实现Comparable接口并重写方法
Data.getData().stream().sorted().forEach(System.out::println);





![](https://user-gold-cdn.xitu.io/2020/1/9/16f89eb78d4a47fa?w=661&h=295&f=png&s=76976)

#### 中间操作4:limit

- 在数据源中取前三个数据

// 中间操作4: limit
// limit: 限制,只取流中前指定位的数据
// 在数据源中取前三个数据
Data.getData().stream().limit(3).forEach(System.out::println);



![](https://user-gold-cdn.xitu.io/2020/1/9/16f89ebbbc5d3060?w=728&h=205&f=png&s=49304)

#### 中间操作5:skip

- 跳过前三个元素,取后面剩下的元素

// 中间操作5: skip
// skip: 跳过
// 跳过前三个元素,取后面剩下的元素
Data.getData().stream().skip(3).forEach(System.out::println);



![](https://user-gold-cdn.xitu.io/2020/1/9/16f89ebe70250ba3?w=621&h=189&f=png&s=43474)

#### 中间操作6:map

元素映射,用指定的元素替换掉流中的元素

- 使用map将对象替换为对象的名字

// 中间操作6: map
// map: 元素映射,用指定的元素替换掉流中的元素
// 将流中的Person对象替换位他们的姓名
Data.getData().stream().map(ele -> ele.getName()).forEach(System.out::println);



![](https://user-gold-cdn.xitu.io/2020/1/9/16f89ec2d7bc6b80?w=265&h=241&f=png&s=17372)

#### 2.1.3 对流中数据的整合:转成集合、数量(最终操作)

#### 最终操作1:collect

- 转换为List

public class Program {

  public static void main(String[] args) {

      // 获取数据源方式1
      Stream stream = Data.getData().stream();

      // 最终操作1: collect,配合Collectors使用
      // 将集合中的元素转换成List
      List list = stream.collect(Collectors.toList());

      System.out.println(list);
  }

}


运行结果


![](https://user-gold-cdn.xitu.io/2020/1/9/16f89ec6387db838?w=930&h=191&f=png&s=40757)

- 转换为set

// 将集合中的元素转换为Set
Set set = stream.collect(Collectors.toSet());
System.out.println(set);


运行结果


![](https://user-gold-cdn.xitu.io/2020/1/9/16f89ecb0d9c2d77?w=971&h=163&f=png&s=38809)

- 转换为map

// 转换为Map(name为键,score为值)

      // 方式1

// Map map = stream.collect(Collectors.toMap(
// ele -> ele.getName(),
// ele -> ele.getScore()
// ));

      
      // 方式2        
      Map map = stream.collect(Collectors.toMap(
              Person::getName,
              Person::getScore
      ));

运行结果


![](https://user-gold-cdn.xitu.io/2020/1/9/16f89ece4fab606c?w=761&h=156&f=png&s=36323)

#### 最终操作2:reduce

reduce的思想

> 比如在计算一个数组中的元素的和时,首先会计算前两个数的和,然后拿着前两个数的和与第三个数求和,计算出结果后将三个数的和与第四个数相加,以此类推。


![](https://user-gold-cdn.xitu.io/2020/1/9/16f89ed13d3a3c24?w=1198&h=895&f=png&s=180011)

- 计算数组中数据的和

// 最终操作2: reduce(将数据汇总在一起)
Stream stream1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional res = stream1.reduce((n1, n2) -> n1 + n2);
// 获取到最终的返回值
System.out.println(res.get());



![](https://user-gold-cdn.xitu.io/2020/1/9/16f89ed33f40f7a9?w=433&h=179&f=png&s=18896)

- 使用reduce计算Person对象中成绩的和

// 计算Person中Score的和
Optional res = stream.reduce(

  (n1, n2) -> new Person().setScore(n1.getScore() + n2.getScore())

);
System.out.println(res.get().getScore());



![](https://user-gold-cdn.xitu.io/2020/1/9/16f89ed7fff6fd4f?w=605&h=187&f=png&s=24829)

**缺点:上面的写法每次都会产生一个临时的对象,产生了不必要的性能损耗**

- 使用reduce计算Person对象中成绩的和(优化)

// 计算Person中Score的和(使用临时变量,减少性能开销)
Person temp = new Person();
Optional res = stream.reduce(

  (n1, n2) -> temp.setScore(n1.getScore() + n2.getScore())

);
System.out.println(res.get().getScore());


 
![](https://user-gold-cdn.xitu.io/2020/1/9/16f89edb3efcb8ba?w=433&h=144&f=png&s=15022)

#### 最终操作3:max和min

- 使用max找出Person中成绩最高的人

// 最终操作3: max和min
// 需求1: 找到集合中成绩最高的人的信息
Person max = stream.max(

  (ele1, ele2) -> ele1.getScore() - ele2.getScore()

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



![](https://user-gold-cdn.xitu.io/2020/1/9/16f89edf8cbc08cf?w=685&h=161&f=png&s=26944)

- 使用min找出Person中成绩最低的人

// 需求2: 找到集合中成绩最低的人的信息
Person min = stream.min(

  (ele1, ele2) -> ele1.getScore() - ele2.getScore()

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


 
![](https://user-gold-cdn.xitu.io/2020/1/9/16f89ee1b0369afb?w=667&h=178&f=png&s=27307)

#### 最终操作4:matching

- 使用anyMatch查看集合中是否有成绩高于80的人

// 判断集合中是否包含成绩大于80的学员
boolean res1 = stream.anyMatch((ele) -> ele.getScore() > 80);
System.out.println(res1);



![](https://user-gold-cdn.xitu.io/2020/1/9/16f89ee55e666b52?w=575&h=159&f=png&s=19291)

- 使用allMatch查看集合中的成绩是否全部高于60

//查看集合中的人的成绩是否全部高于60
boolean res2 = stream.allMatch((ele) -> ele.getScore() > 60);
System.out.println(res2);



![](https://user-gold-cdn.xitu.io/2020/1/9/16f89ee8f17111c3?w=611&h=170&f=png&s=24096)

- 使用noneMatch查看集合中的人的分数是否不包含80以下的

boolean res3 = stream.noneMatch((ele) -> ele.getScore() < 80);
System.out.println(res3);



![](https://user-gold-cdn.xitu.io/2020/1/9/16f89f0480b61e55?w=586&h=174&f=png&s=24574)

#### 最终操作5:count

- 使用count计算元数据中有多少条数据

// 最终操作5: 求元数据中有多少个元素
long count = stream.count();
System.out.println(count);



![](https://user-gold-cdn.xitu.io/2020/1/9/16f89f07ee53f641?w=355&h=148&f=png&s=11989)

#### 最终操作6:forEach

- 使用forEach遍历集合中的元素

// 最终操作6: forEach
// stream.forEach(ele -> System.out.println(ele));
stream.forEach(System.out::println);



![](https://user-gold-cdn.xitu.io/2020/1/9/16f89f0a50acedd4?w=651&h=271&f=png&s=66604)

#### 最终操作7:findFirst和findAny

- FindFirst: 获取流中的第一个元素
FindAny: 获取流中任意一个元素(并不是随机获取元素)
对于串行流,结果等同于findFirst
findAny用于并行流中可能会与findFirst一样,也可能不一样

// FindFirst: 获取流中的第一个元素
// FindAny: 获取流中任意一个元素(并不是随机获取元素)
// 对于串行流,结果等同于findFirst
// findAny用于并行流中可能会与findFirst一样,也可能不一样
System.out.println(Data.getData().parallelStream().findFirst());
System.out.println(Data.getData().stream().findFirst());
System.out.println(Data.getData().parallelStream().findAny());
System.out.println(Data.getData().stream().findAny());


#### 最终操作的注意事项

- 为什么会被称为最终操作?

Person max = stream.max(

  (ele1, ele2) -> ele1.getScore() - ele2.getScore()

).get();
Person min = stream.min(

  (ele1, ele2) -> ele1.getScore() - ele2.getScore()

).get();



![](https://user-gold-cdn.xitu.io/2020/1/9/16f89f1605bddd13?w=1116&h=123&f=png&s=58705)

报错信息表示**流正在被处理或者已经被关闭了**,如果已经被关闭了再次调用当然会报错,这也是为什么叫最终操作的原因。

### 3.并行流

#### 3.1 获取并行流的方式

// 并行流
// 获取并行流的两种方式
Data.getData().stream().parallel();
Data.getData().parallelStream();


#### 3.2 并行流与串行流对比

// 串行流: 19920ms
// 并行流: 12204ms
long startTime = System.currentTimeMillis();
//LongStream.rangeClosed(0L, 50000000000L)
// .reduce(Long::sum);
LongStream.rangeClosed(0L, 50000000000L)

.parallel()
.reduce(Long::sum);

long endTime = System.currentTimeMillis();

System.out.println(endTime - startTime);


#### 3.3 flatMap

String[] array = {"hello", "world"};
// 需要获取所有字符 List -> h, e, l, l, o, w, o, r, l, d
// Arrays.stream(array)
// .map(ele -> ele.split(""))
// .forEach(ele -> System.out.println(ele.length));
System.out.println(Arrays.stream(array)

               .map(ele -> ele.split(""))
               .flatMap(Arrays::stream)
               .collect(Collectors.toList()));


![](https://user-gold-cdn.xitu.io/2020/1/9/16f89f1be415e2d3?w=613&h=160&f=png&s=25764)

### 4.Collectors

> Collectors是一个工具类,提供着若干个方法,返回一个Collector接口的实现类对象

#### 4.1 maxBy

​    通过指定的规则获取流中最大的元素

System.out.println(Data.getData().stream()

            .collect(Collectors.maxBy((ele1, ele2) -> ele1.getScore() - ele2.getScore())));


![](https://user-gold-cdn.xitu.io/2020/1/9/16f89f218209f9b7?w=734&h=149&f=png&s=35079)

#### 4.2 minBy

​    通过指定的规则获取流中最小的元素

System.out.println(Data.getData().stream()

            .collect(Collectors.minBy((ele1, ele2) -> ele1.getScore() - ele2.getScore())));


![](https://user-gold-cdn.xitu.io/2020/1/9/16f89f239777e5d8?w=665&h=118&f=png&s=26600)

#### 4.3 joining

合并,将流中的元素,以字符串的形式拼接起来

// 把Person中的姓名拼成一个字符串
String res1 = Data.getData().stream()

.map(Person::getName)
.collect(Collectors.joining());

System.out.println(res1);



![](https://user-gold-cdn.xitu.io/2020/1/9/16f89f25b1228a6d?w=588&h=150&f=png&s=30002)

String res2 = Data.getData().stream()

.map(Person::getName)
.collect(Collectors.joining("-"));

System.out.println(res2);



![](https://user-gold-cdn.xitu.io/2020/1/9/16f89f28697714d6?w=583&h=165&f=png&s=25451)

String res3 = Data.getData().stream()

.map(Person::getName)
.collect(Collectors.joining("-", "{", "}"));

System.out.println(res3);



![](https://user-gold-cdn.xitu.io/2020/1/9/16f89f2a234a0d85?w=617&h=156&f=png&s=31837)

#### 4.4 summingInt

计算int类型的和,将流中的元素映射为int类型的元素进行求和

- 将Person对象的成绩进行求和

// 将Person对象的成绩进行求和
System.out.println(Data.getData().stream()

                 .collect(Collectors.summingInt(ele -> ele.getScore())));


![](https://user-gold-cdn.xitu.io/2020/1/9/16f89f2cae304810?w=476&h=146&f=png&s=20722)

#### 4.5 averagingInt

计算int类型的平均值

- 计算不及格学生的平均成绩

System.out.println(Data.getData().stream()

                 .filter(ele -> ele.getScore() < 60)
                 .collect(Collectors.averagingInt(Person::getScore)));

#### 4.6 summarizingInt

将流中的元素映射成int类型的元素,获取这些数据的描述信息

System.out.println(Data.getData().stream()

               .collect(Collectors.summarizingInt(ele -> ele.getScore())));


![](https://user-gold-cdn.xitu.io/2020/1/9/16f89f3333407992?w=1088&h=137&f=png&s=39793)

你可能感兴趣的:(javastream)