你不知道的泛型--获取具体的泛型参数类型

震惊! 竟然可以获取泛型参数信息

在使用Jackson,Gson等需要将字符串反序列化成类的时候, 我们可以使用以下的方式来生成具体的类, 而不是只有array和map的JsonObject.
见以下代码, 定义一个简单的user类.

class User {
    private String name;
    private int age;

    public User() {}
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // getter/setter
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
    public int getAge() {return age;}
    public void setAge(int age) {this.age = age;}

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        User user = (User) o;
        if (age != user.age) {
            return false;
        }
        return name != null ? name.equals(user.name) : user.name == null;
    }
public class JacksonTest {
    public static void main(String[] args) throws IOException {

        //生成一个user list

        User frank = new User("frank", 20);
        User lisa = new User("lisa", 18);
        List users = Arrays.asList(frank, lisa);

        //将这个user list序列化成json字符串, 结果如下
        //[{"name":"frank","age":20},{"name":"lisa","age":18}]
        String usersJsonString = new ObjectMapper().writeValueAsString(users);

        //对生成的字符串进行反序列成java对象. 可以直接反序列化成List
        ObjectReader usersReader = new ObjectMapper().readerFor(new TypeReference>() {
        });

        List jsonToUsers = usersReader.readValue(usersJsonString);

        //比较原先的list和反序列生成的list是否相同. assert通过
        assert jsonToUsers.equals(users);
    }

}

以上的代码如果仔细想一下, 会觉得其实很奇怪!
Java的泛型不是做了类型擦除吗, 为什么还能够获得泛型的具体类信息, 然后将字符串反序列化成我需要的类型?

何为泛型擦除

Java的泛型擦除导致无法在运行时获得类型信息. 比如List,List, 它们的类型都是List.class, JVM运行时无法区分它们.例如以下的代码, 直到最后一步才抛异常: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String.

    public void generic() {
        ArrayList names = new ArrayList<>();
        names.add("tom");
        names.add("jerry");
        ArrayList objects=names;
        //JVM无法区别这到底是一个String的list, 还是一个Integer的list,还是其他的list
        objects.add(1);
        objects.add(new Date());
        //直到这一步才抛异常
        String aDate = names.get(2);
    }

因为所有的ArrayList泛型类都是使用了同一份代码: ArrayList.class. 其内部就是将所有的element的引用认为是Object引用. Java的大神们在编译JDK的ArrayList时, 不知道你将来将要使用的泛型是ArrayList, ArrayList 还是ArrayList, 所以大家都是ArrayList, element都是Object也能用. 这就是类型擦除.

为什么又能获取具体的泛型参数

但是对于上面的:

ObjectReader usersReader = new ObjectMapper().readerFor(new TypeReference>() {
        });

其实new TypeReference>() {}是一个匿名类实例, 我们在编译代码时, 会生成一个新的class文件. 上面的代码要是写的更直白一些, 等价于下面的代码:

class UserListTypeReference extends TypeReference> {

}

ObjectReader usersReader=new ObjectMapper().readerFor(new UserListTypeReference());

UserListTypeReference类在编译生成UserListTypeReference.class文件的时候,它的泛型信息是确定的, 就是List, 虽然运行部分的字节码中进行了泛型擦除,但是在class文件中保存了它的泛型信息的,所以上面Jackson转换的时候就可以知道泛型信息.
使用以下代码可以验证上面的结论:

    public static void main(String[] args) {
        UserListTypeReference userListTypeReference = new UserListTypeReference();
        ParameterizedType genericSuperclass = (ParameterizedType) userListTypeReference.getClass().getGenericSuperclass();
        //获取users的Type
        ParameterizedType usersType = (ParameterizedType) genericSuperclass.getActualTypeArguments()[0];

        Type rawType = usersType.getRawType();
        //users的原生类型是List
        assert rawType == List.class;

        Type type = usersType.getActualTypeArguments()[0];
        //users的泛型参数类型是String
        assert type == String.class;
    }

我要更多

不仅仅是对超类是指定了泛型参数的类的类来说, 可以获得泛型参数信息. 如果一个类的字段, 或者方法(返回值, 传入值)已经指定了其泛型参数, 那么也可以获得这个泛型参数. 因为这些信息在编译时, 就已经写到了class文件中.

对于以下的一个User类. 主要看hobbies field, 其类型是List.
在main方法中, 我们去获取hobbies的泛型参数信息.

public class User {
    private String name;
    private List hobbies;

    public String getName() {
        return name;
    }

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

    public List getHobbies() {
        return hobbies;
    }

    public void setHobbies(List hobbies) {
        this.hobbies = hobbies;
    }
}

我们可以使用以下方法, 获取hobbies的类型及其泛型参数. 也可以获得getHobbies方法的返回值类型及其泛型参数. 当然也可以获得setHobbies方法的传入值类型及其泛型参数.

    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
        Field hobbies = User.class.getDeclaredField("hobbies");

        Class hobbiesClass = hobbies.getDeclaringClass();
        //hobbies的类型是List类
        assert hobbiesClass == List.class;

        ParameterizedType parameterizedType = (ParameterizedType) hobbies.getGenericType();
        //hobbies的List类型的泛型参数是String类
        assert parameterizedType.getActualTypeArguments()[0] == String.class;



        Method getHobbies = User.class.getDeclaredMethod("getHobbies");
        //getHobbies的返回值类型是List类
        Class getHobbiesReturnType = getHobbies.getReturnType();
        assert getHobbiesReturnType == List.class;

        //getHobbies的返回值类型List的泛型参数是String类
        ParameterizedType genericReturnType = (ParameterizedType) getHobbies.getGenericReturnType();
        assert genericReturnType.getActualTypeArguments()[0] == String.class;



        Method setHobbies = User.class.getDeclaredMethod("setHobbies", List.class);
        //setHobbies方法的第一个参数类型是List类
        Class setHobbiesParameter = setHobbies.getParameterTypes()[0];
        assert setHobbiesParameter == List.class;

        //setHobbies方法的第一个参数类型List的泛型参数是String类
        ParameterizedType setHobbiesParameterGeneric = (ParameterizedType) getHobbies.getGenericReturnType();
        assert setHobbiesParameterGeneric.getActualTypeArguments()[0] == String.class;
    }

总结

Java的泛型是个很复杂的东西. 泛型擦除无处不在. 对于具体化的代码, 其字段, 方法返回值,泛型参数在编译时是可知的话,这些信息是被写到了生成的class文件中的.可以通过java的反射api获取泛型参数类型.
这个特点非常有用, 例如很多JSON框架, Jackson, Gson有类似的类(TypeReference,TypeToken),Spring的泛型注入, 以及spring的泛型事件监听器如:class ContextRefreshListener implements ApplicationListener 等, 都是使用了类似的机制.

你可能感兴趣的:(java)