在使用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 等, 都是使用了类似的机制.