寫給大忙人看的Java SE 8 讀書心得筆記與習題-第一章 lambda 表示式

此書英文原名是: java se 8 for the really impatient

就算假裝忽略即將要正式發行的java 9,這也是一本2015就出版的簡中翻譯版。
java這幾年已經算是更新慢的主流語言,卻還有一堆東西要補阿....

幾位前輩曾經形容歐美的教科書注重跟你搏感情,
內容常常會寫了一大段的敘述讓你體會為什麼這麼做。
教科書已經太久沒讀早忘光了,
倒是在Spring in action 裡面體會到這點
滿滿的英文字翻譯完才發現前面都在寫前幾版怎麼做,
現實世界怎麼處理,為什麼會這麼做。

但是此書以一本讓你快速使用API的工具書來說還算不錯,沒有寫的很長,
比起看java doc 跟patch note還多了一點原理解釋跟使用時機讓你體會。

特色一

喜歡用javaFx作範例

雖然javaFx是蠻適合lambda的使用時機
不過現在應該很少人寫java application
,只好乖乖的下載原始檔來跑

作者範例原始檔語英文勘誤頁面

特色二

每章最後的練習問題不附解答...

雖然在網路上可以找到其他人自己寫的解答,
不過觀念性的問題就比較難找答案了.....
尤其很多書上問的問題是有其特殊目的的

從小背書背習慣了,還真有點不適應。
雖然我也忘了以前怎麼讀書的。


第一章要點

  1. 一個lambda表示式是一個帶有參數的程式碼區塊
  2. 當你想要程式碼區塊在以後某個時間點執行時,可以使用lambda表示式

    其實就是常見的匿名內部類別(Anonymous inner class)


    可以用lambda表示式替代

  3. lambda表示式是可以轉換成函式(簡中翻函數式接口)@FunctionalInterface
  4. 方法跟建構可以引用,但無需要調用他們。
    以下畫面是使用eclipse 的ctrl +1功能 提示語法轉換
    若要完整支援java 8語法 ,請至少昇級到mars版本

  5. 你可以向介面提供default跟static方法來提供具體的實現
  6. 你必須解決接口中多個默認方法之間的衝突。

由於後面章節還會介紹如何使用lambda coding,
第一章的內容在實作上算寫非常少
非常建議跑一次以下網站的教學
oracle的Lambda-QuickStart

練習題與自己版本的解答

  1. Array.sort方法中Comparator的Thread的呼叫sort中的Thread是同一個嗎?
    用 System.out.println(Thread.currentThread().getName()); 來看起來是同一個
  2. 使用java.io.file的listFiles(FileFiter)方法,編寫一個返回指定目錄下具有指定擴展名的索有文件。使用lambda表示式來代替FileFiter對象,再將它改寫為一個方法引用

    用ide寫一遍就自動帶出Anonymous inner class了,改成lambda表示式也只是ctrl+1就出來了。
    在初期語法不熟的情況下,蠻常使用IDE的語法轉換來確認語法可以怎麼改寫。
    當然實作細節差異要自己另外去學習。
  3. 使用java.io.file的listFiles(FileFiter)方法,編寫一個返回指定目錄下具有指定擴展名的索有文件。使用lambda表示式(而不是使用FilenameFilter)來實現,他會補捉閉合作用域中的哪些變數?

    閉合作用域應該是指enclosing scope
    Java 8 區域變數不用再標記為final後才能給Anonymous inner class跟lambda使用,
    當然用了以後不能在程式碼區塊內改變變數的值,基本上IDE會提示你
    Local variable xxx defined in an enclosing scope must be final or effectively final
    但是如果你改變的是傳進來的list之類,IDE不會提示你有問題。
    不過基本上為了避免函數副作用(side effect ),平常就會避免這種寫法。
  4. 對於一個指定的File對象數組首先按照路徑的目錄排序然後對每個每組目錄中的元素再依照路徑名排序,請使用lambda表示式。

    跟前面差不多 就是要自己寫排序邏輯

  5. 從你的項目中選取一個包含一些ActionListener,Runnable或者其他類似代碼,將他們替換成lambda表示式。這樣可以節省多少行代碼?替換後的代碼是否有更好的可讀性?
    這個過程中你使用了方法引用嗎?

    以實務上來說新的程式碼都會改成lambda,看起來比較簡潔。
    Anonymous inner class實在是有點醜。

    不過由於lambda表示式可以省略的東西太多,
    如果專案的code style有規定哪些東西不要省略,
    請用IDE的功能加回去,雖然{}這種東西加不加老是有人在吵。

    但是希望不要改上癮,一口氣用某些IDE的特異功能連舊的程式碼都改掉,
    維護期看到程式碼被一口氣改實在是很難判斷是不是有東西都被偷改掉。
  6. 你是否討厭在Runnable實作中處理checkedException?寫一個補捉所有異常的
    uncheck方法,再將它改造成不需要檢查異常的方法:例如
       new Thread(uncheck(() -> {
                  System.out.println("Zzzz!!");
                    Thread.sleep(1000);
                  })).start();
          提示:定義一個RunnableEx介面,其run方法可以拋出所有異常,然後實現
           public static Runnable uncheck(RunnableEx runner)。在uncheck函數中使用一個
          lamba表示式。
          為什麼你不能直接使用 Callable<Void>來代替RunnableEx


          根據題目寫出來的程式碼會像下面那樣,但Callable我真的不熟0rz
          覺得是因為直接用沒辦法額外作手腳catch call丟出來的例外吧...
          
          public class Exercise1_6 {
          
              @FunctionalInterface
              interface RunnableEx {
                  public void run() throws Exception;
              }
          
              public static Runnable uncheck(RunnableEx runner) {
                  return () -> {
                     
                          try {
                              runner.run();
                          } catch (Exception e) {
                              // TODO Auto-generated catch block
                              e.printStackTrace();
                          }
                     
                  };
              }
              
              
              public static void main(String[] args) {
                  new Thread(uncheck(() -> {
                      System.out.println("Zzzz!!");
                      Thread.sleep(1000);
                  })).start();
                  
                  Callable callable = new Callable() {
                      public Void call()  {
                          return null;          
                      }
                  };
                  FutureTask future = new FutureTask(callable);
                  new Thread(future).start();
              }
          
          }
          
          


        1. 編寫一個靜態方法andThen,他接收兩個 Runnable  實例做為參數,並返回一個分別運行
          這兩個實例的Runnable對象。在main方法中,向andThen傳第兩個lambda表達式,並運行返回的實例
          
          public class Exercise1_7 {
          
              public static Runnable andThen(Runnable a, Runnable b) {
                  return new Runnable() {
          
                      @Override
                      public void run() {
                          a.run();
                          b.run();
                      }
          
                  };
          
              }
          
              public static void main(String[] args) {
                  new Thread(andThen(() -> {
                      System.out.println("a");
                  }, () -> {
                      System.out.println("b");
                  })).start();
              }
          
          }
        2. 當一個lambda表示式捕獲了如下foreach中的值時,會發生什麼?   String[] names = {"Peter", "Paul", "Mary"};
           List<Runnable> runners = new ArrayList<>();
           for (String name : names) { runners.add(() -> System.out.println(name)); }
          這樣做是否合法?每個lambda表示式都補獲了一個不同的值,還是他們都獲得了最後的值? 如果使用傳統的for迴圈,例如for (int i = 0; i < names.length; i++) ,又會發生什麼?
          在我電腦上跑
           lambda foreach: Paul Mary Peter
           lambda for:Peter Paul Mary
           Anonymous inner class foreach: Peter Paul Mary
           Anonymous inner class  for:Peter Paul Mary
          只有lambda 遇上 foreach會不照原本String[]的順序
          完蛋,這題跟第一題一起看讓我越搞不懂了...
          
          public class Exercise1_8 {
          
              public static void main(String[] args) {
                 public static void main(String[] args) {
                  String[] names = { "Peter", "Paul", "Mary" };
                  List runners = new ArrayList<>();
                  for (String name : names) {
                      runners.add(() -> System.out.println("runners " + name));
                  }
                  for (Runnable runner : runners) {
                      new Thread(runner).start();
                  }
          
                  List runners2 = new ArrayList<>();
                  for (int i = 0; i < names.length; i++) {
                      String name = names[i];
                      runners2.add(() -> System.out.println("runners2 " + name));
                  }
          
                  for (Runnable runner : runners2) {
                      new Thread(runner).start();
                  }
                  
                  List runners3 = new ArrayList<>();
                  
                  for (String name : names) {
                      runners3.add(new Runnable() {
                          @Override
                          public void run() {
                              System.out.println("runners3 " + name);
                          }
                      });
                  }
                  for (Runnable runner : runners3) {
                      new Thread(runner).start();
                  }
          
                  List runners4 = new ArrayList<>();
                  for (int i = 0; i < names.length; i++) {
                      String name = names[i];
                      runners4.add(new Runnable() {
                          @Override
                          public void run() {
                              System.out.println("runners4 " + name);
                          }
                      });
                  }
          
                  for (Runnable runner : runners4) {
                      new Thread(runner).start();
                  }        
              }
          
          }
          
          
        3. 編寫一個繼承Collection的子界面Collection2,並添加一個默認方法
          default void forEachIf(Consumer<E> action, Predicate<E> filter)
          ,用來將action運用到所有filter返回true的元素上,你能夠怎麼使用他?

          
          
          public class Exercise1_9 {
          
              public interface Collection2 extends Collection {
                  default void forEachIf(Consumer action, Predicate filter) {
                      forEach((E e) -> {
                          if (filter.test(e)) {
                              action.accept(e);
                          }
                      });
                  }
          
                  // 另外一種寫法
                  default void forEachIfStreams(Consumer action, Predicate filter) {
                      this.stream().filter(filter::test).forEach(action::accept);
                  }
              }
          
              class AList2 extends ArrayList implements Collection2 {
          
                  private static final long serialVersionUID = -8328319672786699491L;
          
              }
          
              public static void main(String[] args) {
          
                  Exercise1_9 a = new Exercise1_9();
                  AList2 x = a.new AList2<>();
          
                  x.add("a");
                  x.add("b");
                  x.add("c");
          
                  x.forEachIfStreams(
                          // Lambda expression for Consumer interface
          
                          s -> System.out.println(s),
          
                          // Lambda expression for Predicate inerface
          
                          t -> "c".equals(t));
              }
              // print c
          
          }
          
          
          
        4. 瀏覽Collection類中的方法。如果哪一天你可以做主,你會將每個方法放到哪個介面中?
          這個方法會是一個默認方法還是靜態方法?
        5. 假如你有一個實現了兩個介面I與J的類,這兩個介面都有一個void f()方法。
          如果I介面中的f方法是一個抽象的、默認或者靜態方法,且J介面的f方法也是一個抽象的、默認或者靜態方法,分別會發生什麼?如果這個類繼承至s類並實現了介面,且S跟I中都有一個void f()方法,又分別會發生什麼?

          兩個都是介面就只能Override掉,如果其中一個是繼承就是直接繼承

          
          public class Exercise1_11 {
          
              interface I {
                  default void f() {
                      System.out.println("I");
                  };
              }
          
              interface J {
                  default void f() {
                      System.out.println("J");
                  };
              }
          
              public class S {
                  public void f() {
                      System.out.println("s");
                  };
              }
          
              public class Ij implements I, J {
          
                  @Override
                  public void f() {
                      I.super.f();
                      J.super.f();
                      System.out.println("Ij");
                  }
          
                
              }
          
              public class Si extends S implements I {
          
              }
          
              public static void main(String[] args) {
               
                  Exercise1_11 a= new Exercise1_11();
                  Ij x=a. new Ij();
                  x.f();
                  Si y=a. new Si();
                  y.f();
                  //I
                  //J
                  //Ij
                  //s
                  
              }
              
          }
          
          
        6. 在過去,你知道向界面中添加方法是一種不好的型式,因為它會破壞已有的代碼。
          現在你知道了可以像介面添加新方法,同時能夠提供一個默認的實作。
          這樣作安全程度為何?
          描述一個Collection介面的新方法會導致遺留代碼編譯失敗的場景。
          二進位的兼容性如何?jar文件中的遺留代碼是否能運行?

           







        留言

        熱門文章

        汐科定便當記錄(一)

        汐科定便當記錄 (完)

        ireport換行