天天看點

如何使用Lambda表達式替代匿名内部類?

Java Lambda 表達式的一個重要用法是簡化某些匿名内部類(Anonymous Classes)的寫法。實際上 Lambda 表達式并不僅僅是匿名内部類的文法糖,JVM 内部是通過 invokedynamic 指令來實作 Lambda 表達式的。具體原理放到下一篇。本篇我們首先感受一下使用 Lambda 表達式帶來的便利之處。

取代某些匿名内部類

本節将介紹如何使用 Lambda 表達式簡化匿名内部類的書寫,但 Lambda 表達式并不能取代所有的匿名内部類,隻能用來取代函數接口(Functional Interface)的簡寫。先别在乎細節,看幾個例子再說。

// JDK7 匿名内部類寫法
new Thread(new Runnable(){// 接口名
@Override
public void run(){// 方法名
    System.out.println("Thread run()");
  }
}).start();
           

例子 1:無參函數的簡寫

如果需要建立一個線程,一種常見的寫法是這樣:

// JDK8 Lambda 表達式寫法
new Thread(
  () -> System.out.println("Thread run()")// 省略接口名和方法名
).start();
           

上述代碼給 Tread 類傳遞了一個匿名的 Runnable 對象,重載 Runnable 接口的 run()方法來實作相應邏輯。這是 JDK7 以及之前的常見寫法。匿名内部類省去了為類起名字的煩惱,但還是不夠簡化,在 Java 8 中可以簡化為如下形式:上述代碼跟匿名内部類的作用是一樣的,但比匿名内部類更進一步。這裡連接配接口名和函數名都一同省掉了,寫起來更加神清氣爽。如果函數體有多行,可以用大括号括起來,就像這樣:

// JDK8 Lambda 表達式代碼塊寫法
new Thread(
  () -> {
      System.out.print("Hello");
      System.out.println(" Hoolee");
   }
).start();
           

例子 2:帶參函數的簡寫

如果要給一個字元串清單通過自定義比較器,按照字元串長度進行排序,Java 7 的書寫形式如下:

// JDK7 匿名内部類寫法
List<String> list = Arrays.asList("I", "love", "you", "too");
Collections.sort(list, new Comparator<String>(){// 接口名
@Override
public int compare(String s1, String s2){// 方法名
    if(s1 == null)
        return -1;
    if(s2 == null)
        return 1;
  return s1.length()-s2.length();
  }
});
           

上述代碼通過内部類重載了 Comparator 接口的 compare()方法,實作比較邏輯。采用 Lambda 表達式可簡寫如下:

// JDK8 Lambda 表達式寫法
List<String> list = Arrays.asList("I", "love", "you", "too");
Collections.sort(list, (s1, s2) ->{// 省略參數表的類型
    if(s1 == null)
      return -1;
    if(s2 == null)
      return 1;
    return s1.length()-s2.length();
});
           

上述代碼跟匿名内部類的作用是一樣的。除了省略了接口名和方法名,代碼中把參數表的類型也省略了。這得益于 javac 的類型推斷機制,編譯器能夠根據上下文資訊推斷出參數的類型,當然也有推斷失敗的時候,這時就需要手動指明參數類型了。注意,Java 是強類型語言,每個變量和對象都必需有明确的類型。

// Lambda 表達式的書寫形式
Runnable run = () -> System.out.println("Hello World");// 1
ActionListener listener = event -> System.out.println("button clicked");// 2
Runnable multiLine = () -> {// 3 代碼塊
    System.out.print("Hello");
    System.out.println(" Hoolee");
};
BinaryOperator<Long> add = (Long x, Long y) -> x + y;// 4
BinaryOperator<Long> addImplicit = (x, y) -> x + y;// 5
           
// 自定義函數接口
@FunctionalInterface
public interface ConsumerInterface<T>{
    void accept(T t);
}
           
ConsumerInterface<String> consumer = str -> System.out.println(str);
進一步的,還可以這樣使用:
class MyStream<T>{
  private List<T> list;
  ...
  public void myForEach(ConsumerInterface<T> consumer){// 1
    for(T t : list){
        consumer.accept(t);
    }
  }
}
MyStream<String> stream = new MyStream<String>();
stream.myForEach(str -> System.out.println(str));// 使用自定義函數接口書寫 Lambda 表達式