您的位置:首页 > 其它

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

2017-04-27 19:44 302 查看

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

在使用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<User> users = Arrays.asList(frank, lisa);

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

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

List<User> jsonToUsers = usersReader.readValue(usersJsonString);

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

}


以上的代码如果仔细想一下, 会觉得其实很奇怪!

Java的泛型不是做了类型擦除吗, 为什么还能够获得泛型的具体类信息, 然后将字符串反序列化成我需要的类型?

何为泛型擦除

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

public void generic() {
ArrayList<String> 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<String>
,
ArrayList<Integer>
还是
ArrayList<Date>
, 所以大家都是ArrayList, element都是Object也能用. 这就是类型擦除.

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

但是对于上面的:

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


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

class UserListTypeReference extends TypeReference<List<User>> {

}

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


UserListTypeReference类在编译生成UserListTypeReference.class文件的时候,它的泛型信息是确定的, 就是
List<User>
, 虽然运行部分的字节码中进行了泛型擦除,但是在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<String>
.

在main方法中, 我们去获取hobbies的泛型参数信息.

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

public String getName() {
return name;
}

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

public List<String> getHobbies() {
return hobbies;
}

public void setHobbies(List<String> 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 等, 都是使用了类似的机制.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: