Effective Java 第三版——30. 優先使用泛型方法

Tips
《Effective Java, Third Edition》一書英文版已經出版,這本書的第二版想必不少人都讀過,號稱Java四大名著之一,不過第二版2009年出版,到如今已經將近8年的時間,但隨着Java 6,7,8,甚至9的發佈,Java語言發生了深入的變化。
在這裏第一時間翻譯成中文版。供你們學習分享之用。java

Effective Java, Third Edition

 30. 優先使用泛型方法

正如類能夠是泛型的,方法也能夠是泛型的。 對參數化類型進行操做的靜態工具方法一般都是泛型的。 集合中的全部「算法」方法(如binarySearch和sort)都是泛型的。算法

編寫泛型方法相似於編寫泛型類型。 考慮這個方法,它返回兩個集合的並集:express

// Uses raw types - unacceptable! [Item 26]

public static Set union(Set s1, Set s2) {

    Set result = new HashSet(s1);

    result.addAll(s2);

    return result;
}

此方法能夠編譯但有兩個警告:安全

Union.java:5: warning: [unchecked] unchecked call to

HashSet(Collection<? extends E>) as a member of raw type HashSet

        Set result = new HashSet(s1);

                     ^

Union.java:6: warning: [unchecked] unchecked call to

addAll(Collection<? extends E>) as a member of raw type Set

        result.addAll(s2);

                     ^

要修復這些警告並使方法類型安全,請修改其聲明以聲明表示三個集合(兩個參數和返回值)的元素類型的類型參數,並在整個方法中使用此類型參數。 聲明類型參數的類型參數列表位於方法的修飾符和返回類型之間。 在這個例子中,類型參數列表是<E>,返回類型是Set<E>。 類型參數的命名約定對於泛型方法和泛型類型是相同的(條目 29和68):app

// Generic method

public static <E> Set<E> union(Set<E> s1, Set<E> s2) {

    Set<E> result = new HashSet<>(s1);

    result.addAll(s2);

    return result;

}

至少對於簡單的泛型方法來講,就是這樣。 此方法編譯時不會生成任何警告,並提供類型安全性和易用性。 這是一個簡單的程序來運行該方法。 這個程序不包含強制轉換和編譯時沒有錯誤或警告:ide

// Simple program to exercise generic method

public static void main(String[] args) {

    Set<String> guys = Set.of("Tom", "Dick", "Harry");

    Set<String> stooges = Set.of("Larry", "Moe", "Curly");

    Set<String> aflCio = union(guys, stooges);

    System.out.println(aflCio);

}

當運行這個程序時,它會打印[Moe, Tom, Harry, Larry, Curly, Dick](輸出中元素的順序依賴於具體實現。)工具

union方法的一個限制是全部三個集合(輸入參數和返回值)的類型必須徹底相同。 經過使用限定通配符類型( bounded wildcard types)(條目 31),可使該方法更加靈活。學習

有時,須要建立一個不可改變但適用於許多不一樣類型的對象。 由於泛型是經過擦除來實現的(條目 28),因此可使用單個對象進行全部必需的類型參數化,可是須要編寫一個靜態工廠方法來重複地爲每一個請求的類型參數化分配對象。 這種稱爲泛型單例工廠(generic singleton factory)的模式用於方法對象( function objects)(條目 42),好比Collections.reverseOrder方法,偶爾也用於Collections.emptySet之類的集合。ui

假設你想寫一個恆等方法分配器( identity function dispenser)。 類庫提供了Function.identity方法,因此沒有理由編寫你本身的實現(條目 59),但它是有啓發性的。 若是每次要求的時候都去建立一個新的恆等方法對象是浪費的,由於它是無狀態的。 若是Java的泛型被具體化,那麼每一個類型都須要一個恆等方法,可是因爲它們被擦除之後,因此泛型的單例就足夠了。 如下是它的實例:url

// Generic singleton factory pattern

private static UnaryOperator<Object> IDENTITY_FN = (t) -> t;

@SuppressWarnings("unchecked")

public static <T> UnaryOperator<T> identityFunction() {

    return (UnaryOperator<T>) IDENTITY_FN;

}

IDENTITY_FN轉換爲(UnaryFunction <T>)會生成一個未經檢查的強制轉換警告,由於UnaryOperator <Object>對於每一個T都不是一個UnaryOperator <T>。可是恆等方法是特殊的:它返回未修改的參數,因此咱們知道,使用它做爲一個UnaryFunction <T>是類型安全的,不管T的值是多少。所以,咱們能夠放心地抑制由這個強制生成的未經檢查的強制轉換警告。 一旦咱們完成了這些,代碼編譯沒有錯誤或警告。

下面是一個示例程序,它使用咱們的泛型單例做爲UnaryOperator <String>UnaryOperator <Number>。 像往常同樣,它不包含強制轉化,編譯時也沒有錯誤和警告:

// Sample program to exercise generic singleton

public static void main(String[] args) {

    String[] strings = { "jute", "hemp", "nylon" };

    UnaryOperator<String> sameString = identityFunction();

    for (String s : strings)

        System.out.println(sameString.apply(s));

    Number[] numbers = { 1, 2.0, 3L };

    UnaryOperator<Number> sameNumber = identityFunction();

    for (Number n : numbers)

        System.out.println(sameNumber.apply(n));

}

雖然相對較少,類型參數受涉及該類型參數自己的某種表達式限制是容許的。 這就是所謂的遞歸類型限制(recursive type bound)。 遞歸類型限制的常見用法與Comparable接口有關,它定義了一個類型的天然順序(條目 14)。 這個接口以下所示:

public interface Comparable<T> {

    int compareTo(T o);

}

類型參數T定義了實現Comparable <T>的類型的元素能夠比較的類型。 在實際中,幾乎全部類型都只能與本身類型的元素進行比較。 因此,例如,String類實現了Comparable <String>Integer類實現了Comparable <Integer>等等。

許多方法採用實現Comparable的元素的集合來對其進行排序,在其中進行搜索,計算其最小值或最大值等。 要作到這一點,要求集合中的每個元素均可以與其中的每個元素相比,換言之,這個元素是能夠相互比較的。 如下是如何表達這一約束:

// Using a recursive type bound to express mutual comparability

public static <E extends Comparable<E>> E max(Collection<E> c);

限定的類型<E extends Comparable <E >>能夠理解爲「任何能夠與本身比較的類型E」,這或多或少精確地對應於相互可比性的概念。

這裏有一個與前面的聲明相匹配的方法。它根據其元素的天然順序來計算集合中的最大值,並編譯沒有錯誤或警告:

// Returns max value in a collection - uses recursive type bound

public static <E extends Comparable<E>> E max(Collection<E> c) {

    if (c.isEmpty())

        throw new IllegalArgumentException("Empty collection");

    E result = null;

    for (E e : c)

        if (result == null || [e.compareTo(result](http://e.compareTo(result)) > 0)

            result = Objects.requireNonNull(e);

    return result;

}

請注意,若是列表爲空,則此方法將引起IllegalArgumentException異常。 更好的選擇是返回一個 Optional<E>(條目 55)。

遞歸類型限制可能變得複雜得多,但幸運的是他們不多這樣作。 若是你理解了這個習慣用法,它的通配符變體(條目 31)和模擬的自我類型用法(條目 2),你將可以處理在實踐中遇到的大多數遞歸類型限制。

總之,像泛型類型同樣,泛型方法比須要客戶端對輸入參數和返回值進行顯式強制轉換的方法更安全,更易於使用。 像類型同樣,你應該確保你的方法能夠不用強制轉換,這一般意味着它們是泛型的。 應該泛型化現有的方法,其使用須要強制轉換。 這使得新用戶的使用更容易,而不會破壞現有的客戶端(條目 26)。

相關文章
相關標籤/搜索