Java8新特性之流式操作

什么是流式操作

Java 8 API添加了一個新的抽象稱為流Stream,可以讓你以一種聲明的方式處理數(shù)據(jù)。

Stream 使用一種類似用 SQL 語句從數(shù)據(jù)庫查詢數(shù)據(jù)的直觀方式來提供一種對 Java 集合運算和表達的高階抽象。

Stream API可以極大提高Java程序員的生產(chǎn)力,讓程序員寫出高效率、干凈、簡潔的代碼。

這種風格將要處理的元素集合看作一種流, 流在管道中傳輸, 并且可以在管道的節(jié)點上進行處理, 比如篩選, 排序,聚合等。

元素流在管道中經(jīng)過中間操作(intermediate operation)的處理,最后由最終操作(terminal operation)得到前面處理的結果。

1.流式操作舉例

1.1創(chuàng)建實體類

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 傳統(tǒng)的對象初始化方式

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的一個新特性,流式操作不是一個數(shù)據(jù)結構,不負責任何的數(shù)據(jù)存儲,它更像是一個迭代器,可以有序的獲取數(shù)據(jù)源中的每一個數(shù)據(jù),并且可以對這些數(shù)據(jù)進行一些操作。流式操作的每一個方法的返回值都是這個流的本身。

2.1 流式操作的三個步驟

2.1.1 獲取數(shù)據(jù)源:集合、數(shù)組

  • 設置數(shù)據(jù)源

    public class Data {
    
        /**
         * 數(shù)據(jù)源
         */
        public static ArrayList<Person> getData() {
            ArrayList<Person> list = new ArrayList<Person>();
    
            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;
        }
    }
    
  • 獲取數(shù)據(jù)源的方式

    public class Program {
        public static void main(String[] args) {
    
            // 獲取數(shù)據(jù)源方式1
            Stream stream = Data.getData().stream();
    
            // 獲取數(shù)據(jù)源方式2
            Stream.of(Data.getData());
            
            // 獲取數(shù)據(jù)源方式3
              //數(shù)據(jù)源為數(shù)組
        }
    }
    

2.1.2 對數(shù)據(jù)進行處理的過程:過濾、排序、映射等(中間操作)

中間操作1:filter

  • 使用filter自定義條件過濾數(shù)據(jù)

    // 中間操作1: filter
    // filter是一個過濾器,可以自定義一個過濾條件,將流中滿足條件的元素保留
    // 查找集合中成績小于80的學生
    List<Person> list = Data.getData().stream()
        .filter(ele -> ele.getScore() < 80)
        .collect(Collectors.toList());
    System.out.println(list);
    
image

中間操作2:distinct

  • 使用distinct實現(xiàn)去重操作

    在數(shù)據(jù)源中添加重復的數(shù)據(jù)

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

    去重規(guī)則:

    • 先判斷對象的hashCode()

    • 如果hashCode()相同再判斷equals()

    // 中間操作2: distinct
    // distinct: 取出集合中不同的元素
    // 去重規(guī)則:
    // 1.先判斷對象的hashCode()
    // 2.如果hashCode()相同再判斷equals()
    Data.getData().stream().distinct().forEach(System.out::println);
    
image

注意:如果小黑的數(shù)據(jù)相同卻要保存兩份,可以在hashCode()方法中返回一個隨機數(shù),隨機數(shù)很小概率會相同,為了確保穩(wěn)定性,可以將equals()方法改為返回false,這樣可以保留兩個信息相同的小黑。

中間操作3:sorted

  • 使用sorted()方法以成績進行升序排序

    要求實體類實現(xiàn)Comparable接口并重寫方法

    // 中間操作3: sorted
    // sorted: 對返回的元素進行排序
    // sorted(): 要求實體類實現(xiàn)Comparable接口并重寫方法
    Data.getData().stream().sorted().forEach(System.out::println);
    
image

中間操作4:limit

  • 在數(shù)據(jù)源中取前三個數(shù)據(jù)

    // 中間操作4: limit
    // limit: 限制,只取流中前指定位的數(shù)據(jù)
    // 在數(shù)據(jù)源中取前三個數(shù)據(jù)
    Data.getData().stream().limit(3).forEach(System.out::println);
    
image

中間操作5:skip

  • 跳過前三個元素,取后面剩下的元素

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

中間操作6:map

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

  • 使用map將對象替換為對象的名字

    // 中間操作6: map
    // map: 元素映射,用指定的元素替換掉流中的元素
    // 將流中的Person對象替換位他們的姓名
    Data.getData().stream().map(ele -> ele.getName()).forEach(System.out::println);
    
image

2.1.3 對流中數(shù)據(jù)的整合:轉成集合、數(shù)量(最終操作)

最終操作1:collect

  • 轉換為List

    public class Program {
        public static void main(String[] args) {
    
            // 獲取數(shù)據(jù)源方式1
            Stream<Person> stream = Data.getData().stream();
    
            // 最終操作1: collect,配合Collectors使用
            // 將集合中的元素轉換成List
            List<Person> list = stream.collect(Collectors.toList());
    
            System.out.println(list);
        }
    }
    

    運行結果

image
  • 轉換為set

    // 將集合中的元素轉換為Set
    Set<Person> set = stream.collect(Collectors.toSet());
    System.out.println(set);
    

    運行結果

image
  • 轉換為map

    // 轉換為Map(name為鍵,score為值)
            // 方式1
    //        Map<String, Integer> map = stream.collect(Collectors.toMap(
    //                ele -> ele.getName(),
    //                ele -> ele.getScore()
    //        ));  
            
            // 方式2        
            Map<String, Integer> map = stream.collect(Collectors.toMap(
                    Person::getName,
                    Person::getScore
            ));
    

    運行結果

image

最終操作2:reduce

reduce的思想

比如在計算一個數(shù)組中的元素的和時,首先會計算前兩個數(shù)的和,然后拿著前兩個數(shù)的和與第三個數(shù)求和,計算出結果后將三個數(shù)的和與第四個數(shù)相加,以此類推。

image
  • 計算數(shù)組中數(shù)據(jù)的和

    // 最終操作2: reduce(將數(shù)據(jù)匯總在一起)
    Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Optional<Integer> res = stream1.reduce((n1, n2) -> n1 + n2);
    // 獲取到最終的返回值
    System.out.println(res.get());
    
image
  • 使用reduce計算Person對象中成績的和

    // 計算Person中Score的和
    Optional<Person> res = stream.reduce(
        (n1, n2) -> new Person().setScore(n1.getScore() + n2.getScore())
    );
    System.out.println(res.get().getScore());
    
image

缺點:上面的寫法每次都會產(chǎn)生一個臨時的對象,產(chǎn)生了不必要的性能損耗

  • 使用reduce計算Person對象中成績的和(優(yōu)化)

    // 計算Person中Score的和(使用臨時變量,減少性能開銷)
    Person temp = new Person();
    Optional<Person> res = stream.reduce(
        (n1, n2) -> temp.setScore(n1.getScore() + n2.getScore())
    );
    System.out.println(res.get().getScore());
    
image

最終操作3:max和min

  • 使用max找出Person中成績最高的人

    // 最終操作3: max和min
    // 需求1: 找到集合中成績最高的人的信息
    Person max = stream.max(
        (ele1, ele2) -> ele1.getScore() - ele2.getScore()
    ).get();
    System.out.println(max);
    
image
  • 使用min找出Person中成績最低的人

    // 需求2: 找到集合中成績最低的人的信息
    Person min = stream.min(
        (ele1, ele2) -> ele1.getScore() - ele2.getScore()
    ).get();
    System.out.println(min);
    
image

最終操作4:matching

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

    // 判斷集合中是否包含成績大于80的學員
    boolean res1 = stream.anyMatch((ele) -> ele.getScore() > 80);
    System.out.println(res1);
    
image
  • 使用allMatch查看集合中的成績是否全部高于60

    //查看集合中的人的成績是否全部高于60
    boolean res2 = stream.allMatch((ele) -> ele.getScore() > 60);
    System.out.println(res2);
    
image
  • 使用noneMatch查看集合中的人的分數(shù)是否不包含80以下的

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

最終操作5:count

  • 使用count計算元數(shù)據(jù)中有多少條數(shù)據(jù)

    // 最終操作5: 求元數(shù)據(jù)中有多少個元素
    long count = stream.count();
    System.out.println(count);
    
image

最終操作6:forEach

  • 使用forEach遍歷集合中的元素

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

最終操作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();
    
image

報錯信息表示流正在被處理或者已經(jīng)被關閉了,如果已經(jīng)被關閉了再次調(diào)用當然會報錯,這也是為什么叫最終操作的原因。

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

4.Collectors

Collectors是一個工具類,提供著若干個方法,返回一個Collector接口的實現(xiàn)類對象

4.1 maxBy

? 通過指定的規(guī)則獲取流中最大的元素

System.out.println(Data.getData().stream()
                .collect(Collectors.maxBy((ele1, ele2) -> ele1.getScore() - ele2.getScore())));
image

4.2 minBy

? 通過指定的規(guī)則獲取流中最小的元素

System.out.println(Data.getData().stream()
                .collect(Collectors.minBy((ele1, ele2) -> ele1.getScore() - ele2.getScore())));
image

4.3 joining

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

// 把Person中的姓名拼成一個字符串
String res1 = Data.getData().stream()
    .map(Person::getName)
    .collect(Collectors.joining());
System.out.println(res1);
image
String res2 = Data.getData().stream()
    .map(Person::getName)
    .collect(Collectors.joining("-"));
System.out.println(res2);
image
String res3 = Data.getData().stream()
    .map(Person::getName)
    .collect(Collectors.joining("-", "{", "}"));
System.out.println(res3);
image

4.4 summingInt

計算int類型的和,將流中的元素映射為int類型的元素進行求和

  • 將Person對象的成績進行求和

    // 將Person對象的成績進行求和
    System.out.println(Data.getData().stream()
                       .collect(Collectors.summingInt(ele -> ele.getScore())));
    
image

4.5 averagingInt

計算int類型的平均值

  • 計算不及格學生的平均成績

    System.out.println(Data.getData().stream()
                       .filter(ele -> ele.getScore() < 60)
                       .collect(Collectors.averagingInt(Person::getScore)));
    

4.6 summarizingInt

將流中的元素映射成int類型的元素,獲取這些數(shù)據(jù)的描述信息

System.out.println(Data.getData().stream()
                   .collect(Collectors.summarizingInt(ele -> ele.getScore())));
image
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容