【Java基礎】泛型

cryAllen發表於2016-06-27

Num1:請不要在新程式碼中使用原生型別

泛型類和介面統稱為泛型。每種泛型定義一組引數化的型別,構成格式是:類或介面名稱,接著用<>把對應於泛型形式型別的引數的實際引數列表括起來。比如:List是一個引數化的型別,表示元素型別為String的列表。最後一點,每個泛型都定義一個原生型別,raw type,即不帶任何實際型別引數的泛型名稱。

示例程式碼:

public class Raw {
    // Uses raw type (List) - fails at runtime! - Page 112
    public static void main(String[] args) {
        List<String> strings = new ArrayList<String>();
        unsafeAdd(strings, new Integer(42));
        String s = strings.get(0); // Compiler-generated cast
    }

    private static void unsafeAdd(List list, Object o) {
        list.add(o);
    }

    // Use of raw type for unknown element type - don't do this! - Page 113
    static int rawNumElementsInCommon(Set s1, Set s2) {
        int result = 0;
        for (Object o1 : s1)
            if (s2.contains(o1))
                result++;
        return result;
    }

    // Unbounded wildcard type - typesafe and flexible - Page 113
    static int numElementsInCommon(Set<?> s1, Set<?> s2) {
        int result = 0;
        for (Object o1 : s1)
            if (s2.contains(o1))
                result++;
        return result;
    }
}

從Java1.5發行版本開始,Java就提供了一種安全的替代方法,稱作無限制的萬用字元型別,如果使用泛型,但不確定或者不關心實際的型別引數,就可以使用一個問號代替。

那麼無限制通配型別Set<?>和原生型別Set之間有什麼區別呢?萬用字元型別是安全的,原生型別則不安全。

Num2:消除非受檢警告

當使用泛型程式設計時,會遇到許多編譯器警告,那麼該如何消除?

可以用@SuppressWarnings("unchecked")這個註解來禁止警告。需要注意的是,每當使用@SuppressWarnings("unchecked")註解時,都要新增一條註釋,說明為什麼這麼做是安全的,這樣可以幫助其他人理解程式碼,更重要的是,可以儘量減少其他人修改程式碼後導致計算不安全的概率。

Num3:列表優先於陣列

陣列與泛型相比,有兩個重要的不同點。

首先,陣列是協變的,泛型則是不可變的。

第二大區別:陣列是具體化的,因此陣列會在執行時才知道並檢查它們的元素型別約束。相比之下,泛型則是通過擦除來實現的,因此泛型只在編譯時強化它們的型別資訊,並在執行時丟棄它們的元素型別資訊。

示例程式碼:

public class Reduction {
    static <E> E reduce(List<E> list, Function<E> f, E initVal) {
        List<E> snapshot;
        synchronized (list) {
            snapshot = new ArrayList<E>(list);
        }
        E result = initVal;
        for (E e : snapshot)
            result = f.apply(result, e);
        return result;
    }

    // A few sample functions
    private static final Function<Integer> SUM = new Function<Integer>() {
        public Integer apply(Integer i1, Integer i2) {
            return i1 + i2;
        }
    };

    private static final Function<Integer> PRODUCT = new Function<Integer>() {
        public Integer apply(Integer i1, Integer i2) {
            return i1 * i2;
        }
    };

    private static final Function<Integer> MAX = new Function<Integer>() {
        public Integer apply(Integer i1, Integer i2) {
            return Math.max(i1, i2);
        }
    };

    private static final Function<Integer> MIN = new Function<Integer>() {
        public Integer apply(Integer i1, Integer i2) {
            return Math.min(i1, i2);
        }
    };

    public static void main(String[] args) {
        List<Integer> intList = Arrays.asList(2, 7, 1, 8, 2, 8, 1, 8, 2, 8);

        // Reduce intList using each of the above reducers
        System.out.println(reduce(intList, SUM, 0));
        System.out.println(reduce(intList, PRODUCT, 1));
        System.out.println(reduce(intList, MAX, Integer.MIN_VALUE));
        System.out.println(reduce(intList, MIN, Integer.MAX_VALUE));
    }
}

Num4:優先考慮泛型類和方法

示例類程式碼:

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;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }

    // Little program to exercise our generic Stack
    public static void main(String[] args) {
        Stack<String> stack = new Stack<String>();
        for (String arg : args)
            stack.push(arg);
        while (!stack.isEmpty())
            System.out.println(stack.pop().toUpperCase());
    }
}

如果類可以從泛型中收益一般,方法也一樣,靜態工具方法尤其適合於泛型化。

示例方法程式碼:單例工廠模式

public interface UnaryFunction<T> {
    T apply(T arg);
}

public class GenericSingletonFactory {
    // Generic singleton factory pattern
    private static UnaryFunction<Object> IDENTITY_FUNCTION = new UnaryFunction<Object>() {
        public Object apply(Object arg) {
            return arg;
        }
    };

    // IDENTITY_FUNCTION is stateless and its type parameter is
    // unbounded so it's safe to share one instance across all types.
    @SuppressWarnings("unchecked")
    public static <T> UnaryFunction<T> identityFunction() {
        return (UnaryFunction<T>) IDENTITY_FUNCTION;
    }

    // Sample program to exercise generic singleton
    public static void main(String[] args) {
        String[] strings = { "jute", "hemp", "nylon" };
        UnaryFunction<String> sameString = identityFunction();
        for (String s : strings)
            System.out.println(sameString.apply(s));

        Number[] numbers = { 1, 2.0, 3L };
        UnaryFunction<Number> sameNumber = identityFunction();
        for (Number n : numbers)
            System.out.println(sameNumber.apply(n));
    }
}

示例方法程式碼:靜態方法模式

public class GenericStaticFactory {
    // Generic static factory method
    public static <K, V> HashMap<K, V> newHashMap() {
        return new HashMap<K, V>();
    }

    public static void main(String[] args) {
        // Parameterized type instance creation with static factory
        Map<String, List<String>> anagrams = newHashMap();
    }
}

泛型方法一個顯著特徵:無需明確指定型別引數的值,不像呼叫泛型構造器的時候必須指定一個型別。

簡而言之,使用泛型比使用需要在客戶端程式碼中進行轉換的型別來的更加安全,也更加容易。

相關文章