JDK1.8新特性(一):Lambda表達式

JDK1.8系列文章

Lambda 表達式

Java 8 是 Java 語言開發(fā)的一個主要版本,Oracle 公司于 2014 年 3 月 18 日發(fā)布 Java 8 ,現(xiàn)在已經(jīng)發(fā)布了 Java 11 和 Java 13。但 Java 8 還是目前使用最多的版本,最主要的原因是能在我們編程過程中帶來很多便利,特別是 Lambda 表達式和 Stream 的支持,使得程序設計更加簡潔,代碼量更少,能把二三十行的代碼,簡化到十行以內(nèi),你敢信?快用上這些新特性來試試吧。
看到 GitHub 上面有個關于 Java 8 的英文倉庫, 新特性的內(nèi)容比較全,地址:https://github.com/winterbe/java8-tutorial ,有能力的朋友可以閱讀。我以這個倉庫作為參考,寫了下面這一系列文章。

一、新特性

在 Java 8 新特性系列文章的第一篇中,稍微先介紹一下新特性有哪些:

  • 接口的靜態(tài)方法和默認方法
  • Lambda 表達式
  • 函數(shù)式接口
  • 方法和構造函數(shù)引用
  • Optional 類
  • Streams (流)
  • Maps
  • 新的時間日期 API
  • Annotations (注解)

在本文中,將詳細介紹前4點新特性(接口的靜態(tài)方法和默認方法、Lambda 表達式、函數(shù)式接口、方法和構造函數(shù)引用),其他的新特性在接下來的幾篇文章會介紹到。

二、接口的默認方法和靜態(tài)方法

1、默認方法

Java 8使我們能夠通過使用 default 關鍵字向接口添加非抽象方法實現(xiàn)。

interface Formula {
    double calculate(int a);

    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}

Formula 接口中除了定義抽象方法 calculate,還定義了默認方法 sqrt。抽象方法需要先實現(xiàn),然后才能夠進行使用,默認方法 sqrt 可以直接使用,我們通過代碼來演示一下。

Formula formula = new Formula() {
    @Override
    public double calculate(int a) {
        return sqrt(a * 100);
    }
};

formula.calculate(100);     // 100.0
formula.sqrt(16);           // 4.0

通過增加 default 關鍵字,使接口可以有實現(xiàn)方法,而且不需要實現(xiàn)類去實現(xiàn)這個方法。思考一下為什么 Java 8 中要新增這個特性?

首先,之前的接口是個雙刃劍,好處是面向抽象而不是面向具體編程,缺陷是,當需要修改接口時候,需要修改全部實現(xiàn)該接口的類,目前的 Java 8 之前的集合框架沒有 foreach 方法,通常能想到的解決辦法是在 JDK 里給相關的接口添加新的方法及實現(xiàn)。然而,對于已經(jīng)發(fā)布的版本,是沒法在給接口添加新方法的同時不影響已有的實現(xiàn)。所以引進的默認方法,目的是為了解決接口的修改與現(xiàn)有的實現(xiàn)不兼容的問題。

2、靜態(tài)方法

Java 8 中接口除了可以使用默認方法,也可以使用靜態(tài)方法。

public interface Formula {
    public static void print(){
        System.out.println("接口中的靜態(tài)方法");
    }
}

三、Lambda 表達式

Lambda 表達式是推動 Java 8 發(fā)布的最重要特性,允許把函數(shù)作為一個方法的參數(shù),傳遞到方法中,使用 Lambda 表達式可以使代碼變得更加簡潔緊湊,接下來我們就來了解一下什么是 Lambda 表達式。

1、使用 Lambda 表達式

Java 8 中引入了一個新的操作符 -> ,該操作符被稱為 Lambda 操作符,Lambda 操作符把 Lambda 表達式拆分成兩部分,左側為 Lambda 表達式的參數(shù)列表,右側為 Lambda 表達式中所需的函數(shù)方法。
讓我們從一個簡單的例子開始,看看在以前的版本的 Java 中如何排序的字符串列表:

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
});

給靜態(tài)方法 Collections.sort 傳入一個 List 對象和一個比較器,來對給定列表中的元素進行排序。通常做法都是創(chuàng)建一個匿名的比較器對象然后將其傳遞給 sort 方法。Java 8 提供了更簡潔的語法,Lambda 表達式,不需要再創(chuàng)建匿名對象:

Collections.sort(names, (String a, String b) -> {
    return b.compareTo(a);
});

可以看出,代碼變得更短更具簡潔,但實際上還可以寫得更短:

Collections.sort(names, (String a, String b) -> b.compareTo(a));

可以去掉大括號 {} 以及 return 關鍵字,可以寫得更短點:

names.sort((a, b) -> b.compareTo(a));

2、Lambda 表達式中的作用域

Lambda 表達式只能引用標記為 final 的外層局部變量,不能在 Lambda 內(nèi)部修改定義在作用域外的局部變量。

  • 訪問局部變量

可以直接在 Lambda 表達式中訪問外部的局部變量:

final int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

Lambda 表達式的局部變量可以不用聲明為 final,但是必須不可被后面的代碼修改(即隱性的具有 final 的語義),該代碼同樣正確:

int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

在Lambda表達式中試圖修改num同樣是不允許的,例如下面的就無法編譯:

int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);
num = 4;
  • 訪問字段和靜態(tài)變量

與局部變量相比,對Lambda表達式中的實例字段和靜態(tài)變量都有讀寫訪問權限。

class Lambda4 {
    static int outerStaticNum;
    int outerNum;

    void testScopes() {
        Converter<Integer, String> stringConverter1 = (from) -> {
            outerNum = 23;
            return String.valueOf(from);
        };

        Converter<Integer, String> stringConverter2 = (from) -> {
            outerStaticNum = 72;
            return String.valueOf(from);
        };
    }
}

四、函數(shù)式接口

Java 語言設計者增加了函數(shù)式接口的概念,來使現(xiàn)有的函數(shù)友好地支持 Lambda。

1、概念

函數(shù)式接口(Functional Interface)在 Java 中是指,有且僅有一個抽象方法,但是可以有多個非抽象方法的接口,適用于函數(shù)式編程場景。需要確保接口中只有一個抽象方法,Java 中的 Lambda 才能順利地進行推導。

在 Java 8 之前,java.lang.Runnablejava.util.concurrent.Callable 是函數(shù)式接口最典型的兩個例子。Java 8 增加了一種特殊的注解 @FunctionalInterface ** ,但是這個注解不是必須的(建議使用),只要接口只包含一個抽象方法,虛擬機會自動判斷該接口為函數(shù)式接口。一般建議在接口上使用 @FunctionalInterface** ** ** 注解進行聲明,這樣的話,編譯器如果發(fā)現(xiàn)你標注了這個注解的接口有多于一個抽象方法的時候會報錯的。

如何定義一個函數(shù)式接口:

@FunctionalInterface
interface GreetingService 
{
    void sayMessage(String message);
}

然后使用 Lambda 表達式來表示該接口的一個實現(xiàn):

GreetingService greetService1 = message -> System.out.println("Hello " + message);

2、函數(shù)接口

JDK 1.8 API包含許多內(nèi)置的函數(shù)接口。其中一些在舊版本的Java中比較常用,比如 Comparator 或 Runnable。對這些現(xiàn)有接口進行擴展,以通過 @FunctionalInterface 注釋對 Lambda 的進行支持。

JDK 1.8 新增加的函數(shù)接口:

  • java.util.function

JDK 1.8 之前已有的函數(shù)式接口:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.security.PrivilegedAction
  • java.util.Comparator
  • java.io.FileFilter
  • java.nio.file.PathMatcher
  • java.lang.reflect.InvocationHandler
  • java.beans.PropertyChangeListener
  • java.awt.event.ActionListener
  • javax.swing.event.ChangeListener

3、函數(shù)接口實例

看到這里相信大家已經(jīng)基本了解什么是函數(shù)式接口,如果想對函數(shù)式接口有更深入的了解,請繼續(xù)看下面的內(nèi)容,將為大家詳細介紹常用的函數(shù)式接口如何使用。
在新增加的函數(shù)接口在 java.util.function包下,它包含了很多類,用來支持 Java 的函數(shù)式編程,那么讓我們來看一看哪些是比較常用的:

java.util.function.Predicate
java.util.function.Consumer
java.util.function.Function
java.util.function.Supplier

Predicate

java.util.function.Predicate 接口定義了一個名叫 test 的抽象方法,它接受泛型 T 對象,并返回一個 boolean 值。在對類型 T 進行斷言判斷時,可以使用這個接口。
使用 Predicate 實現(xiàn)字符串判空操作,測試代碼

// 實現(xiàn) Predicate test 方法,進行判斷傳入的字符串是否為空
Predicate<String> predicate = new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.isEmpty() || s.trim().isEmpty();
    }
};
// 測試傳入的字符串是否為空
System.out.println(predicate.test(""));
System.out.println(predicate.test("  "));
System.out.println(predicate.test("admin"));

測試結果

true
true
false

Consumer

java.util.function.Consumer 接口定義了一個名叫 accept 的抽象方法,接受泛型 T,沒有返回值。如果需要訪問類型 T 的對象,并對其執(zhí)行某些操作,可以使用這個接口,通常稱為消費性接口。

使用 Consumer 實現(xiàn)集合遍歷操作,測試代碼

// 實現(xiàn) Consumer accept 方法,進行集合的遍歷并打印
Consumer<Collection> consumer = new Consumer<Collection>() {
    @Override
public void accept(Collection collection) {
        if (null != collection && collection.size() > 0) {
            for (Object c : collection) {
                System.out.println(c);
            }
        }
    }
};

// 在集合中添加元素
List<String> list = new ArrayList<>();
list.add("杭州");
list.add("北京");
list.add("上海");

// 遍歷 list 輸出元素內(nèi)容到控制臺
consumer.accept(list);

測試結果

杭州
北京
上海

Function

java.util.function.Function<T, R> 接口定義了一個叫做 apply 的方法,接受一個泛型 T 的對象,并返回一個泛型 R 的對象。如果需要定義一個 Lambda,將輸入的信息映射到輸出,可以使用這個接口,通常稱為功能性接口。

使用 Function 實現(xiàn)用戶密碼 Base64 加密操作,測試代碼

// 實現(xiàn) Function apply 方法,進行用戶密碼 Base64 加密操作
Function<String, String> function = new Function<String, String>() {
    @Override
    public String apply(String s) {
        return Base64.getEncoder().encodeToString(s.getBytes());
    }
};
// 輸出加密后的字符串
System.out.println(function.apply("123456"));

測試結果

MTIzNDU2

Supplier

java.util.function.Supplier 接口定義了一個 get 的抽象方法,它沒有參數(shù),返回一個泛型 T 的對象,這類似于一個工廠方法。

使用 Supplier 實現(xiàn)返回字符串,測試代碼

// 實現(xiàn) Supplier get 方法,進行字符串返回
Supplier<String> supplier = new Supplier<String>() {
    @Override
    public String get() {
        return "get!";
    }
};
// 輸出返回的字符串
System.out.println(supplier.get());

測試結果

get!

五、方法和構造函數(shù)引用

Java 8 允許通過::關鍵字傳遞方法或構造函數(shù)的引用。
下面,我們在 Car 類中定義了 4 個方法作為例子來區(qū)分 Java 中 4 種不同方法的引用。

public class Car {
    public static Car create(final Supplier<Car> supplier) {
        return supplier.get();
    }

    public static void collide(final Car car) {
        System.out.println("Collided " + car.toString());
    }

    public void follow(final Car another) {
        System.out.println("Following the " + another.toString());
    }

    public void repair() {
        System.out.println("Repaired " + this.toString());
    }
}

1、構造器引用

它的語法是Class::new,或者更一般的Class< T >::new實例如下:

final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );

2、靜態(tài)方法引用

它的語法是 Class::static_method,實例如下:

cars.forEach( Car::collide );

3、特定類的任意對象的方法引用

它的語法是 Class::method 實例如下:

cars.forEach( Car::repair );

4、特定對象的方法引用

它的語法是 instance::method 實例如下:

final Car police = Car.create( Car::new );
cars.forEach( police::follow );

參考網(wǎng)站:

六、公眾號

如果大家想要第一時間看到我更新的 Java 方向?qū)W習文章,可以關注一下公眾號【Lucas的咖啡店】。所有學習文章公眾號首發(fā),請各位大大掃碼關注一下哦!

Java 8 新特性學習視頻請關注公眾號,私信【Java8】即可免費無套路獲取學習視頻。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容