你不知道的泛型--获取具体的泛型参数类型
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 等, 都是使用了类似的机制.
相关文章推荐
- 黑马程序员-通过反射获取泛型参数类型
- java基础-反射 --通过反射 获取泛型实际类型参数
- Gson通过借助TypeToken获取泛型参数的类型的方法
- java获取泛型参数实际类型
- Gson通过借助TypeToken获取泛型参数的类型的方法
- 获取泛型参数的泛型类型
- Gson通过借助TypeToken获取泛型参数的类型的方法(转)
- Gson通过借助TypeToken获取泛型参数的类型的方法
- 黑马程序员——通过反射获取方法中泛型参数的指定类型
- 跳过编译器,获取泛型参数的实际类型
- 通过反射获取泛型参数类型
- 获取java泛型参数类型
- ParameterizedType获取java泛型参数类型
- ParameterizedType获取java泛型参数类型
- 跳过编译器,获取泛型参数的实际类型
- Java获取泛型参数的类型的方法 .
- 通过反射获取泛型的参数类型信息
- ParameterizedType获取java泛型参数类型
- 利用java反射获取泛型类的类型参数具体类对象
- 获取泛型类中的泛型参数的类型