1. 程式人生 > >Java 基礎回顧:泛型和 Class 類

Java 基礎回顧:泛型和 Class 類

1、泛型

以 ArrayList 為例,在範型出現之前,ArrayList 的實現機制是內部管理一個 Object[] 型別的陣列。比如add 方法以前是 add(Object obj),現在是 add(E e)。那麼以前的時候顯然如果你定義一個 String 型別的 ArrayList,傳入 File 型別也是可以的,因為它也繼承自 Object。這顯然就會出現錯誤!但是有了泛型之後,傳入的只能是E型別的,不然會報錯。也就是說,泛型給我們提供了一種型別檢查的機制。

Java泛型是使用型別擦除來實現的。它的好處只是提供了編譯器型別檢查機制。在泛型方法內部無法獲取任何關於泛型引數型別的資訊,泛型引數只是起到了佔位的作用。如 List<String>

在執行時實際上是 List 型別,普通型別被擦除為 Object。因為泛型是擦除的,所以泛型不能用於顯式地引用執行時型別的操作中,例如:轉型、instanceof 操作或者 new 表示式。

另外,

  1. 使用泛型別的程式碼意味著可以被很多不同型別的物件重用;
  2. Java 編譯器最終將泛型版本編譯為無泛型版本;
  3. 使用 extends 的意義就在於指定了擦除的上界。

1.2 泛型類

泛型類的宣告與一般類的宣告語法一致,但需要在宣告的泛型類名稱後使用 <> 指定一個或多個型別引數,如:

class MyClass <E> { } 或 class MyClass <K,V> { }

1.3 泛型介面

泛型介面的宣告與一般介面的宣告語法一致,但需要在宣告的泛型介面名稱後使用 <> 指定一個或多個型別引數,如:

interface IMyMap <E> 或 intetface IMyMap <K,V> 

1.4 泛型方法

從下面的示例中,我們可以看出當定義了一個泛型方法的時候,可以提高程式碼的複用性。如:

public void method(T e) { } 或 public void method(List<?> list)

不過通常我們需要為泛型指定一個擦除上界來對泛型的範圍進行控制。

1.5 泛型引數的約束

<T extends 基類或介面> 或 <T extends 基類或介面1 & 基類或介面2>

前面的形式表示 T 需要是指定的基類或者介面的子類,後面的形式表示 T 需要是指定的介面或者基類 1 的子類並且是基類或者介面 2 的子類。

1.6 泛型的補償

1.6.1 建立例項

使用型別標籤來獲取指定型別的例項:

try {
    Person person = Person.class.newInstance();
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
}

但是使用上面的方式,要求指定的型別標籤必須有預設的構造器。

1.6.2 建立陣列

可以使用型別標籤+Array.newInstance()的方式實現:

int[] arr = (int[]) Array.newInstance(int.class, 5);

下面的這種方式在執行時不會出錯,但是在 main 方法中強制進行型別轉換的時候會出錯:

private static  <T> T[] createArray(T t) {
    T[] arr = (T[]) new Object[5];
    arr[0] = t;
    return arr;
}

public static void main(String ...args) {
    Integer[] array = createArray(5); // ClassCastException
    System.out.println(Arrays.toString(array));
}

這是因為它的執行時型別仍然是Object[],寫成下面的形式就不會錯了:

public static void main(String ...args) {
    Object[] array = createArray(5); // 不會出錯
    System.out.println(Arrays.toString(array));
    Integer integer = (Integer) array[0]; // 也不會錯
    System.out.println(integer);
}

因為有了擦除,陣列的執行時型別只能是 Object[]

1.6.3 自限定的型別

private static class SelfBounded<T extends SelfBounded<T>> {}

private static class A extends SelfBounded<A> {}

private static class B extends SelfBounded<A> {}

private static class C extends SelfBounded {}

// private static class D extends SelfBounded<C> {}  // 錯誤!

// private static class E extends SelfBounded<B> {}  // 錯誤!

// private static class F extends SelfBounded<D> {}  // 錯誤!

可以看出,當定義了 class M extends SelfBounded<N> 的時候,這裡對N的要求是它的必須實現了 SelfBounded<N>

1.6.4 泛類與子類

雖然 Object obj = new Integer(123); 是可行的,但是 ArraylList<Object> ao = new ArrayList<Integer>(); 是錯誤的。因為 IntegerObject 的派生類,但是 ArrayList<Integer> 不是 ArraylList<Object> 的派生類。

1.6.5 萬用字元

根據上面的泛類與子類的關係,如果要實現一個函式,如

void PrintArrayList(ArrayList<Object> c){
    for(Object obj:c){}
}

那麼 ArrayList<Integer>(10) 的例項是無法傳入到該函式中的,因為 ArrayList<Integer>ArrayList<Object> 是沒有繼承關係的。在這種情況下就可以使用萬用字元解決這個問題。我們可以定義上面的函式為如下形式,這樣就可以將泛型傳入了。

PrintArrayList(ArrayList<?>c) {
    for(Object obj:c){}
}

當然,也可以指定萬用字元 ? 的約束,即將其寫成下面的形式

<? extends 基類>
<? super 派生類>

就是在執行時指定擦除的邊界。

2、Class 類

Class 類包含了與類某個類有關的資訊,Class 也支援泛型。它們的效果是相同的,只是使用泛型具有編譯器型別檢查的效果,相對更加安全。

以下是使用Class類的一個測試例子。在這裡我們可以通過反射來獲取並修改 private方法private欄位accessible 屬性。當設定為 true 時,我們就可以對其進行呼叫或者修改。

此外,我們還可以進行自定義註解以及獲取類、方法和欄位的註解等資訊。

示例 1:獲取 Class 物件的屬性資訊,修改 private 型別的欄位,呼叫 private 方法:

public static void main(String ...args) {
    Class<SubClass> subClass = SubClass.class;

    try {
        // 如果SubClass是private型別的,那麼會丟擲以下異常:
        // java.lang.IllegalAccessException: Class me.shouheng.rtti.RttiTest can not access a member of class
        // me.shouheng.rtti.RttiTest$SubClass with modifiers "private"
        SubClass sub = subClass.newInstance();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }

輸出 Class 類的方法,這裡輸出的結果中包含了超類的方法:

    System.out.print("\nMethods:\n");
    System.out.println(Arrays.toString(subClass.getMethods()));

輸出 Class 內部定義的方法,它只輸出了在 SubClass 中新加入的方法的定義:

    System.out.print("\nDeclaredMethods:\n");
    System.out.println(Arrays.toString(subClass.getDeclaredMethods()));

    System.out.print("\nMethodInformation:\n");
    printMethodInfo(subClass);

輸出 Class 的欄位,輸出的結果為空:

    System.out.print("\nFields:\n");
    System.out.println(Arrays.toString(subClass.getFields()));

輸出 Class 中定義的欄位:

    System.out.print("\nDeclaredFields:\n");
    System.out.println(Arrays.toString(subClass.getDeclaredFields()));

    System.out.print("\nFieldInformation:\n");
    printFieldInfo(subClass);

    System.out.print("\nClassInformation:\n");
    printClassInfo(subClass);
}

private static void printMethodInfo(Class<?> c) {
    SubClass sub = new SubClass();
    sub.setName("My Simple Name");
    // 列印方法資訊
    Method[] methods = c.getDeclaredMethods();
    Method method = methods[0];
    System.out.println(method.getName());
    System.out.println(method.getReturnType());

輸出方法的註解資訊,對於能夠輸出的註解資訊是有要求的:

    // 輸出註解資訊
    Annotation[] annotations = method.getDeclaredAnnotations();
    for (Annotation annotation : annotations) {
        System.out.println(annotation);
    }

可以在獲取了方法的Method物件之後呼叫它的invoke方法進行觸發:

    // 觸發方法
    try {
        System.out.println(method.invoke(sub));
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }

修改方法的屬性,這裡我們是已經知道out方法是第二個方法的前提下進行的。還有直接使用Class物件的getMethod嘗試使用方法名獲取方法是不行的:

    try {
        // 設定out方法的accessible為true,這樣可以從外部呼叫該方法
        method = methods[1];
        method.setAccessible(true);
        // 直接根據名稱來獲取out方法是不行的
//      method = c.getMethod("out");
//      method.setAccessible(true);
        method.invoke(sub);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
}

private static void printFieldInfo(Class<?> c) {
    SubClass sub = new SubClass();
    sub.setName("My Simple Name");
    Field[] fields = c.getDeclaredFields();
    Field field = fields[0];
    try {
        System.out.println(field.get(sub));
    } catch (IllegalAccessException e) {
        System.out.println(e);
    }
    Annotation[] annotations = field.getAnnotations();
    for (Annotation annotation : annotations) {
        System.out.println(annotation);
    }
    System.out.println(field.isAccessible());
    System.out.println(field.getName());

我們可以通過修改一個欄位的訪問性來修改這個欄位的屬性:

    field.setAccessible(true);
    try {
        field.set(sub, "Shit");
        System.out.println(field.get(sub));
    } catch (IllegalAccessException e) {
        System.out.println(e);
    }
}

private static void printClassInfo(Class<?> c) {
    System.out.println(Arrays.toString(c.getAnnotations()));
    System.out.println(c.getCanonicalName());
    System.out.println(c.getClass());
    System.out.println(c.getGenericSuperclass());
    System.out.println(Arrays.toString(c.getGenericInterfaces()));
    System.out.println(Arrays.toString(c.getConstructors()));
    System.out.println(c.getPackage());
}

private static class SuperClass {}

// private 型別的不行
@Deprecated
public static class SubClass extends SuperClass implements Interface {
    @me.shouheng.rtti.Field
    @NotNull
    private String name;
    private SuperClass sup;

    @Deprecated
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public SuperClass getSup() {
        return sup;
    }

    public void setSup(SuperClass sup) {
        this.sup = sup;
    }

    private void out() {
        System.out.println("private method in SubClass");
    }
}

private interface Interface {}

輸出結果:

Methods:
[public java.lang.String me.shouheng.rtti.RttiTest$SubClass.getName(), public void me.shouheng.rtti.RttiTest$SubClass.setName(java.lang.String), public me.shouheng.rtti.RttiTest$SuperClass me.shouheng.rtti.RttiTest$SubClass.getSup(), public void me.shouheng.rtti.RttiTest$SubClass.setSup(me.shouheng.rtti.RttiTest$SuperClass), public final void java.lang.Object.wait() throws java.lang.InterruptedException, public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException, public boolean java.lang.Object.equals(java.lang.Object), public java.lang.String java.lang.Object.toString(), public native int java.lang.Object.hashCode(), public final native java.lang.Class java.lang.Object.getClass(), public final native void java.lang.Object.notify(), public final native void java.lang.Object.notifyAll()]

DeclaredMethods:
[public java.lang.String me.shouheng.rtti.RttiTest$SubClass.getName(), private void me.shouheng.rtti.RttiTest$SubClass.out(), public void me.shouheng.rtti.RttiTest$SubClass.setName(java.lang.String), public me.shouheng.rtti.RttiTest$SuperClass me.shouheng.rtti.RttiTest$SubClass.getSup(), public void me.shouheng.rtti.RttiTest$SubClass.setSup(me.shouheng.rtti.RttiTest$SuperClass)]

MethodInformation:
getName
class java.lang.String
@java.lang.Deprecated()
My Simple Name
private method in SubClass

Fields:
[]

DeclaredFields:
[private java.lang.String me.shouheng.rtti.RttiTest$SubClass.name, private me.shouheng.rtti.RttiTest$SuperClass me.shouheng.rtti.RttiTest$SubClass.sup]

FieldInformation:
java.lang.IllegalAccessException: Class me.shouheng.rtti.RttiTest can not access a member of class me.shouheng.rtti.RttiTest$SubClass with modifiers "private"
@me.shouheng.rtti.Field()
false
name
Shit

ClassInformation:
[@java.lang.Deprecated()]
me.shouheng.rtti.RttiTest.SubClass
class java.lang.Class
class me.shouheng.rtti.RttiTest$SuperClass
[interface me.shouheng.rtti.RttiTest$Interface]
[public me.shouheng.rtti.RttiTest$SubClass()]
package me.shouheng.rtti

示例2:泛型引數的獲取:

下面是在實際的框架設計中會用到的一些方法,它嘗試從類的泛型中獲取泛型的名稱:

private static class Model { }

private static class Product extends Model { }

private static class Store<T extends Model> {

    public Store() {
        Class cls = this.getClass();
        ParameterizedType pt = (ParameterizedType) cls.getGenericSuperclass();
        Type[] args = pt.getActualTypeArguments();

        System.out.println(cls);
        System.out.println(pt);
        System.out.println(Arrays.toString(args));
        System.out.println(((Class) args[0]).getSimpleName());
    }
}

private static class ProductStore extends Store<Product> { }

public static void main(String ...args) {
    ProductStore store = new ProductStore();
}

輸出結果:

class me.shouheng.rtti.RttiTest$ProductStore
me.shouheng.rtti.RttiTest.me.shouheng.rtti.RttiTest$Store<me.shouheng.rtti.RttiTest$Product>
[class me.shouheng.rtti.RttiTest$Product]
Product