• 【Java】泛型 之 泛型和反射


    Java的部分反射API也是泛型。例如:Class就是泛型:

    // compile warning:
    Class clazz = String.class;
    String str = (String) clazz.newInstance();
    
    // no warning:
    Class<String> clazz = String.class;
    String str = clazz.newInstance();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    调用ClassgetSuperclass()方法返回的Class类型是Class

    Class sup = String.class.getSuperclass();
    构造方法Constructor也是泛型:

    Class<Integer> clazz = Integer.class;
    Constructor<Integer> cons = clazz.getConstructor(int.class);
    Integer i = cons.newInstance(123);
    
    • 1
    • 2
    • 3

    我们可以声明带泛型的数组,但不能用new操作符创建带泛型的数组:

    Pair<String>[] ps = null; // ok
    Pair<String>[] ps = new Pair<String>[2]; // compile error!
    
    • 1
    • 2

    必须通过强制转型实现带泛型的数组:

    @SuppressWarnings("unchecked")
    Pair<String>[] ps = (Pair<String>[]) new Pair[2];
    
    • 1
    • 2

    使用泛型数组要特别小心,因为数组实际上在运行期没有泛型,编译器可以强制检查变量ps,因为它的类型是泛型数组。但是,编译器不会检查变量arr,因为它不是泛型数组。因为这两个变量实际上指向同一个数组,所以,操作arr可能导致从ps获取元素时报错,例如,以下代码演示了不安全地使用带泛型的数组:

    Pair[] arr = new Pair[2];
    Pair<String>[] ps = (Pair<String>[]) arr;
    
    ps[0] = new Pair<String>("a", "b");
    arr[1] = new Pair<Integer>(1, 2);
    
    // ClassCastException:
    Pair<String> p = ps[1];
    String s = p.getFirst();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    要安全地使用泛型数组,必须扔掉arr的引用:

    @SuppressWarnings("unchecked")
    Pair<String>[] ps = (Pair<String>[]) new Pair[2];
    
    • 1
    • 2

    上面的代码中,由于拿不到原始数组的引用,就只能对泛型数组ps进行操作,这种操作就是安全的。

    带泛型的数组实际上是编译器的类型擦除:

    Pair[] arr = new Pair[2];
    Pair<String>[] ps = (Pair<String>[]) arr;
    
    System.out.println(ps.getClass() == Pair[].class); // true
    
    String s1 = (String) arr[0].getFirst();
    String s2 = ps[0].getFirst();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    所以我们不能直接创建泛型数组T[],因为擦拭后代码变为Object[]:

    // compile error:
    public class Abc<T> {
        T[] createArray() {
            return new T[5];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    必须借助Class来创建泛型数组:

    T[] createArray(Class<T> cls) {
        return (T[]) Array.newInstance(cls, 5);
    }
    
    • 1
    • 2
    • 3

    我们还可以利用可变参数创建泛型数组T[]:

    public class ArrayHelper {
        @SafeVarargs
        static <T> T[] asArray(T... objs) {
            return objs;
        }
    }
    
    String[] ss = ArrayHelper.asArray("a", "b", "c");
    Integer[] ns = ArrayHelper.asArray(1, 2, 3);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    谨慎使用泛型可变参数
    在上面的例子中,我们看到,通过:

    static  T[] asArray(T... objs) {
        return objs;
    }
    
    • 1
    • 2
    • 3

    似乎可以安全地创建一个泛型数组。但实际上,这种方法非常危险。以下代码来自《Effective Java》的示例:

    import java.util.Arrays;
    
    public class Main {
        public static void main(String[] args) {
            String[] arr = asArray("one", "two", "three");
            System.out.println(Arrays.toString(arr));
            // ClassCastException:
            String[] firstTwo = pickTwo("one", "two", "three");
            System.out.println(Arrays.toString(firstTwo));
        }
    
        static <K> K[] pickTwo(K k1, K k2, K k3) {
            return asArray(k1, k2);
        }
    
        static <T> T[] asArray(T... objs) {
            return objs;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    直接调用asArray(T...)似乎没有问题,但是在另一个方法中,我们返回一个泛型数组就会产生ClassCastException,原因还是因为擦拭法,在pickTwo()方法内部,编译器无法检测K[]的正确类型,因此返回了Object[]

    如果仔细观察,可以发现编译器对所有可变泛型参数都会发出警告,除非确认完全没有问题,才可以用@SafeVarargs消除警告。

    如果在方法内部创建了泛型数组,最好不要将它返回给外部使用。
    更详细的解释请参考《Effective Java》“Item 32: Combine generics and varargs judiciously”。

    小结

    • 部分反射API是泛型,例如:Class,Constructor;
    • 可以声明带泛型的数组,但不能直接创建带泛型的数组,必须强制转型;
    • 可以通过Array.newInstance(Class, int)创建T[]数组,需要强制转型;
    • 同时使用泛型和可变参数时需要特别小心。
  • 相关阅读:
    【Bug】Unable to make field private final int java.time.LocalDate.year accessible
    JS 模块化- 04 CMD 规范与 Sea JS
    彻底搞懂Mybatis
    java私钥加密——SHA256withRSA
    extern “C“的用法
    【MongoDB】集群搭建实战 | 副本集 Replica-Set | 分片集群 Shard-Cluster | 安全认证
    mysql查看当前连接数
    Android View自定义参数declare-styleable介绍与使用
    C++ Reference: Standard C++ Library reference: C Library: cwchar: getwc
    jmeter 简单数据写入器 创建文件失败
  • 原文地址:https://blog.csdn.net/ihero/article/details/133277020