2020了你還不會Java8新特性?(三)方法引用詳解及Stream 流介紹和操做方式詳解

方法引用詳解

方法引用: method referencejava

方法引用其實是Lambda表達式的一種語法糖sql

咱們能夠將方法引用看做是一個「函數指針」,function pointer編程

方法引用共分爲4類:

  1. 類名::靜態方法名
  2. 引用名(對象名)::實例方法名
  3. 類名::實例方法名 (比較很差理解,個地方調用的方法只有一個參數,爲何還能正常調用呢? 由於調用比較時,第一個對象來調用getStudentByScore1. 第二個對象來當作參數)
  4. 構造方法引用: 類名::new
public class StudentTest {

    public static void main(String[] args) {
        Student student = new Student("zhangsan",10);
        Student student1 = new Student("lisi",40);
        Student student2 = new Student("wangwu",30);
        Student student3 = new Student("zhaoliu",550);

        List<Student> list = Arrays.asList(student, student2, student3, student1);

//        list.forEach(item -> System.out.println(item.getName()));

        //1. 類名 :: 靜態方法
//        list.sort((studentpar1,studentpar2) -> Student.getStudentByScore(studentpar1,studentpar2));
        list.sort(Student::getStudentByScore);
        list.forEach(item -> System.out.println(item.getScore()));
        System.out.println(" - - - - - - - -- ");

        // 2. 引用名(對象名)::實例方法名
        StudentMethod studentMethod = new StudentMethod();
        list.sort(studentMethod::getStudentBySource);
        list.forEach(item -> System.out.println(item.getScore()));
        System.out.println(" - - - -- -- ");

        // 3. 類名:: 實例方法名
        // 這個地方調用的方法只有一個參數,爲何還能正常調用呢? 由於調用比較時,第一個對象來調用getStudentByScore1. 第二個對象來當作參數
        list.sort(Student::getStudentByScore1);
        list.forEach(item -> System.out.println(item.getScore()));
        System.out.println("- - - - - - - -");

        // 原生的sort 來舉個例子
        List<String> list1 = Arrays.asList("da", "era", "a");
//        Collections.sort(list1,(city1,city2) -> city1.compareToIgnoreCase(city2));
        list1.sort(String::compareToIgnoreCase);
        list1.forEach(System.out::println);
        System.out.println("- - - - - - -- ");

        //4. 構造方法引用
        StudentTest studentTest = new StudentTest();
        System.out.println(studentTest.getString(String::new));
    }

    public String getString(Supplier<String> supplier) {
        return supplier.get()+"hello";
    }

}

默認方法

defaute methodapi

默認方法是指實現此接口時,默認方法已經被默認實現。數組

引入默認方法最重要的做用就是Java要保證向後兼容。多線程

情景一: 一個類,實現了兩個接口。兩個接口中有一個相同名字的默認方法。此時會報錯,須要從寫這個重名的方法框架

情景二: 約定:實現類的優先級比接口的優先級要高。 一個類,實現一個接口,繼承一個實現類。接口和實現類中有一個同名的方法,此時,此類會使用實現類中的方法。dom


Stream 流介紹和操做方式詳解

Collection提供了新的stream()方法。函數式編程

流不存儲值,經過管道的方式獲取值。函數

本質是函數式的,對流的操做會生成一個結果,不過並不會修改底層的數據源,集合能夠做爲流的底層數據源。

延遲查找,不少流操做(過濾、映射、排序等)等能夠延遲實現。

經過流的方式能夠更好的操做集合。使用函數式編程更爲流程。與lambda表達式搭配使用。

流由3部分構成:

  1. 零個或多箇中間操做(操做的是誰?操做的是源)
  2. 終止操做(獲得一個結果)

流操做的分類:

  1. 惰性求值(中間操做)
  2. 及早求值(種植操做)

使用鏈式的調用方式sunc as : stream.xxx().yyy().zzz().count(); 沒有count的時候前邊的三個方法不會被調用。後續會進行舉例。

掌握流經常使用的api,瞭解底層。

流支持並行化,能夠多線程操做。迭代器不支持並行化。

流怎麼用?

流的建立方式

  1. 經過靜態方法 : Stream stream = Stream.of();
  2. 經過數組:Arrays.stream();
  3. 經過集合建立對象:Stream stream = list.stream;

流的簡單應用

public static void main(String[] args) {
        IntStream.of(1,2,4,5,6).forEach(System.out::println);
        IntStream.range(3, 8).forEach(System.out::println);
        IntStream.rangeClosed(3, 8).forEach(System.out::println);
    }

舉例:將一個數組中的數字都乘以二,而後求和。

public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        System.out.println(list.stream().map(i -> i*2).reduce(0,Integer::sum));
    }

函數式編程和傳統面向對象編程根本上有什麼不一樣?

傳統面向對象編程傳遞的是數據。函數式編程經過方法傳遞的是一種行爲,行爲指導了函數的處理,根據行爲對數據進行加工。

舉例:流轉換成list的練習

public static void main(String[] args) {

        Stream<String> stream = Stream.of("hello", "world", "hello world");
//        String[] stringArray = stream.toArray(length -> new String[length]);
        //替換成方法引用的方式  --> 構造方法引用.
        String[] stringArray = stream.toArray(String[]::new);
        Arrays.asList(stringArray).forEach(System.out::println);
        System.out.println("- - - - - - - - - - -");

        //將流轉換成list,   有現成的封裝好的方法
        Stream<String> stream1 = Stream.of("hello", "world", "hello world");
        List<String> collect = stream1.collect(Collectors.toList());// 自己是一個終止操做
        collect.forEach(System.out::println);

        System.out.println("- - - - - - ");

        //使用原生的 collect 來將流轉成List
        Stream<String> stream2 = Stream.of("hello", "world", "hello world");
//        List<String> lis = stream2.collect(() -> new ArrayList(), (theList, item) -> theList.add(item),
//                (theList1, theList2) -> theList1.addAll(theList2));
        // 將上面的轉換成方法引用的方式  -- 這種方法很差理解.
        List<String> list = stream2.collect(LinkedList::new, LinkedList::add, LinkedList::addAll);
        //這種方法,若是想要返回ArrayList也能夠實現.
//        List<String> list1 = stream2.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
        list.forEach(System.out::println);
    }

Collectors類中包含了流轉換的多個輔助類

舉例: 將流 轉成各類類型的數據。

public static void main(String[] args) {
        Stream<String> stream = Stream.of("hello", "world", "hello world");

        //將流轉換成List 另外一種方法
//        List<String> list= stream.collect(Collectors.toCollection(ArrayList::new));
//        list.forEach(System.out::println);

        //將流轉成set
//        Set<String> set = stream.collect(Collectors.toSet());
        //轉成TreeSet
//        TreeSet<String> set = stream.collect(Collectors.toCollection(TreeSet::new));
//        set.forEach(System.out::println);

        //轉成字符串
        String string = stream.collect(Collectors.joining());
        System.out.println(string);

        //Collectors 類中有多重輔助的方法.

    }

遇到問題的時候,先思考一下可否用方法引用的方式,使用流的方式來操做。由於用起來比較簡單。

舉例:將集合中的每個元素 轉換成大寫的字母, 給輸出來。

public static void main(String[] args) {
        //將集合中的每個元素 轉換成大寫的字母, 給輸出來
        List<String> list = Arrays.asList("hello","world","hello world");

        //轉成字符串,而後轉成大寫.
//        System.out.println(list.stream().collect(Collectors.joining()).toUpperCase());
        //上面的代碼 能夠轉換成下邊的代碼.
//        System.out.println(String.join("", list).toUpperCase());

        //視頻上給出的   仍是List的大寫
        list.stream().map(String::toUpperCase).collect(Collectors.toList()).forEach(System.out::println);
  
  //將集合 的數據給平方一下輸出.
        List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5);
        list1.stream().map(item -> item * item).collect(Collectors.toList()).forEach(System.out::println);

}

流中的 .map () 方法,是對集合中的每個數據進行一下操做。

stream 的 flat操做。 打平操做。

public static void main(String[] args) {
// 舉例:   flag 的操做, 打平. 一個集合中有三個數組, 打平以後,三個數組的元素依次排列.
Stream<List<Integer>> stream = Stream.of(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5));
//將裏邊每個ArrayList的數據 作一個平方.  而後打平. 輸出一個list
stream.flatMap(theList -> theList.stream()).map(item -> item * item).forEach(System.out::println);
}

Stream 其餘方法介紹:

public static void main(String[] args) {
//        stream 其餘方法介紹.

        //  generate(). 生成stream對象
        Stream<String> stream = Stream.generate(UUID.randomUUID()::toString);
//        System.out.println(stream.findFirst().get());
// findFirst,找到第一個對象.而後就短路了,會返回一個Optional對象(爲了不NPE),不符合函數式編程
//        stream.findFirst().isPresent(System.out::print);

        // iterate()     會生成 一個 無限的串行流.
        // 通常不會單獨使用. 會使用limit  來限制一下總長度.
        Stream.iterate(1, item -> item + 2).limit(6).forEach(System.out::println);
    }

Stream 運算練習:(Stream提供了各類操做符)

舉例:找出該流中大於2的元素,而後每一個元素*2 ,而後忽略掉流中的前兩個元素,而後再取流中的前兩個元素,最後求出流元素中的總和.

Stream<Integer> stream = Stream.iterate(1, item -> item + 2).limit(6);
        //找出該流中大於2的元素,先使用filter()過濾.
        //每一個元素*2 使用mapToInt 避免重複拆箱.
        //忽略掉流中的前兩個元素; 使用 skip(2)
        //再取流中的前兩個元素;  使用limit(2)
        //求出流元素中的總和.  使用sum()
System.out.println(stream.filter(item -> item>2).mapToInt(item -> item * 2).skip(2).limit(2).sum());

舉例:找出該流中大於2的元素,而後每一個元素*2 ,而後忽略掉流中的前兩個元素,而後再取流中的前兩個元素,最後找到最小的元素.

// .min() 返回的是IntOptional.
//        System.out.println(stream.filter(item -> item>2).mapToInt(item -> item * 2).skip(2).limit(2).min());
        //應該這樣調用. 上邊的可能會出NPE異常
        stream.filter(item -> item>2).mapToInt(item -> item * 2).skip(2).limit(2).min().ifPresent(System.out::println);

舉例:獲取最大值,最小值,求和等各類操做。 .summaryStatistics();

image-20200104172236805

在練習的過程當中發現了一個問題。若是是這樣連續打印兩條對流操做以後的結果。會報流未關閉的異常。

image-20200104170801230

image-20200104172608140

注意事項:流被重複使用了,或者流被關閉了,就會出異常。

如何避免:使用方法鏈的方式來處理流。 具體出現的緣由,後續進行詳細的源碼講解。


舉例 :中間操做(惰性求值) 和停止操做(及早求值)本質的區別

public static void main(String[] args) {
    List<String> list = Arrays.asList("hello", "world", "hello world");

    //首字母轉大寫
    list.stream().map(item ->{
        String s = item.substring(0, 1).toUpperCase() + item.substring(1);
        System.out.println("test");
        return s;
    }).forEach(System.out::println);
    //沒有遇到停止操做時,是不會執行中間操做的.是延遲的
    // 遇到.forEach() 停止操做時,纔會執行中間操做的代碼
}

舉例:流使用順序不一樣的區別

//程序不會中止
IntStream.iterate(0,i->(i+1)%2).distinct().limit(6).forEach(System.out::println);
//程序會中止
IntStream.iterate(0,i->(i+1)%2).limit(6).distinct().forEach(System.out::println);

Stream底層深刻

  • 和迭代器不一樣的是,Stream能夠並行化操做,迭代器只能命令式地、串行化操做

  • 當使用穿行方式去遍歷時,每一個item讀完後再讀下一個item
  • 使用並行去遍歷時,數據會被分紅多個段,其中每個都在不一樣的線程中處理,而後將結果一塊兒輸出。
  • Stream的並行操做依賴於Java7中引入的Fork/Join框架。

流(Stream)由3部分構成:

  1. 源(Source)
  2. 零個或多箇中間操做(Transforming values)(操做的是誰?操做的是源)
  3. 終止操做(Operations)(獲得一個結果)

內部迭代和外部迭代

描述性的語言:sql和Stream的對比

select name from student where age > 20 and address = 'beijing' order by desc;

===================================================================================

Student.stream().filter(student -> student.getAge >20 ).filter(student -> student.getAddress().equals("beijing")).sorted(..).forEach(student -> System.out.println(student.getName));

上述的描述,並無明確的告訴底層具體要怎麼作,只是發出了描述性的信息。這種流的方式就叫作內部迭代。針對於性能來講,流的操做確定不會下降性能。

外邊迭代舉例: jdk8之前的用的方式。

List list = new ArrayList<>();

for(int i = 0 ;i <= students.size();i++){

​ Student student = students.get(i);

If(student.getAge() > 20 )

​ list.add(student);

}

Collections.sort(list.....)

list.forEach().....

Stream的出現和集合是密不可分的。

集合關注的是數據與數據存儲自己,流關注的則是對數據的計算。

流與迭代器相似的一點是:流是沒法重複使用或消費的。

如何區分中間操做和停止操做:

中間操做都會返回一個Stream對象,好比說返回Stream ,Stream ,Stream ;

停止操做則不會返回Stream類型,可能不返回值,也可能返回其餘類型的單個值。


並行流的基本使用

舉例: 串行流和並行流的簡單舉例比較

public static void main(String[] args) {
    // 串行流和並行流的比較
    List<String> list = new ArrayList<>(5000000);

    for (int i = 0; i < 5000000; i++) {
        list.add(UUID.randomUUID().toString());
    }

    System.out.println("開始排序");
    long startTime = System.nanoTime();
    //   list.parallelStream().sorted().count(); //串行流
    list.parallelStream().sorted().count(); //並行流
    long endTime = System.nanoTime();
    long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
    System.out.println("排序時間爲: "+ millis);
}

image-20200104183834756

image-20200104183924340

結果如圖,並行流和串行流時間上錯了4倍。

舉例: 打印出列表中出來第一個長度爲5的單詞.. 同時將長度5打印出來.

public static void main(String[] args) {
        List<String> list = Arrays.asList("hello", "world", "hello world");

//        list.stream().mapToInt(item -> item.length()).filter(length -> length ==5)
//                      .findFirst().ifPresent(System.out::println);

        list.stream().mapToInt(item -> {
            int length = item.length();
            System.out.println(item);
            return length;
        }).filter(length -> length == 5).findFirst().ifPresent(System.out::println);
    //返回的是hello  , 不包含 world.  
    }

返回的是hello , 不包含 world.

流的操做原理: 把流想成一個容器,裏邊存儲的是對每個元素的操做。操做時,把操做串行化。對同一個元素進行串行的操做。操做中還包含着短路操做。

舉例: 找出 這個集合中全部的單詞,並且要去重. flatMap()的使用。

public static void main(String[] args) {
        //舉例; 找出 這個集合中全部的單詞,並且要去重.
        List<String> list = Arrays.asList("hello welcome", "world hello", "hello world", "hello hello world");

//        list.stream().map(item -> item.split(" ")).distinct()
//                .collect(Collectors.toList()).forEach(System.out::println);

        //使用map不能知足需求, 使用flatMap
        list.stream().map(item -> item.split(" ")).flatMap(Arrays::stream)
                .distinct().collect(Collectors.toList()).forEach(System.out::println);

        //結果爲  hello  welcome world 
    }

舉例:組合起來. 打印出 hi zhangsan , hi lisi , hi wangwu , hello zhangsan , hello lisi .... flatMap()的使用。

public static void main(String[] args) {

    //組合起來. 打印出  hi zhangsan , hi lisi , hi wangwu , hello zhangsan , hello lisi ....
    List<String> list = Arrays.asList("Hi", "Hello", "你好");
    List<String> list1 = Arrays.asList("zhangsan", "lisi", "wangwu");

    List<String> collect = list.stream().flatMap(item -> list1.stream().map(item2 -> item + " " +
            item2)).collect(Collectors.toList());

    collect.forEach(System.out::println);

}

舉例: 流對分組/分區操做的支持. group by / protition by

public static void main(String[] args) {
        //數據準備.
        Student student1 = new Student("zhangsan", 100, 20);
        Student student2 = new Student("lisi", 90, 20);
        Student student3 = new Student("wangwu", 90, 30);
        Student student4 = new Student("zhangsan", 80, 40);
        List<Student> students = Arrays.asList(student1, student2, student3, student4);

        //對學生按照姓名分組.
        Map<String, List<Student>> listMap = students.stream().collect(Collectors.groupingBy(Student::getName));
        System.out.println(listMap);

        //對學生按照分數分組.
        Map<Integer, List<Student>> collect = students.stream().collect(Collectors.groupingBy(Student::getScore));
        System.out.println(collect);

        //按照年齡分組.
        Map<Integer, List<Student>> ageMap = students.stream().collect(Collectors.groupingBy(Student::getAge));
        System.out.println(ageMap);

        //按照名字分組後,獲取到每一個分組的元素的個數.
        Map<String, Long> nameCount = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.counting()));
        System.out.println(nameCount);

        //按照名字分組,求得每一個組的平均值.
        Map<String, Double> doubleMap = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.averagingDouble(Student::getScore)));
        System.out.println(doubleMap);
   
            //分區,  分組的一種特例. 只能分兩個組 true or flase .   partitioning  By
        Map<Boolean, List<Student>> collect1 = students.stream().collect(Collectors.partitioningBy(student -> student.getScore() >= 90));
        System.out.println(collect1);

    }
相關文章
相關標籤/搜索