Java8-Stream流

Java8-Stream基礎操做

在學習Stream以前必須有Lambda,的基礎,最好有泛型的基礎html

Stream是Java8的新特性,能夠進行對集合進行一些相似SQL的操做,例如篩選,排序,分組等,極大提升編碼效率java

而操做使用鏈式編程,只須要在源操做上.xxx()方法便可使用mysql

特色:sql

  1. 不是數據結構,不會存儲數據
  2. 惰性求值,中間操做只對操做進行記錄,只有執行結束操做時纔會進行求值操做
  3. 不會修改原來數據源,Stream的操做會保存在另外一個新的對象中

Stream基礎操做分類編程

中間操做 終止操做
filter allMatch
limit anyMatch
skip noneMatch
distinct findFirst
map findAny
flatMap count,max,min
sorted reduce
collect

操做Stream只須要3步 :數組

  1. 建立Stream,獲得一個數據源,經過例如集合,數組,來獲取一個流
  2. 中間操做對數據進行處理
  3. 終止操做,執行中間操做,產生結果

Stream的任何操做都是在stream對象上進行的,也就是說,任何Stream操做首先都須要獲取到Stream對象數據結構

建立Stream流

  1. 能夠經過Collection系列集合提供的stream()串行流或parallelStream並行流來獲取流ide

    public void test(){
        List list=new ArrayList();
        //獲取串行流
        list.stream();
        //獲取並行流
        list.parallelStream();
    }

    對於串行和並行如今能夠簡單理解爲 串行上的操做是在一個線程中依次完成,而並行是在多個線程上同時執行,咱們如今僅須要使用串行流便可函數

  2. 經過Arrays中的靜態方法stream來獲取數組流工具

    public void test(){
        User [] users=new User[1];
        Stream<User> stream = Arrays.stream(users);
    }
  3. 經過Stream的靜態方法of()

    public void test(){
        String[] strings=new String[10];
        Stream<String> stream = Stream.of(strings);
    }

那麼如今已經獲取到Stream流了,咱們能夠進行相似SQL的操做

爲了方便演示,建立Student類

public class Student {
    //學生姓名
    private String name;
    //學生年齡
    private int age;
    //學生所在年級
    private int grade;
    //性別,1男0女
    private int sex;
    //省略構造,get,set,toString方法
}

在類初始化時添加一些數據

private static  List<Student> studentList =new ArrayList<>();
//用於下面演示
private static Student student =new Student("小明",7,1,1);
static{
    studentList.add(student);
    studentList.add(student);
    studentList.add(new Student("小花",7,2,0));
    studentList.add(new Student("小李",9,3,0));
    studentList.add(new Student("小趙",8,3,1));
    studentList.add(new Student("小王",7,3,0));
    studentList.add(new Student("小亮",6,2,1));
    studentList.add(new Student("小光",6,1,0));
}

中間操做

篩選和切片

  • filter 從流中排除某些元素
  • limit 相似mysql的limit 指定元素最大數量,具備短路功能,能夠提升效率
  • skip(n) 跳過元素,返回跳過n個元素,不足n個返回一個空流
  • distinct 篩選 經過流所生成元素的hashcode和equals去除重複元素

首先來看filter 從流中排除元素

咱們來看一下filter方法參數

Stream<T> filter(Predicate<? super T> predicate);

//看一下傳入參數,函數式接口,可使用Lambda
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

那也就說明,咱們能夠本身定義判斷條件,結果返回一個boolean類型便可

public void test(){  
    Stream<Student> studentStream = studentList.stream()
        .filter((x) -> x.getAge() > 8);
    System.out.println("未執行終止操做前");
    studentStream.forEach(System.out::println);
}
//結果,很明顯只有一個小李年齡大於8歲
未執行終止操做前
User{name='小李', age=9, grade='3', sex=0}

關於上面兩個來解釋一下,能夠理解爲x就是每次循環studentList中那個Student對象,使用lambda簡化了代碼而已,同時咱們證實了全部的中間操做都是惰性求值,只有執行到終止方法纔會真正的進行操做

若是不使用Lambda就是下面這樣,很是的麻煩

studentList.stream()
    .filter(new Predicate(){
        @Override
        public boolean test(Object o) {
            Student student=(Student) o;
            return student.getAge()>8;
        }
    })
    .forEach(new Consumer() {
        @Override
        public void accept(Object o) {
            System.out.println(o);
        }
    });

接下來看limit,相似於SQL中的limit,可是沒有起始值,只有須要的條數

limit具備短路功能,能夠提升效率, 假如list中有不少條數據,limit只須要找到設置的數量就中止查找,減小了資源的浪費

public void test3() {
    studentList.stream()
        .limit(2)
        .forEach(System.out::println);
}
//結果,只顯示兩條
User{name='小明', age=7, grade='1', sex=1}
User{name='小明', age=7, grade='1', sex=1}

skip(n) 跳過n條數據,若是流中數據不足n條,那麼返回個空流, 這裏的空流指流裏沒有數據,而不是返回null

public void test3() {
    studentList.stream()
        .skip(6)
        .forEach(System.out::println);
}
//結果,只獲取到數據的最後兩條
User{name='小亮', age=6, grade='2', sex=1}
User{name='小光', age=6, grade='1', sex=0}
//====咱們來設置跳過條數大點,讓它返回一個空流看看
//由於返回的是個空流,forEach看不到效果,咱們使用另外一箇中間操做count,它返回的是流中數據的數量
public void test3() {
    long count = studentList.stream()
        .skip(100)
        .count(); 
    System.out.println(count);
}
//結果 0

distinct 篩選,經過流所生成元素的hashcode和equals去除重複元素

public void test4() {
    studentList.stream()
        //排除重複
        .distinct()
        .forEach(System.out::println);
}
//結果,只有一個小明,咱們在開始時添加了兩個同一對象的小明,distinct方法根據hashcode和equals去除了
//其餘Student數據....就不展現了

映射

map方法,這個map不是咱們理解的那個相似於hashMap,它的做用是接收一個函數做爲參數,該函數會被應用到每一個元素上,並將其映射爲一個新的元素

什麼意思呢,來看下面的例子,例如數組中存放了學生的英文名字,咱們須要把名字所有轉換爲大寫

@Test
public void test5() {
    String[] names = new String[]{"jame", "joker", "jack"};
    Arrays.stream(names)
        .map((name)-> name.toUpperCase())
        .forEach(System.out::println);
}
//結果
JAME
JOKER
JACK

能夠看到,全部的名字都轉換爲了大寫,那麼意味着數組中全部的字符串都使用了toUpperCase()轉大寫方法

咱們來看下面的這個例子

//這個方法將student對象轉換爲一個Stream<Student >流
public static Stream<Student> getStudentStream(Student student){
    List<Student> studentList=new ArrayList<>();
    studentList.add(student);
    return studentList.stream();
}
//這個方法裏調用getStudentStream方法,將每個學生類都包裝爲Stream類型
public void test() {
    Stream<Stream<Student>> streamStream = studentList.stream()
        .map(MyStream::getStudentStream);
    streamStream.forEach((ss)->{
        ss.forEach(System.out::println);
    });
}
//結果
User{name='小明', age=7, grade='1', sex=1}
User{name='小明', age=7, grade='1', sex=1}
.....

能夠看到在從Stream中獲取Student對象時很麻煩,有簡單點的方法嗎?固然有,來看下面這個方法

flatMap 方法接收一個函數做爲參數,將流中每一個值轉換爲另外一個流,而後把全部流鏈接爲一個流

仍是上面那個例子,咱們將map替換爲flatMap方法,結果和上面一致就不展現了

public void test() {
    studentList.stream()
        .flatMap(MyStream::getStudentStream)
        .forEach(System.out::println);
}

什麼原理呢?咱們上面的方法getStudentStream調用完成後每個學生對象都變爲一個流,可是它們仍是在最初的studentList的流中,相似套娃,流中還有一個流,而flatMap就是將最初的流中的全部流的數據都提取到最初的流中,造成一個流

好比揹包中有一個錢包,錢包裏面有錢,咱們打開揹包,而後打開錢包才能拿到錢,而如今flatMap把錢包中的錢都拿出來放入了揹包中,咱們只須要打開揹包便可拿到

排序

sorted()若是不傳入參數,則根據天然排序

public void test() {
    studentList.stream()
        .sorted()
        .forEach(System.out::println);
}

咱們啓動測試,出現了異常

java.lang.ClassCastException: com.jame.basics.stream.Student cannot be cast to java.lang.Comparable

爲何呢?原來sorted方法的天然排序會調用Comparable接口的compareTo方法,而咱們的Student根本沒有實現這個接口,咱們來實現一下,只是個簡單的判斷,判斷了年齡和年級

@Override
public int compareTo(Object o) {
    Student student = (Student) o;
    if (student.getAge() > this.getAge())
        return 1;
    if (student.getGrade() > this.getGrade())
        return 1;
    return -1;
}

那麼咱們再來試一下sorted方法,看一下結果

User{name='小李', age=9, grade='3', sex=0}
User{name='小趙', age=8, grade='3', sex=1}
User{name='小王', age=7, grade='3', sex=0}
User{name='小花', age=7, grade='2', sex=0}
User{name='小明', age=7, grade='1', sex=1}
User{name='小明', age=7, grade='1', sex=1}
User{name='小亮', age=6, grade='2', sex=1}
User{name='小光', age=6, grade='1', sex=0}

能夠看到已經進行降序的排序了,若是想要升序排序只須要修改Student中的compareTo方法便可

那麼除了實現Comparabl接口來進行排序,還能有其餘辦法嗎? 還有一個辦法

咱們看到除了使用無參,還有個構造能夠傳入一個lambda,咱們能夠在lambda中定義排序規則

public void test() {
    studentList.stream()
        .sorted((s1,s2)->{
            if(s1.getSex()>s2.getSex())
                return 1;
            return -1;
        })
        .forEach(System.out::println);
}
//結果
User{name='小光', age=6, grade='1', sex=0}
User{name='小王', age=7, grade='3', sex=0}
User{name='小李', age=9, grade='3', sex=0}
User{name='小花', age=7, grade='2', sex=0}
User{name='小亮', age=6, grade='2', sex=1}
User{name='小趙', age=8, grade='3', sex=1}
User{name='小明', age=7, grade='1', sex=1}
User{name='小明', age=7, grade='1', sex=1}

咱們定義了根據性別排序,女生排在前面

終止操做

全部的終止操做使用後會直接返回結果,後面不容許在進行任何操做

查找與匹配

allMatch 查找全部元素是否符合條件

//判斷全部人的年齡是否都等於8
public void test10(){
    boolean b = studentList.stream()
        .allMatch((x) -> x.getAge() == 8);
}
//結果 false

anyMatch 查找是否有一個符合條件

//判斷是否有一個學生年齡爲8
public void test10(){
    boolean b = studentList.stream()
        .anyMatch((x) -> x.getAge() == 8);
}
//結果 true

noneMatch 查找是否有不符合條件的 全都不知足纔會返回true

//判斷是否有年齡不爲8的
public void test10(){
    boolean b = studentList.stream()
        .noneMatch((x) -> x.getAge() == 8);
    System.out.println(b);
}
//false 雙重否認就是確定,也就是說學生中有年齡爲8歲的

findFirst 返回第一個元素

public void test10(){
    Optional<Student> first = studentList.stream()
        .findFirst();
    System.out.println(first.get());
}
//結果 User{name='小明', age=7, grade='1', sex=1}
//爲何返回的是一個Optional呢?咱們能夠簡單理解爲Optional就是對Student的一個包裝
//由於在Stream中,只有可能出現null的地方,都會返回一個包裝類

findAny 返回任意一個元素

public void test10(){
    Optional<Student> first = studentList.stream()
        .findAny();
    System.out.println(first.get());
}
//這個方法通常和filter 方法一塊兒使用,單獨使用的話通常返回的都是第一個對象

count 返回Stream流中總數

public void test11() {
    long count = studentList.stream()
        .count();
    System.out.println(count);
}
//結果 8

max 返回流中最大對象,咱們能夠自定義判斷條件

//這裏咱們使用學生類的年齡做爲判斷條件
public void test10() {
    Optional<Student> student = studentList.stream()
        .max((s1, s2) -> {
            if (s1.getAge() > s2.getAge())
                return 1;
            return -1;
        });
    System.out.println(student.get());
}
//結果  User{name='小李', age=9, grade='3', sex=0}

min 返回流中最小對象 一樣,咱們也能夠自定義判斷的條件

//使用學生類的年級來判斷
public void test12() {
    Optional<Student> student = studentList.stream()
        .min((s1, s2) -> {
            if (s1.getGrade() > s2.getGrade())
                return 1;
            return -1;
        });
    System.out.println(student.get());
}
//結果  User{name='小明', age=7, grade='1', sex=1}

歸約

reduce 能夠將流中元素反覆結合起來,獲得一個值,來看下面例子

public void test15() {
    List<Integer> list=new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    Optional<Integer> reduce = list.stream()
        .reduce((x, y) -> { 
            System.out.println(x+"=="+y);
            return x+y;
        });
    System.out.println(reduce.get());
}
//結果 
x:1==y:2
x:3==y:3
x:6==y:4
10

咱們能夠清楚的看到第一次進入這個方法時,流中的第一條數據賦值給了x,而y賦值了第2條數據,當執行完一遍後將結果賦值給了x,y的數據則依次迭代下去

還有一個注意的點: 返回的類型可能爲空,因此Stream使用了Optional來包裝

reduce還能夠設置起始值

public void test15() {
    List<Integer> list=new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);

    Integer reduce = list.stream()
        .reduce(5, (x, y) -> {
            System.out.println("x:" + x + "==y:" + y);
            return x + y;
        });
    System.out.println(reduce);
}
//結果
x:5==y:1
x:6==y:2
x:8==y:3
x:11==y:4
15

當設置起始值後,第一次x的值爲設置的起始值,而y則爲流中第一條數據的值,之後的遍歷每次都會把return的值賦值給x,y的值則根據流中得數據迭代賦值

咱們能夠看到返回的類型已經不是Optional,緣由也能夠想到,由於賦值了起始值,因此不用怕傳入的流中沒有數據了

下面是一個小例子,用一個Student對象來保存整個學生集合中年齡最大的值,班級最大的值

public void test13() {
    Student s = new Student();
    Optional<Student> reduce = studentList.stream()
        .reduce((x, y) -> {
            if (x.getAge() < y.getAge())
                s.setAge(y.getAge());
            if (x.getGrade() < y.getGrade())
                s.setGrade(y.getGrade());
            return s;
        });
    System.out.println(reduce.get());
    //結果  User{name='null', age=9, grade='3', sex=0}

若是不太理解能夠本身試試

收集

若是咱們想經過Stream流來獲取List,Map,Set怎麼辦呢?

咱們能夠經過collect方法,將流轉換爲其餘形式,接收一個Collector接口的實現用於給Stream中元素作總彙的方法

例如咱們想把全部學生名字轉換爲一個Set集合

能夠看到須要一個Collector收集器接口來進行須要的收集操做,而JDK提供了一個Collectors的工具類來提供不一樣的收集操做

public void test16() {
    Set<String> nameSet = studentList.stream()
        //map接收一個函數做爲參數,該函數會被應用到每一個元素上,並將其映射爲一個新的元素
        //而這個函數是getName,能夠理解爲每一個對象都調用getName方法,那麼返回的就全都是一個String類型的
        .map(Student::getName)
        //toSet(),將結果收集爲一個Set集合
        .collect(Collectors.toSet());
    for (String s : nameSet) {
        System.out.println(s);
    }
}
//結果小光
小明
小李
....

相似的操做還有toList,就再也不演示,也可使用toCollection來生成Collection接口類型下的其餘集合

public void test16() {
    studentList.stream()
        .map(Student::getName)
        .collect(Collectors.toCollection(HashSet::new));
}

也能夠經過收集器來獲取平均值

public void test16() {
    Double sAveragin = studentList.stream()
        //還有averagingInt averagingLong 就是結果類型不一樣,一個爲int 一個爲long
        .collect(Collectors.averagingDouble(Student::getAge));
    System.out.println(sAveragin);
}
//結果 7.125

獲取總和

public void test16() {
    Double sAveragin = studentList.stream()
        //和上面獲取平均值同樣,還有不一樣的返回類型,相似summingInt等等
        .collect(Collectors.summingDouble(Student::getAge));
    System.out.println(sAveragin);
}
//結果 57.0

分組

相似於SQL的分組,那麼它的返回類型確定是一個map,該怎麼寫呢?來看下面的例子

//咱們根據年齡分組
public void test17() {
    Map<Integer, List<Student>> collect = studentList.stream()
        .collect(Collectors.groupingBy(Student::getAge));
    Set<Integer> integers = collect.keySet();
    for (Integer integer : integers) {
        System.out.println("key:"+integer+"===value:"+collect.get(integer));
    }
}
//結果
key:6===value:[User{name='小亮', age=6, grade='2', sex=1}, User{name='小光', age=6, grade='1', sex=0}]
key:7===value:[User{name='小明', age=7, grade='1', sex=3}, User{name='小明', age=7, grade='1', sex=3}, User{name='小花', age=7, grade='2', sex=0}, User{name='小王', age=7, grade='3', sex=0}]
key:8===value:[User{name='小趙', age=8, grade='3', sex=1}]
key:9===value:[User{name='小李', age=9, grade='3', sex=0}]

多級分組

好比咱們先按照年級分組,而後根據性別分組該怎麼辦呢?這時候就須要使用多級分組了

咱們能夠看到,這個Collectors.groupingBy()方法中還一個再傳入一個Collectors對象,一切都明白了,再寫一個收集器放入就能夠了,來看下面例子

public void test18() {
    Map<Integer, Map<Integer, List<Student>>> map = studentList.stream()
        .collect(Collectors.groupingBy(
            (x) -> x.getGrade(),
            Collectors.groupingBy((y) -> y.getSex())
        ));
    Set<Integer> keySet = map.keySet();
    for (Integer k : keySet) {
        System.out.println("年級爲:"+k);
        Map<Integer, List<Student>> map1 = map.get(k);
        Set<Integer> keySet1 = map1.keySet();
        for (Integer k1 : keySet1) {
            System.out.println(map1.get(k1));
        }
    }
}
}
//結果
年級爲:1
[User{name='小光', age=6, grade='1', sex=0}]
[User{name='小明', age=7, grade='1', sex=3}, User{name='小明', age=7, grade='1', sex=3}]
年級爲:2
[User{name='小花', age=7, grade='2', sex=0}]
[User{name='小亮', age=6, grade='2', sex=1}]
年級爲:3
[User{name='小李', age=9, grade='3', sex=0}, User{name='小王', age=7, grade='3', sex=0}]
[User{name='小趙', age=8, grade='3', sex=1}]

若是有多個條件,只須要在後面添加Collectors.groupingBy便可,不過條件多了取的時候就很是麻煩了

咱們也能夠根據數值本身設置key的值,須要注意的是key的類型必須和當前分組的類型一致

public void test18() {
    Map<Integer, Map<Integer, List<Student>>> map = studentList.stream()
        .collect(Collectors.groupingBy(
            (x) -> {
                if(x.getGrade()==1){
                    return 111;
                }else if(x.getGrade()==2){
                    return 222;
                }else {
                    return 333;
                }
            },
            Collectors.groupingBy((y) -> y.getSex())
        ));
    Set<Integer> keySet = map.keySet();
    for (Integer k : keySet) {
        System.out.println("最外層k的值:"+k);
        Map<Integer, List<Student>> map1 = map.get(k);
        Set<Integer> keySet1 = map1.keySet();
        for (Integer k1 : keySet1) {
            System.out.println(map1.get(k1));
        }
    }
}
//結果
最外層k的值:333
[User{name='小李', age=9, grade='3', sex=0}, User{name='小王', age=7, grade='3', sex=0}]
[User{name='小趙', age=8, grade='3', sex=1}]
最外層k的值:222
[User{name='小花', age=7, grade='2', sex=0}]
[User{name='小亮', age=6, grade='2', sex=1}]
最外層k的值:111
[User{name='小光', age=6, grade='1', sex=0}]
[User{name='小明', age=7, grade='1', sex=3}, User{name='小明', age=7, grade='1', sex=3}]

partitioningBy 咱們還能夠經過某些判斷根據true和false來分組

public void test20() {
    Map<Boolean, List<Student>> map = studentList.stream()
        .collect(Collectors.partitioningBy((x) -> x.getGrade() >= 2));
    Set<Boolean> booleans = map.keySet();
    for (Boolean aBoolean : booleans) {
        System.out.println("年級是否大於等於2年級:"+aBoolean);
        List<Student> studentList = map.get(aBoolean);
        for (Student student : studentList) {
            System.out.println(student);
        }
    }
}
//結果
年級是否大於等於2年級:false
User{name='小明', age=7, grade='1', sex=3}
User{name='小明', age=7, grade='1', sex=3}
User{name='小光', age=6, grade='1', sex=0}
年級是否大於等於2年級:true
User{name='小花', age=7, grade='2', sex=0}
User{name='小李', age=9, grade='3', sex=0}
User{name='小趙', age=8, grade='3', sex=1}
User{name='小王', age=7, grade='3', sex=0}
User{name='小亮', age=6, grade='2', sex=1}

summarizingInt 能夠直接獲取全部元素的某一項數值得最大值,最小值,平均值,總和,咱們一塊兒來看下面的例子

public void test21() {
    IntSummaryStatistics collect = studentList.stream()
        .collect(Collectors.summarizingInt((x)->x.getAge()));
    System.out.println("學生年齡的最大值:"+collect.getMax());
    System.out.println("學生年齡的最小值:"+collect.getMin());
    System.out.println("學生年齡的平均值:"+collect.getAverage());
    System.out.println("學生年齡的總和:"+collect.getSum());
}
//結果
學生年齡的最大值:9
學生年齡的最小值:6
學生年齡的平均值:7.125
學生年齡的總和:57

還有相似的例如summarizingDouble,summarizingLong,只不過是返回的類型不一樣,這裏再也不展現

鏈接操做

能夠經過join方法將結果收集在一塊兒,下面例子

public void test22() {
    String collect = studentList.stream()
        .map(Student::getName)
        .collect(Collectors.joining());
    System.out.println(collect);
}
//結果  小明小明小花小李小趙小王小亮小光

還能夠設置分割符,開頭符號,結尾符號

public void test22() {
    String collect = studentList.stream()
        .map(Student::getName)
        .collect(Collectors.joining(",","我是開頭符號","我是結尾符號"));
    System.out.println(collect);
}
//結果
我是開頭符號小明,小明,小花,小李,小趙,小王,小亮,小光我是結尾符號

到此,Stream的基本使用就完成了,感謝閱讀

本文僅我的理解,若是有不對的地方歡迎評論指出或私信,謝謝٩(๑>◡<๑)۶

相關文章
相關標籤/搜索