Java 泛型及相关使用

面试时遇到一个问题,泛型如何获取真实的类型?GG,下来以后查了相关资料,看了几遍才懂是啥意思,写在这里牢记!

泛型

泛型——就是将类型的明确推迟到创建对象或执行方法时再进行。泛型相当于一个参数,修饰这个类实际上是什么。
举例来说:
ArrayList中,E就是一个未定的参数,而ArrayList中,Integer就被固定为一个确定的实际参数了。
整个ArrayList被称作一个泛型类型,而ArrayList被称作参数化的类型(ParameterizedType)。
定义一个含泛型参数T的类,然后在类中设置一个T变量,和在类中直接定义一个Object类相比,有如下的好处:

  1. 在创建这个泛型类时,T的类型就被固定下来了,不会出现被传入错误参数的情况;
  2. 获取T的时候,获取到的是实际类型,而不需要再进行一次强转,以免出现强转问题。

在实例化一个泛型类时,如果不使用<>指定类型,或者使用指定类型,那么泛型参数就相当于Object,泛型类就失去了泛型的作用。 在实例化一个泛型类时,如果使用定义,可以在?后面附加条件extends A或 super A。前者表示届时这个参数化类型变量应该是A的子类,后者表示届时这个参数化类型变量应该是B的超类(祖先类)。

此外,在定义时,可以使用来缩小填入的参数的类型范围。

泛型擦除

泛型类在编译后,<>中的类型信息会被擦除,在内存中实际表现为Object类。而在get()等方法需要返回确切类型的地方,实际的泛型参数类型会被直接写入。
例如ArrayList,在编译后,内存中仅表现为ArrayList,其get()方法会由

E get(){/* ... */}

变成

Integer get(){/*  ... */}

以ArrayList为例,调用get()方法从内部Object[]数组中拿到原始数据,然后强转为Integer并返回。

既然有泛型擦除的概念,那么对于一个表面定型,其实不定型的ArrayList来说,要插入一个别的元素也是可以做到的。使用反射拿到ArrayList的add(Object o)方法,即可插入一个其他元素。

    ArrayList<Integer> ai = new ArrayList<>();
    ai.add(123);
    try {
        ai.getClass().getDeclaredMethod("add",Object.class).invoke(ai,"string");
    } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
        e.printStackTrace();
    }
    for (Object o:ai)
        System.out.println(o);
    for (Integer i:ai)
        // 运行时会报ClassCastException,因为有一个String无法被转为Integer
        System.out.println(i);

泛型获取真实类型

泛型如何获取到真实类型呢?我们分为三种情况来分析

1.某个类继承了某个泛型类型

    private static class Generic<T extends Number>{
        T data;
        int id;
    }

    private static class ExtendedGeneric extends Generic<Integer>{
        private void te(){

        }
    }

    public static void main(String[] args) throws Exception {
        Type t= ((ParameterizedType) ExtendedGeneric.class.
                getGenericSuperclass()).getActualTypeArguments()
                [0];
        System.out.println(t.getTypeName());
    }
运行结果:java.lang.Integer

2.某个类实现了某个泛型接口


    private interface GenericInterface<P>{
        P getP();
    }

    private static class ImplGeneric implements GenericInterface<Pipe>{
        @Override
        public Pipe getP() {
            return null;
        }
    }

    public static void main(String[] args) throws Exception {
        Type t= ((ParameterizedType) ImplGeneric.class.
                getGenericInterfaces()[0]).getActualTypeArguments()
                [0];
        System.out.println(t.getTypeName());
    }
运行结果:java.nio.channels.Pipe

提示:这里和情况1很类似,只是父类只能有一个,接口可以有很多,所以getGenericSuperclass()返回的是一个Type,而getGenericInterfaces()返回的是一个Type[]数组。

3.某个方法的返回参数为泛型类型

这种情况下没有什么靠谱的方法,如果能确保这个泛型类型不为空,且值是统一的(不存在前述的那种反射写入其他类型的情况),可以通过不靠谱的方法得到。代码如下:

    public static void main(String[] args) throws Exception {
        ArrayList<String > strings = new ArrayList<>();
        strings.add("123");
        Class<?> c= ArrayList.class.getDeclaredMethod("get", int.class)
                .invoke(strings,0).getClass();
        System.out.println(c.getName());
    }
运行结果:java.lang.String
posted @ 2020-03-21 20:58  PraveZ  阅读(124)  评论(0编辑  收藏  举报