常見面試題:java8有什麼新特性?

主要有以下這些新特性:

  • lambda 表示式,經常配合函式式介面使用,可以有效減少程式碼量

    • Runnable 是一個函式式介面,下面展示了建立執行緒三種寫法,顯然最後一種最簡潔:

      class OldWay implements Runnable {
      @Override
      public void run() {
      System.out.println("最原始的方法");
      }
      } public class Test {
      public static void main(String[] args) { new Thread(new OldWay()).start(); new Thread(new Runnable() {
      @Override
      public void run() {
      System.out.println("匿名內部類");
      }
      }).start(); new Thread(() -> {
      System.out.println("lambda表示式");
      }).start(); }
      }
    • 在 new 一個 Thread 時需要傳入一個 Runnable 介面的實現類

      • 第一種是最原始的做法,先建立一個 class 來實現 Runnable 介面,然後在建立執行緒時傳入這個實現類,太麻煩了
      • 第二種是匿名內部類的寫法,把實現類的名字給省略掉了,稍微方便點,但 run 這個方法名其實也有點冗餘,因為 Runnable 裡面就這麼一個方法,不寫出來應該也沒關係啊
      • 第三種是 lambda 表示式的寫法,把方法名也省略掉了,最簡潔,但注意,如果接口裡有多個方法,那麼只能採用前兩種方法了
    • 更直觀的感受一下 lambda 表示式和函式式介面之間的關係:

      public class Test {
      public static void main(String[] args) {
      Runnable runnable = () -> {
      System.out.println("nb");
      };
      }
      }
    • 另一個常見應用就是集合類的 forEach 方法,需要一個 Consumer 引數,這也是一個函式式介面,裡面的 accept 方法需要一個引數並且沒有返回值(不用記,在 IDEA 裡點進去看就行),一個例子如下,它遍歷 list 中的每個元素,加一後輸出:

      public class Test {
      public static void main(String[] args) {
      ArrayList<Integer> list = new ArrayList<>();
      list.add(1);
      list.add(2);
      list.add(3);
      //2 3 4
      list.forEach((Integer num) -> {
      num = num + 1;
      System.out.println(num);
      });
      }
      }
    • lambda 表示式還有些小細節,比如引數列表中引數的型別其實可以省略,如果程式碼塊裡只有一條語句那麼花括號也可以省略,如果引數列表裡只有一個引數那麼圓括號也可以省略,但其實就算不省略也足夠簡潔了,我覺得沒必要省略

  • 方法引用,感覺有點說不清,可以看個例子,就比如前面遍歷 list,如果我就是想遍歷一次 list 然後輸出,可以用到方法引用:

    public class Test {
    public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3); list.forEach((Integer num) -> {
    System.out.println(num);
    }); list.forEach(System.out::println); }
    }
    • 首先,forEach 是需要一個 Consumer 引數的,這個函式式介面的 accept 方法需要一個引數並且沒有返回值,我們有兩個方案,一個就是自己寫一個 lambda 表示式,另一個就是使用方法引用,直接引用一個已經寫好了的滿足條件的方法,比如這裡的 System.out.println 方法就是需要一個引數的 void 方法,滿足條件,當然我們也可以定製一個滿足條件的方法然後用方法引用的方式來使用,如下:

      class TestReference{
      public static void myPrint(Integer num){
      System.out.println(num);
      }
      }
      public class Test {
      public static void main(String[] args) {
      ArrayList<Integer> list = new ArrayList<>();
      list.add(1);
      list.add(2);
      list.add(3);
      list.forEach(TestReference::myPrint);
      }
      }
  • 函式式介面,前面其實已經提到過了,如果一個接口裡面只有一個方法,那麼這就是一個函式式介面,對於函式式介面,我們可以通過 lambda 表示式或者方法引用來進行快速的實現,而不必新建一個 class 去繼承或者寫一個匿名內部類

  • 預設方法,意思是說,我們在寫一個介面時可以通過 default 關鍵字為其中的方法提供預設的實現方案,使得實現類就算不覆寫這個方法也沒有關係:

    interface TestInterface{
    default void test(){
    System.out.println("here");
    }
    } class TestDefault implements TestInterface{
    //沒有覆寫test方法也沒有報錯
    } public class Test {
    public static void main(String[] args) {
    //here
    new TestDefault().test();
    }
    }
  • Stream API,我們可以把一個集合轉換為流,在這個流上做各種操作,比如查詢、排序、過濾等等

    • 主要有以下這些操作:

      • 中間操作:指操作完成後還是返回一個流物件,可以拿著這個物件繼續操作下去
      • 結束操作:指操作完成後不再返回流物件,一切都結束了
      • 無狀態:指元素的處理不受之前元素的影響,可以挨個處理
      • 有狀態:指該操作只有拿到所有元素之後才能繼續下去
      • 非短路操作:指必須處理完所有元素才能得到最終結果
      • 短路操作:指遇到某些符合條件的元素就可以得到最終結果
    • 來個案例,現在給定一個 list,想要先求出每個元素的平方,然後排序,然後找出 10 和 100 之間的那些元素,然後去除重複元素,最後輸出:

      public class Test {
      public static void main(String[] args) {
      //準備我們的list
      ArrayList<Integer> list = new ArrayList<>();
      int[] ints = {4, 1, 6, 2, 8, 5, 15, 11, 9};
      for (int i : ints) {
      list.add(i);
      }
      //轉換為流
      Stream<Integer> stream = list.stream();
      //第一步,求出每個元素的平方
      stream.map((Integer origin) -> {
      return origin * origin;
      })
      //第二步,排序
      .sorted()
      //第三步,找出10和100之間的那些值
      .filter((Integer num) -> {
      return num >= 10 && num <= 100;
      })
      //第四步,去重
      .distinct()
      //第五步,輸出
      .forEach(System.out::println);
      }
      }
    • 從案例中可以發現,很多流操作是需要一個函式式介面作為引數的,因此一定要搭配前面的 lambda 表示式和方法引用來完成這些流操作,否則程式碼量是過大的

    • 至於到底需要寫什麼樣的 lambda 表示式(幾個引數,返回值是什麼),一定要在 IDEA 裡點進去看,直接背是不現實的

  • 新的 Date Time API,因為 java 中同時存在 java.util.Datejava.sql.Date 兩個時間類,很容易讓人迷惑,而且這兩個包裡的內容也存在諸多問題,因此 java8 中新增了 java.time 這個包來把所有時間類的 API 一網打盡

    • 直接看個案例吧,演示部分 API 的使用:

      public class Test {
      public static void main(String[] args) { //獲取當前的日期時間(年月日+時分秒)
      LocalDateTime currentTime = LocalDateTime.now();
      System.out.println("當前的日期和時間: " + currentTime); //獲取當前的日期(年月日)
      LocalDate date1 = currentTime.toLocalDate();
      System.out.println("date1: " + date1); //分別得到當前的月、日、秒
      Month month = currentTime.getMonth();
      int day = currentTime.getDayOfMonth();
      int seconds = currentTime.getSecond();
      System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds); //把當前的日期時間中的年替換為2012,日替換為10
      LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
      System.out.println("date2: " + date2); //顯式的構造出一個日期
      LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
      System.out.println("date3: " + date3); //顯式的構造出一個時間
      LocalTime date4 = LocalTime.of(22, 15);
      System.out.println("date4: " + date4); //解析字串來得到一個時間
      LocalTime date5 = LocalTime.parse("20:15:30");
      System.out.println("date5: " + date5);
      }
      }
    • 最後的輸出如下:

      當前的日期和時間: 2021-08-24T22:03:43.468015700
      date1: 2021-08-24
      月: AUGUST, 日: 24, 秒: 43
      date2: 2012-08-10T22:03:43.468015700
      date3: 2014-12-12
      date4: 22:15
      date5: 20:15:30
  • Optional 類,很好的解決了 NullPointerException 的問題

學習過程中,部分內容有參考網上其它人的文章,如有侵權必刪