程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA編程入門知識 >> Effective Java之泛型使用介紹

Effective Java之泛型使用介紹

編輯:JAVA編程入門知識

泛型

不要在新代碼中使用原始類型

泛型(generic):聲明中具有一個或多個類型參數

原始類型(raw type):不帶任何實際類型參數的泛型名稱

格式: 類或接口的名稱 < 對應於泛型形式類型參數的實際參數 >

List<String> 就是對應於List<E>的實際參數為String的參數化類型

如與List<E>對應的原始類型是List

優點:

  • 在編譯時發現插入類型的錯誤(越早發現錯誤越好,編譯時發現好於運行時發現)
  • 不再需要手工轉換類型
   //JDK5之前的寫法,使用的是原始類型
    private static final List stringList = new ArrayList();

    //有了泛型之後的寫法,使用泛型
    private static final List<String> stringList2 = new ArrayList<String>();

    //JDK7 能將後面<>裡的類型省略,被稱為Diamond
    private static final List<String> stringList3 = new ArrayList<>();

    public static void main(String[] args) {
        String str = "test";

        Integer integer = 1;

        stringList.add(str);
        stringList.add(integer);//可通過編譯,但之後報ClassCastException錯誤

        stringList2.add(str);
//        stringList2.add(integer);//無法通過編譯

        for(Iterator iterator = stringList.iterator();iterator.hasNext();){
            String string = (String) iterator.next();
            System.out.println(string);
        }
        for(Iterator iterator = stringList2.iterator();iterator.hasNext();){
            String string =  iterator.next();
            System.out.println(string);
    }

ListList<Object>之間的區別?

List逃避了泛型檢查,List<Object>則是告知編譯器,它能夠持有任意類型的對象

無限制的通配符類型:
使用泛型,但不確定或者不關心實際的類型參數,可以用一個問號代替。如List<?>

泛型信息在運行時會被擦除

學習鏈接:

1.https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

2.http://stackoverflow.com/questions/313584/what-is-the-concept-of-erasure-in-generics-in-java

下面通過一個小demo說明類型擦除

      //類型擦除
        List<String> stringList = new ArrayList<>();
        List<Integer> integerList = new ArrayList<>();
        System.out.println(stringList.getClass().toString());
        System.out.println(integerList.getClass().toString());
        System.out.println(stringList.getClass()==integerList.getClass());



        integerList.add(100);
        Method method = integerList.getClass().getMethod("add",Object.class);
        method.invoke(integerList,"abc");

        System.out.println(integerList);

運行結果:

一般不在代碼中使用原始類型,除了兩種例外情況(都是因為泛型信息在運行時會被擦除):

  • 1.在類文字(class literals)中

如: List.class,String[].class,int.class都合法 List<String>.class,List<String>.class都不合法

  • 2.instanceof
  •   if(o instanceof Set){   //原始類型(Raw Type)
      Set<?> set = (Set<?>)o;//通配符類型(WildCard Type)
    }

下面的表格是泛型相關的術語:
泛型相關的術語

下面這張圖很好的介紹了無限制通配符和其他泛型符號之間的關系:

消除非受檢警告

始終在盡可能小的范圍內使用SuppressWarnings注解

Java源碼中的ArrayList類中有個toArray方法,其中就有強轉的警告:

 @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

最好是將范圍進一步縮小。將注解由整個方法到局部的變量聲明上去。

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

列表優於數組

  • 數組是協變的(covariant),泛型則是不可變的

   Object[] objectArray = new String[1];
   List<Object> objectList = new ArrayList<String>();//無法通過編譯 imcompatible types
   // String類是Object類的子類
   //String[]是Object[]的子類
   //而List<String>並不是List<String>的子類型  
  • 數組是具體化的(reified),在運行時才知道並檢查它們的元素類型約束。而泛型通過擦除來實現的。泛型只在編譯時強化類型信息,並在運行時擦除它們的元素類型信息。擦除就是使泛型可以與沒有使用泛型的代碼可以互用。

          Object[] objectArray = new String[1];
          List<String> objectList = new ArrayList<String>();
          objectArray[0] = 3;//可通過編譯,運行時報錯
//        objectList.add(1);//編譯時報錯

數組和泛型不能很好地混合使用。可用列表代替數組。

總結:數組是協變且可具體化的,泛型是不可變的且可被擦除的。-->數組提供了運行時類型安全而編譯時類型不安全。而泛型反之。

優先考慮泛型

泛型相比於Object的優點:

  • 不需要強制類型轉換
  • 編譯時類型安全
public class SomeClazz<T> {
    public Object dosthWithObj(Object obj){
        return obj;
    }
    
    public T dosthWithT(T t){
        return t;
    }

    public static void main(String[] args) {
        SomeClazz<Foo> someClazz = new SomeClazz<Foo>();
        Foo foo = new Foo();
        Foo foo1 = (Foo) someClazz.dosthWithObj(foo);
        Foo foo2 = someClazz.dosthWithT(foo);
    }
}
public class Stack<E> {
    private E [] elements;
    private static final int MAX_SIZE = 16;
    private int size = 0;

    @SuppressWarnings("unchecked")
    public Stack(){
        elements = (E[]) new Object[MAX_SIZE];
    }

    public void push(E e){
        ensureSize();
        elements[size++]=e;
    }

    public E pop(){
        if(size==0)
            throw new EmptyStackException();
        E e = elements[--size];
        elements[size]=null;
        return e;
    }

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

    public static void main(String[] args) {
        Stack<Integer> stack = new Stack<>();
        for(int i =0;i<50;i++){
            stack.push(i);
        }
        for(int i = 0;i<10;i++){
            System.out.println(i+": "+stack.pop());
        }
    }


}
class EmptyStackException extends RuntimeException{

}

前面曾鼓勵優先使用列表而不是數組。並不意味著所有的泛型中都要使用列表。況且Java並不是生來就支持列表的。

每個類型都是它自身的子類型。

如有 SomeClazz<E extends Number>

    SomeClazz<Number>是合法的

優先考慮泛型方法

方法可以考慮泛型化,特別是靜態工具方法。

泛型方法語法:

方法修飾語 泛型 返回值 方法名()

public static <T> T foo(T args);

/**
     * 使用泛型方法
     * 返回兩個集合的聯合
     * @param s1
     * @param s2
     * @param <E>
     * @return
     *
     * 局限:兩個參數和返回的結果的類型必須全部相同
     * 解決方法:使用有限制的通配符
     */
    public static <E> Set<E> unionGeneric(Set<E> s1,Set<E> s2){
        Set<E> result = new HashSet<>(s1);
        result.addAll(s2);
        return result;
    }

public static <K,V> Map<K,V> newHashMap(){
        return new HashMap<K,V>();
    }

泛型單例工廠:

 public interface UnaryFunction<T>{
        T apply(T arg);
    }
    private static UnaryFunction<Object> IDENTITY_FUNCTION =
            new UnaryFunction<Object>() {
                @Override
                public Object apply(Object arg) {
                    return arg;
                }
            };

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

    /**
     * 每次都要創建一個,很浪費,而且它是無狀態的.
     * 泛型被具體化了,每個類型都需要一個恆等函數,但是它們被擦除後,就只需要一個泛型單例了.
     * @param <T>
     * @return
     */
    public static <T> UnaryFunction<T> identityFunction2(){
        return new
                UnaryFunction<T>() {
                    @Override
                    public T apply(T arg) {
                        return arg;
                    }
                };
    }

遞歸類型限制:

通過某個包含該類型參數本身的表達式來限制類型參數

<T extends Comparable<T>>//針對可以與自身進行比較的每個類型T

利用有限制通配符來提升API的靈活性

參數化類型是不可變的。

雖然String類是Object類的子類,但是List<String>和List<Object>無關
/**
 * 棧的實現
 * @param <E>
 * API:
 *   public Stack();
 *   public void push(E e);
 *   public E pop();
 *   public boolean isEmpty();
 *
 * 新增API:
 *   before:
 *     public void pushAll(Iterable<E> i);
 *     public void popAll(Collection<E> c);
 *   after:
 *     使用有限制的通配符類型(bounded wildcard type)
 *    public void pushAll(Iterable<? extends E> i);
 *    public void popAll(Collection<? super E> c);
 *
 */

class Stack<E>{
    private E [] elements;
    private static final int INIT_CAPABILITY = 16;
    private int size = 0;
    @SuppressWarnings("unchecked")
    public Stack(){
        elements = (E[]) new Object [INIT_CAPABILITY];
    }
    public void push(E e){
        checkCapability();
        elements[size++]=e;
    }
    public E pop(){
        if(size==0)
            throw new RuntimeException("Empty Stack");
        E e = elements[--size];
        elements[size]=null;
        return e;
    }

    private void checkCapability() {
        if(size==elements.length)
            elements = Arrays.copyOf(elements,2*elements.length-1);
    }
    public boolean isEmpty(){
        return size==0;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        String start = super.toString();
        sb.append(start);
        for(int i = 0 ;i<size;i++){
            String s = " ["+elements[i]+"]";
            sb.append(s);
        }
        return sb.toString();
    }

    //before
//    public void pushAll(Iterable<E> i){
//        for(E e:i){
//            push(e);
//        }
//    }
//    public void popAll(Collection<E> c){
//        while (!isEmpty()){
//            c.add(pop());
//        }
//    }
    //after
    public void pushAll(Iterable<? extends E> i){
        for(E e:i){
            push(e);
        }
    }
    public void popAll(Collection<? super E> c){
        while(!isEmpty()){
            c.add(pop());
        }
    }



        Stack<Number> stack= new Stack<>();
        Iterable<Integer> integers = Arrays.asList(1,2,3,4,5);
        Collection<Object> objectCollection = new LinkedList<>();
        //before
//        stack.pushAll(integers);//參數類型不對
//        stack.popAll(objectCollection);//參數類型不對
        //after
        stack.pushAll(integers);
        System.out.println(stack);
        stack.popAll(objectCollection);
        System.out.println(stack);

從上面的Demo中我們知道,Java中提供了有限制的通配符類型來提高API的靈活性。

Collection<? extends E>

Collection<? super E>

一般在表示生產者消費者的輸入參數上使用通配符類型。

PECS:Producer-extends Consumer-super

      ------------------
     * 參數化類型  通配符類型
     *  T生產者   extends
     *  T消費者   super
     * ------------------
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved