1. 程式人生 > >你真的開始用JDK8了嗎?(下)

你真的開始用JDK8了嗎?(下)

上篇文章中,看到了JDK8中的Optional和Lambda Expressions帶來程式設計上的改變,甚至程式設計思維的改變。接下來我們繼續看JDK8的Stream和Interface default method給我們帶來的改變

Stream

Stream也是JAVA8的一大特點,這裡的Stream和IO的那個Stream不同,它提供了對集合操作的增強,極大的提高了操作集合物件的便利性。下面我們就通過一個示例來看,使用Stream給我們帶來哪些改變。

有一批學生考試資料,包括學生ID,班級ID,學科,分數。來計算如下指標

  1. 找出所有語文科目,分數大於60分的學生
  2. 找出語文科目,排名第一的學生
  3. 計算學生ID為10的學生的總分
  4. 所有學生按照總分從大到小

在JDK8之前,實現以上功能也非常簡單的,相信對於一個剛剛學習Java的工程師來說,也很容易實現。不妨大家給自己設定一個限制,如何使用最少的程式碼實現以上功能。這裡留作一個問題思考,下面我們使用Stream API來實現這些需求.

篩選:filter用法

找出所有語文科目,分數大於60分的學生

studentScores.stream()
        .filter(s -> "語文".equals(s.getSubject()) && s.getScore() >= 60f)
        .collect(Collectors.toList())
        .forEach(System.out::println);

排序:sorted用法

找出語文科目,排名第一的學生

Optional<StudentScore> studentScore = studentScores.stream()
        .filter(s -> "語文".equals(s.getSubject()))
        .sorted((s1, s2) -> s1.getScore() > s2.getScore() ? -1 : 1)
        .findFirst();
if (studentScore.isPresent()) {
    System.out.println(studentScore.get());
}

統計計算:reduce

計算學生ID為10的學生的總分

Double total = studentScores.stream()
        .filter(s -> s.getStudentId() == 10)
        .mapToDouble(StudentScore::getScore)
        .reduce(0, Double::sum);
System.out.println(total);
分組統計:Collectors

所有學生按照總分從大到小

studentScores.stream()
        .collect(Collectors.groupingBy(StudentScore::getStudentId
                , Collectors.summingDouble(StudentScore::getScore)))
        .entrySet()
        .stream()
        .sorted((o1, o2) -> o1.getValue() < o2.getValue() ? 1 : -1)
        .forEach(System.out::println);

以上的示例已經包括了Stream API的大部分的功能。從以上可以看到,在進行計算時,總是需要使用在集合物件中使用stream()方法,先轉成Stream然後在進行後面的操作,為什麼不直接在集合類下直接實現如下操作呢?Stream和集合類有哪些區別?Oracle給出瞭如下說明

  1. Stream沒有儲存,它不是資料結構,並不儲存資料。它可以像陣列、生成器等資料來源獲取資料,通過一個計算流進行操作
  2. 在功能性質上,通過流的操作,不會修改資料來源,比如,filter操作,是從集合的流上獲取一個新的流,而不是將過濾掉的元素從集合上刪除
  3. 延遲計算,許多流式的計算像filter、map等,是通過懶式的實現。一個數據流操作包括三個基本步驟,資料來源->資料轉換->執行獲取結果。每次轉換原有 Stream 物件不改變,返回一個新的 Stream 物件。資料轉換的操作都是lazy的
  4. 可以支援無限的大小。雖然集合是有限的,但是流是可以支援無限大小的,像limit(n)或者findFirst可以讓無限的流操作在有限的時間內完成
  5. 流的元素只能在一次建立中被訪問到一次,像Iterator一樣,必須生成一個新的流來訪問新的元素

Interface default method

在JDK8中,使用forEach方法可以直接遍歷陣列,當然們進入檢視forEach檢視原始碼時,我們在Iterable介面可以看到如下程式碼:

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

這不是方法的實現麼?是的,在接口裡可以寫實現了。在JDK8中為了支援新特性,必須對原有介面進行改造。需要在不破壞現有的架構情況下在接口裡增加新方法。這也是JAVA8引入Default method的原因。但是引入Default method之後,需要思考兩個問題:

和抽象類區別

當介面有了default method 之後,介面看起來和抽象性是一樣的,但是他們兩個在Java8中還是有區別的。

抽象類,有自己的構造方法,並且可以定義自己的資料結構,但是預設方法只能呼叫介面時使用,並不會涉及到特定介面實現類的狀態。具體使用介面還是抽象類還需要根據具體業務場景來界定

介面的多繼承問題

在java中可以支援多繼承,如果兩個介面實現了同樣的預設方法,那麼應該使用哪個呢?

比如:

public interface DemoA {

    default void test() {
        System.out.println("I'm DemoA.");
    }
}

public interface DemoB {

    default void test() {
        System.out.println("I'm DemoB.");
    }
}

如果一個DemoImpl繼承以上兩個介面,程式碼如下:

public class DemoImpl implements DemoA, DemoB {
}

這時,IDE會在DemoImpl下面有一條紅線,提示不能繼承在DemoA和DemoB中的test方法,需要實現該方法

public class DemoImpl implements DemoA, DemoB {
    @Override
    public void test() {
        DemoB.super.test();
        DemoA.super.test();;
    }
}

實現該方法,和其他方式類似,你可以呼叫父類中的方法,也可以直接自己實現

Other features

除了以上的一些特性,JDK8中還支援了其他的一些特性值得關注

  1. 引入了新的Date-Time API(JSR 310)來改進時間、日期的處理。
  2. 引入新的Nashorn JavaScript引擎,使得我們可以在JVM上開發和執行JS應用。
  3. 引入了Base64編碼的支援
  4. 新增了支援陣列的並行處理的parallelSort方法等等

歡迎關注我的公眾號MyArtNote

MyArtNote