Effective Java 讀書筆記(四):泛型

1 不要使用原始類型

(1)術語

術語 例子
參數化類型(Parameterized type) List<String>
實際類型參數(Actual type parameter) String
泛型類型(Generic type) List<E>
形式類型參數(Formal type parameter) E
無限制通配符類型(Unbounded wildcard type) List<?>
原始類型(Raw type) List
有限制類型參數(Bounded type parameter) <E extends Number>
遞歸類型限制(Recursive type bound) <T extends Comparable<T>>
有限制通配符類型(Bounded wildcard type) List<? extends Number>
泛型方法(Generic method) static <E> List< E > asList(E[] a)
類型令牌(Type token) String.class

(2)爲何不使用原始類型?

  • 原始類型
// 按照這麼寫並不會報錯(List內部由一個Object數組維護),可是使用上很容易出錯。
List list = new ArrayList();
list.add("Hello");
list.add(100);
  • 原始類型失去了安全性(強轉異常)
  • 原始類型失去了可讀性(Object)

2 消除未檢查警告

(1)概述

  • 消除未檢查警告能夠減小ClassCastException的發生。
  • 當警告沒法消除且代碼沒有問題的狀況下,使用@SuppressWarnings("unchecked")註解來禁止這個警告。
  • 消除警告儘量具體。

(2)示例

public <T> T[] toArray(T[] a) {
    if (a.length < size) {
        @SuppressWarnings("unchecked") 
        T[] result = (T[]) Arrays.copyOf(elements, size, a.getClass());
        return result;
    }
    System.arraycopy(elements, 0, a, 0, size); 
    if (a.length > size)
        a[size] = null; 
    return a;
}

3 列表優於數組

(1)數組是協變的,泛型是收約束的。

  • 若是Sub是Super的一個子類型,那麼數組類型Sub[]也是數組類型Super[]的子類型。
// 運行時報錯
Object[] objectArray = new Long[1];
objectArray[0] = "I don't fit in"; // Throws ArrayStoreException

// 沒法編譯經過
List<Object> ol = new ArrayList<Long>(); // Incompatible types
ol.add("I don't fit in");

(2)數組是具化的,泛型是可擦除的。

  • 數組在運行時才知道並檢查元素類型。html

  • E,List<E>,和List<String>這些類型在技術上都被稱爲不可具化類型,即運行時展現信息比編譯時展現信息要少的類型。java

(3)數組提供了運行時類型安全性,不保證編譯時安全性,泛型則反過來。

4 優先考慮泛型

public class Stack<E> {
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    
    // The elements array will contain only E instances from push(E).
    // This is sufficient to ensure type safety, but the runtime 
    // type of the array won't be E[]; it will always be Object[]! 
    @SuppressWarnings("unchecked")
    public Stack() {
        elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
    }
    
    public void push(E e) { 
        ensureCapacity(); 
        elements[size++] = e;
    }
    
    public E pop() { 
        if (size == 0) throw new EmptyStackException();
        E result = elements[--size];
        elements[size] = null; // Eliminate obsolete reference return result;
    }
    
    // no changes in isEmpty or ensureCapacity 
}

5 優先使用泛型方法

(1)泛型方法

  • 原始類型方法
public static Set union(Set s1, Set s2) {
    Set result = new HashSet(s1);
    result.addAll(s2);
    return result;
}
  • 泛型方法
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
    Set<E> result = new HashSet<>(s1);
    result.addAll(s2);
    return result;
}

(2)恆等函數分發器

  • 每次建立新的恆等函數對象
public static <T> UnaryOperator<T> identityFunction() {
    return (t) -> t;
}
  • 緩存一個泛型單例,節約內存且足夠應付全部的狀況
private static UnaryOperator<Object> IDENTITY_FN = (t) -> t;

@SuppressWarnings("unchecked")
public static <T> UnaryOperator<T> identityFunction() {
    return (UnaryOperator<T>) IDENTITY_FN;
}

(3)遞歸類型限制

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) > 0){
            result = Objects.requireNonNull(e);
        }
    }
    return result;
}

注:當列表是空的時候,這個方法會拋出IllegalArgumentException異常。一種更好的辦法是返回一個Optional<E>。api

6 使用有限制通配符來增長API的靈活性

(1)demo1

public class Stack<E> {
    public Stack();
    public void push(E e);
    public E pop();
    public boolean isEmpty();
    
    // 生產者
    public void pushAll(Iterable<? extends E> src) {
        for (E e : src)
            push(e);
    }
    
    // 消費者
    public void popAll(Collection<? super E> dst) { 
        while (!isEmpty())
            dst.add(pop()); 
    }
}

// 使用pushAll
Stack<Number> numberStack = new Stack<>();
Iterable<Integer> integers = ... ;
numberStack.pushAll(integers);

// 使用popAll
Stack<Number> numberStack = new Stack<Number>(); 
Collection<Object> objects = ... ; 
numberStack.popAll(objects);
  • ? extends E修飾的集合:存放E或E的子類
  • ? super E修飾的集合:存放E或E的父類

(2)demo2

// 修改前
public static <E> Set<E> union(Set<E> s1, Set<E> s2);
// 修改後
public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2);
// 使用
Set<Integer> integers = Set.of(1, 3, 5);
Set<Double> doubles = Set.of(2.0, 4.0, 6.0); 
Set<Number> numbers = union(integers, doubles);
  • 泛型做爲返回值時,E表明返回E或E的子類。

具體參考:泛型總結數組

(3)demo3

// 修改前
public static <T extends Comparable<T>> T max(List<T> list);
// 修改後
public static <T extends Comparable<? super T>> T max(List<? extends T> list);
// 使用
// ScheduledFuture不直接實現Comparable,可是它的父類接口實現了Comparable,因此爲了支持這種狀況須要修改成<T extends Comparable<? super T>>。
List<ScheduledFuture<?>> scheduledFutures = ... ;
max(scheduledFutures);
  • Comparable接口一般都是消費者,因此在通常狀況下,你應該優先用Comparable<? super T>,而不是Comparable<T>。
  • 對於Comparator接口也應該如此,也就是應該優先使用Comparator<? super T>,而不是Comparator<T>

(4)demo4

// 無界類型參數
public static <E> void swap(List<E> list, int i, int j); 

// 無界通配符:該方式優於上一個方式,可是因爲無界通配符類型沒法修改,即須要藉助helper進行修改,但這對於調用者無需關心。
public static void swap(List<?> list, int i, int j) { 
    swapHelper(list, i, j);
}

private static <E> void swapHelper(List<E> list, int i, int j) { 
    list.set(i, list.set(j, list.get(i)));
}

7 考慮類型安全的異構容器

(1)異構容器

public class Favorites {
    private Map<Class<?>, Object> favorites = new HashMap<>();
    
    public <T> void putFavorite(Class<T> type, T instance) {
        favorites.put(Objects.requireNonNull(type), instance);
    } 
    
    // 確保類型安全,不安全將拋出異常
    public <T> void putFavorite(Class<T> type, T instance) {
        favorites.put(type, type.cast(instance));
    }
    
    public <T> T getFavorite(Class<T> type) {
        return type.cast(favorites.get(type));
    }
}

// 使用
public static void main(String[] args) {
    Favorites f = new Favorites();
    f.putFavorite(String.class, "Java");
    f.putFavorite(Integer.class, 0xcafebabe);
    f.putFavorite(Class.class, Favorites.class);
    String favoriteString = f.getFavorite(String.class);
    int favoriteInteger = f.getFavorite(Integer.class);
    Class<?> favoriteClass = f.getFavorite(Class.class);
    System.out.printf("%s %x %s%n", favoriteString,
    favoriteInteger, favoriteClass.getName());
}

(2)限定類型的令牌

// 表示類,方法,屬性和其餘程序元素的反射類型實現
public interface AnnotatedElement {
    <T extends Annotation> T getAnnotation(Class<T> annotationType);
}

// 若是一個Class<?>的對象但願傳遞給接收Class<T>的泛型方法,能夠將對象轉換爲Class<? extends Annotation>,可是會有編譯時警告,因此須要藉助asSubclass轉換所調用的Class對象來表示由其參數表示的類的子類。
static Annotation getAnnotation(AnnotatedElement element,String annotationTypeName) {
    Class<?> annotationType = null; // Unbounded type token
    try {
        annotationType = Class.forName(annotationTypeName);
    } catch (Exception ex) {
        throw new IllegalArgumentException(ex);
    }
    return element.getAnnotation(annotationType.asSubclass(Annotation.class));
}
相關文章
相關標籤/搜索