1. 程式人生 > >JAVA1.8新特性Stream流

JAVA1.8新特性Stream流

今天我們來學習一下Java 8 的新特新—>Stream流;

Stream流

stream流是Java8的新特性,它也是有關於集合的新api;
Stream 作為 Java 8 的一大亮點,它與 java.io 包裡的 InputStream 和 OutputStream 是完全不同的概念。Java 8 中的 Stream 是對集合(Collection)物件功能的增強,它專注於對集合物件進行各種非常便利、高效的聚合操作,或者大批量資料操作;
Stream API 藉助於同樣新出現的 Lambda 表示式,極大的提高程式設計效率和程式可讀性;
下面我們用一個例子來引入Stream流的操作:

Stream流的資料操作特性

這裡有一個集合

List<Integer> list = Arrays.asList(1,2,3,4,5);

我們要寫一個方法找偶數,返回一個新的list包含結果;
用我們以前的方法做的話就會比較繁瑣:

public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1,2,3,4,5);
        System.out.println(exec(list, new Predicate<Integer>
(){ @Override public boolean test(Integer i) { return i % 2 == 0; } }))//System.out.println(exec(list, i -> i % 2 == 0)); } // 設計模式中,策略模式 public static List<Integer> exec(List<Integer> list, Predicate<Integer>
predicate) { List<Integer> list2 = new ArrayList<>(); for(Integer i :list) { if(predicate.test(i)) { list2.add(i); } } return list2; }

在這裡我們使用了設計模式中的策略模式,將它的處理方法拿出來,可以對它的使用方法進行主動的編寫,同時我們用了一個新的模式Predicate,這個模式叫做斷言模式,顧名思義就是對我們要進行的處理進行斷言操作,斷定它能進行的功能;比如上面的例子就是我們求出了list集合中所有的偶數,那我們如果不想對他進行求偶操作呢?比如我們想求出集合中所有大於三的資料呢?
這個時候我們就可以改變這個斷言,重新寫入想要的操作;

 System.out.println(exec(list, new Predicate<Integer>(){
            @Override
            public boolean test(Integer i) {
                return i >3;
            }

在不用斷言模式的時候,我們甚至需要新寫一個方法,然後呼叫它,有了斷言模式,我們就可以主動的給exec這個方法傳入我們想要的策略;
甚至我們可以對斷言進行簡寫;即lambda表示式;
在這裡插入圖片描述
這裡我們可以看到這個介面是一個函式式介面,就是單方法介面;所以我們完全可以用lambda表示式給他寫入斷言;

System.out.println(exec(list,i -> i % 2 == 0);
System.out.println(exec(list,i -> i > 3);

用了lambda表示式就大大減少了我們的程式碼量;
可是這也是比較麻煩的 ;而且在之前我們遍歷集合的時候就需要迴圈,或者 Iterator 迭代器來遍歷;這也都是很浪費時間和空間的;
那有沒有一種方法,是我們不用呼叫方法,直接用語句來獲得所有的偶數呢?這個時候我們就需要用到jdk1.8的新特性,Stream流,將陣列元素變為一條資料流,然後對這個資料流進行操作,過濾或者收集想要的資料;

  • 特點:
  1. Stream 不是集合元素,它不是資料結構並不儲存資料,它是有關演算法和計算的,它更像一個高階版本的 Iterator。原始版本的 Iterator,使用者只能顯式地一個一個遍歷元素並對其執行某些操作;高階版本的 Stream,使用者只要給出需要對其包含的元素執行什麼操作,比如 “過濾掉長度大於 10 的字串”、“獲取每個字串的首字母”等,Stream 會隱式地在內部進行遍歷,做出相應的資料轉換。

  2. Stream 就如同一個迭代器(Iterator),單向,不可往復,資料只能遍歷一次,遍歷過一次後即用盡了,就好比流水從面前流過,一去不復返。

  3. Stream 的資料來源本身可以是無限的。

Stream()類的方法

  1. filter (過濾器)
    這個過濾器的方法就是對流中的資料一個一個的進行篩選,看看它是不是符合規則,如果不符合就攔截住,也就是捨棄,如果符合要求,則讓他通過,進行下一步的操作,說白了就像一個濾網一樣,我們可以設定過濾的規則,然後它對資料流進行篩選;
    比如上面的例子來說我們就可以用這樣的方法;
List<Integer> list = Arrays.asList(1,2,3,4,5,6);
List<Integer> stream = list.stream().filter( i -> 
		i%2  == 0).collect(Collectors.toList());
System.out.print(stream);

在這裡插入圖片描述
我們可以看到filter的引數就是一個Predicae<>斷言;也就是說我們可以給它裡面傳一個lambda表示式,就是過濾的規則; 同時它返回的又是一個Stream流物件,這時我們又可以用它的collect(收集器),對過濾下來的資料收集起來,把他存入一個List集合;然後這時候我們輸出得到的這個集合,就是我們收集的資料;

  1. map (對映)
    對映的, lambda把原有的元素轉換為另一個元素, 不會改變個數;
    上面的filter過濾器就是給把我們的資料按我們的規則篩選出來,那map()方法就是將我們的元素按照規則對映成另外的元素;比如我們要得到上面的list集合中的所有元素的2倍的集合,這時候我們就需要用到map()方法;
	List<Integer> list4 = Arrays.asList(1, 2, 3, 4, 5, 6);
        List<Integer> collect = list4.stream().map(i -> 
        i * 2).collect(Collectors.toList());
        System.out.println(collect);

在這裡插入圖片描述
這只是簡單的對映,我們又得到了需要的資料,同時原集合仍然存在;我們再看看map()方法的引數;
在這裡插入圖片描述

在這裡插入圖片描述

這裡我們需要一個Function()引數,這個模式的方法lambda表示式的寫法就是一個引數,返回一個結果;

	map(i ->  i * 2)

這句對映就很直觀的幫我們解釋了Function介面的特性;

  1. == flatMap(扁平化對映)==
    這個類就好比是流的型別轉換;
    比如我們現在定義了一個list集合,它的元素是字串陣列;
List<String[]> list = new ArrayList<>();
        list.add(new String[]{"張三", "李四"});
        list.add(new String[]{"王五", "張三"});
        list.add(new String[]{"錢七", "周八"});

如果我們想將它裡面的每個字串提取出來,組成一個新的集合,按照原來的方法我們就需要兩重遍歷,先遍歷集合,後遍歷陣列,得到每一個字串元素,再新建一個集合,將它們存進去;

 List<String> list2 = new ArrayList<>();

        for (String[] strings : list) {
            for (String string : strings) {
                list2.add(string);
            }
        }
        System.out.println(list2);

可如果用flatmap方法做扁平化對映時就特別簡單,我們一句話就可以得到這個集合;

List<String> list3 = list.stream().flatMap( s -> 
Arrays.stream(s)).collect(Collectors.toList());

flatmap的引數同樣是一個Function,只是這次我們傳入的引數是list集合的元素String[]陣列,把它對映成一個數組流Arrays.stream,然後collect收集起來;這個方法就很方便;
它就可以用在我們平時大規模資料轉換的時候,用流的方式,得到需要的格式;

  1. forEach
    遍歷流,接收一個Consumer
list3.stream().forEach( (a) -> {
    System.out.println(a);
} );
  1. map的流遍歷
    接收一個BiConsumer
Map<String, String> map = new HashMap<>();
map.put("a", "張");
map.put("b", "李");
map.forEach( (key, value) -> {
    System.out.println("key:" +key + " value:" + value);
} );

重要模式

上面說了很多種的模式,再這裡我們統一進行一下說明;

  1. Predicate 斷言介面
    對應的lambda 一個引數,返回結果是boolean
    (a) -> { return true|false; }

  2. BiPredicate 雙引數斷言
    對應的lambda 兩個引數,返回結果是boolean
    (a, b) -> { return true|false; }

  3. Function 函式介面
    對應的lambda 一個引數,一個返回結果,引數和返回結果的型別可以不一樣

  4. BiFunction 雙引數函式介面
    兩個引數,一個結果
    (a, b) -> { 根據ab返回一個結果}

  5. Consumer 消費介面
    一個引數 沒有結果
    (a) -> { 不需要return }

  6. BiConsumer 雙引數消費介面
    兩個引數,沒有結果
    (a,b) -> { 不需要return }

  7. Supplier 生產者介面
    沒有引數,返回一個結果
    () -> {return 結果}
    有了這些模式知識的幫忙我們就可以更好的理解Stream()中方法的引數需求了;

7. 其它常見api

求個數count()

System.out.println(list3.stream().count());

去除重複distinct()

System.out.println(list3.stream().distinct().collect(toList()));

獲取最大最小值(引數需要一個比較器)

// 返回的是Optional 型別,怕集合為空時,沒有合法的最大值
List<String> list4 = Arrays.asList("zhang", "li", "zhao", "wang"); 
System.out.println(list4.stream().max((a, b) -> a.compareTo(b)));
System.out.println(list4.stream().min((a, b) -> a.compareTo(b)));

如果是數字流,除了最大最小值外,還有平均值,和

System.out.println(IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).max());
System.out.println(IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).min());
System.out.println(IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).average());
System.out.println(IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).sum());

有了上面的知識我們來做一個比較複雜的需求:
我們定義了一個學生類,它的屬性包括名字,性別,所在城市;

public class Student {

    private String name;
    private String sex;
    private String city;

    public Student(String name, String sex, String city) {
        this.name = name;
        this.sex = sex;
        this.city = city;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", city='" + city + '\'' +
                '}';
    }

    public String getSex() {
        return sex;
    }

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

    public String getName() {
        return name;
    }

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

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }
}

然後我們定義一個集合存放學生類的物件

 List<Student> students = Arrays.asList(
                new Student("zhang", "男", "西安"),
                new Student("li", "男", "西安"),
                new Student("wang", "女", "北京"),
                new Student("zhao", "女", "上海"),
                new Student("zhou", "男", "北京")
        );

這時我們如果想要按照性別進行分組儲存呢?(男生存在一起,女生存在一起)
按照我們本來的方法需要定義map陣列,按照鍵值對形式,將性別和物件形成對映,然後儲存起來;
具體程式碼:

Map<String, List<Student>> map2 = new HashMap<>();
        for (Student student : students) {
            if (student.getSex().equals("男")) {
                List<Student> nan = map2.get("男");
                if (nan == null) {
                    nan = new ArrayList<>();
                    map2.put("男", nan);
                }
                nan.add(student);
            } else {
                List<Student> nv = map2.get("女");
                if (nv == null) {
                    nv = new ArrayList<>();
                    map2.put("女", nv);
                }
                nv.add(student);
            }
        }
        System.out.println(map2);

我們發現上面的邏輯很複雜,我們也很容易出錯,同時這只是我們按照性別分組,那如果類別特別多的話,比如我們按照所在城市分組呢?難道我們每一個都要進行判斷嗎?顯然這是不現實的,這時我們就必須用Stream流來實現了:

//按照性別分組
Map<String, List<Student>> map3 = students.stream().collect(
Collectors.groupingBy( s -> s.getSex() ));
        System.out.println(map3);

這裡我們用了groupingBy方法,傳入的引數是一個Function,分組的標準就是我們function的返回屬性;

//按照所在城市分組
Map<String, List<Student>> map4 = students.stream().collect(
Collectors.groupingBy( s -> s.getCity()));
        System.out.println(map4);

答案:
{
女=[Student{name=‘wang’, sex=‘女’, city=‘北京’}, Student{name=‘zhao’, sex=‘女’, city=‘上海’}],
男=[Student{name=‘zhang’, sex=‘男’, city=‘西安’}, Student{name=‘li’, sex=‘男’, city=‘西安’}, Student{name=‘zhou’, sex=‘男’, city=‘北京’}]
}

{
上海=[Student{name=‘zhao’, sex=‘女’, city=‘上海’}],
西安=[Student{name=‘zhang’, sex=‘男’, city=‘西安’}, Student{name=‘li’, sex=‘男’, city=‘西安’}],
北京=[Student{name=‘wang’, sex=‘女’, city=‘北京’}, Student{name=‘zhou’, sex=‘男’, city=‘北京’}]
}