1. 程式人生 > 其它 >JDK 8 新特性 lambda

JDK 8 新特性 lambda

技術標籤:java

引言

在 JDK 8 之前介面怎麼例項化呢?

  1. 正經方式是定義一個類去實現介面,然後實現介面中的方法;
public class Test implements Runnable{
    public static void main(String[] args) {
        new Thread(new Test()).start();
    }
    @Override
    public void run() {
        System.out.println("thread name " + Thread.currentThread
().getName() + " is running."); } }
  1. 匿名內部類的方式去實現介面,這種方式與第一種相比會少一個 .java 的原始檔,但編譯後也會產生位元組碼檔案,檔名格式是類名 $ 加上從 1 開始的編號;
public class Test {
    public static void main(String[] args) {
        new Thread(new Runnable(){
            @Override
            public void run() {
                System.
out.println("thread name " + Thread.currentThread().getName() + " is running."); } }).start(); } }

在這裡插入圖片描述
3. lambda 方式實現介面,這種方式編譯後不會產生 .class 檔案,且你不用指明該類要去實現什麼方法,從程式碼量上可以感受到它使程式碼更優雅的特性。

public class Test {
    public static void main(String[] args) {
        new
Thread(() -> System.out.println("thread name " + Thread.currentThread().getName() + " is running.")); } }

概念

lambda 是 JDK 8 之後的新特性,可以取代大部分需要用到匿名內部類的場景,常用在集合的遍歷操作中。
它可以對函式式介面做一個簡單的實現,但不是所有的介面實現都能使用 lambda 去編寫。它要求被實現的介面只能有一個抽象方法(函式式介面)。
JDK 提供的內建函式式介面之一:

// 該註解通常和 lambda 一同出現,它表明該介面的實現可以使用簡潔的 lambda 實現
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    // default 方法是 jdk 8 之後有的特性
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> {
            accept(t); 
            after.accept(t);
       };
    }
}

語法

正常版

(Object a,Object b,Object c,...) -> {...}
小括號裡面就是被重寫的方法需要的各引數值
花括號裡面就是實現類要編寫的方法實現邏輯

lambda 表示式其實就是函數語言程式設計思想的落地,所以 lambda 就是編寫一個 Java 中的方法,既然是方法,那麼它就有引數列表(小括號裡面的內容),也有方法體(花括號中的內容)。

簡化版

  • 引數列表中的所有引數的型別可以一起省略;
(a, b, c,...) -> {...}
@FunctionalInterface
public interface Running {
    void speed(String day, Integer speed);
}
public class Test{
    public static void main(String[] args) {
        Running running = (day, speed) -> {
            System.out.println(day + "---" + speed);
        };
        running.speed("20200123", 200);
    }
}
  • 當引數列表就一個引數時,可以省略 () ;
a -> {...}
@FunctionalInterface
public interface Running {
    void speed(String day);
}
public class Test{
    public static void main(String[] args) {
        Running running = day -> {
            System.out.println(day);
        };
        running.speed("20200123");
    }
    
    public void method(String day) {
        System.out.println(day);
    }
}
  • 當方法體只有一條語句時,可以省略 {},如果這個方法有返回值,那麼該條語句前面會預設加上 return 關鍵字。
public class Test{
    public static void main(String[] args) {
        Running running = day -> System.out.println(day);
        running.speed("20200123");
    }
}
  • 如果你重寫的那個匿名內部類的方法,有一個方法已經幫你實現了,此時我們就沒必要再去實現。語法上分兩種引用寫法,分別是靜態引用和例項引用兩種:
public class Test{
    public static void main(String[] args) {
        //Running running = day -> System.out.println(day);
        //靜態引用
        Running running = System.out::println;
        //例項引用
        Running running = new Test()::method;
        running.speed("20200123");
    }
    
    public void method(String day) {
        System.out.println(day);
    }
}

常用示例

1. 在集合中的應用舉例(ArrayList )

forEach(Consumer<? super E> action) 迭代

集合遍歷的業務程式碼

public class App {
    static List<String> list = new ArrayList<>();
    public static void main(String[] args) {
        list.add("上海");
        list.add("北京");
        list.add("廣州");
        list.add("深圳");
        // jdk 8 之前匿名內部類寫法
        list.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });
        // 普通 lambda 寫法
        list.forEach(s -> {
            System.out.println(s);
        });
        // 簡便寫法,如果實現類重寫的方法是其他類已經實現的類方法
        list.forEach(System.out::println);
        // 簡便寫法,如果實現類重寫的方法是其他例項已有的方法
        list.forEach(new App()::method);
    }
    // 例項方法
    public void method(String ele) {
        System.out.println(ele);
    }
}
// ArrayList 複寫 Iterable 介面的 forEach 方法
@Override
public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    @SuppressWarnings("unchecked")
    final E[] elementData = (E[]) this.elementData;
    final int size = this.size;
    // fori 迴圈依次獲取每個元素傳遞給 action 實現的 accept 方法
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        action.accept(elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

程式碼解讀:
forEach 方法需要傳遞 Consumer 物件,所以需要宣告一個實現了 Consumer 介面的型別,你可以單獨寫個類去做實現,一般都使用匿名內部類的方式做的實現,但在 JDK 8 之後可以使用 lambda 表示式對函式式介面做個簡單的實現。
下面是 foreach 中最主要的兩行程式碼:

action.accept(elementData[i]);
// elementData[i] 會傳遞給 s,而 s 就是方法的引數,所以方法體能拿到這個變數的引用
list.forEach(s -> System.out.println(s));

removeIf(Predicate<? super E> filter) 刪除元素

業務程式碼

// 匿名內部類寫法
list.removeIf(new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.equals("上海");
    }
});
// lambda 寫法
list.removeIf(s -> s.equals("上海"));
@Override
public boolean removeIf(Predicate<? super E> filter) {
    ...
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        @SuppressWarnings("unchecked")
        final E element = (E) elementData[i];
        if (filter.test(element)) {
            removeSet.set(i);
            removeCount++;
        }
    }
    ...
}