1.背景
今天在使用JSON.parseObject
方法时,发现了有下面这个方法
/**
* <pre>
* String jsonStr = "[{\"id\":1001,\"name\":\"Jobs\"}]";
* List<Model> models = JSON.parseObject(jsonStr, new TypeReference<List<Model>>() {});
* </pre>
* @param text json string
* @param type type refernce
* @param features
* @return
*/
public static <T> T parseObject(String text, TypeReference<T> type, Feature... features) {
return (T) parseObject(text, type.type, ParserConfig.global, DEFAULT_PARSER_FEATURE, features);
}
就是可以利用Typereference
来保证返回参数时保留泛型,这样的话可以避免很多强转,看了一下这个方法的注解,意思是让我new TypeReference<List<Model>>() {}
,在TypeReference
的泛型中传入你想返回的泛型即可,于是我尝试自己写了一下,却报错了,代码如下
JSON.parseObject(JSON.toJSONString(ressult), new TypeReference<Res<CameraInfo>>());
我仔细看了这个方法的注解,发现其实不是直接new TypeReference<Res<CameraInfo>>()
,而是new
一个他的匿名内部类,我就在想为什么非要创建一个他的匿名内部类呢?而且这个匿名内部类里面什么也没有写,于是我继续去看了TypeReference
的源码,其获取泛型的方法如下
/**
* Constructs a new type literal. Derives represented class from type
* parameter.
*
* <p>Clients create an empty anonymous subclass. Doing so embeds the type
* parameter in the anonymous class's type hierarchy so we can reconstitute it
* at runtime despite erasure.
* <pre>
*
* 构造一个新类型文字。 从类型参数派生表示类。
* 客户端创建一个空的匿名子类。 这样做将在匿名类的类型层次结构中嵌入类型参数,这样我们就可以在运行时重新构造它,尽管已经擦除。
* </pre>
*/
protected TypeReference(){
//获取父类的带泛型的class
Type superClass = getClass().getGenericSuperclass();
//获取父类的泛型
Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
//缓存泛型并返回
Type cachedType = classTypeCache.get(type);
if (cachedType == null) {
classTypeCache.putIfAbsent(type, type);
cachedType = classTypeCache.get(type);
}
this.type = cachedType;
}
可以看到在方法的注释上,也注明了使用方法,即创建一个匿名子类来获取其泛型,而TypeReference
这个类的构造方法也是protected
修饰的,这样就保证了我们不能直接创建这个class
来进行使用。为什么非要子类呢?直接使用这个类不行吗?于是我到网上去详细了解了一下泛型以及泛型擦除等特性,找到以下两篇能很好解释的文章。
2.了解泛型的特性
Java 的泛型擦除和运行时泛型信息获取 这篇文章,是直接讲解Java 的泛型擦除和运行时泛型信息获取的,相当于直接解释了,为什么必须创建匿名子类才能获取到泛型内容,但是里面只是简单的提到了*简而言之,Java 的泛型擦除是有范围的,即类定义中的泛型是不会被擦除的。*但是并没有讲清楚擦除的范围是多大,什么时候泛型不会被擦除,以及为什么在这种情况下泛型不会被擦除。于是我继续详细了解泛型的特性
java泛型(二)、泛型的内部原理:类型擦除以及类型擦除带来的问题 这篇文章是对泛型的特性的详细介绍,讲的非常详细。而在其三、类型擦除引起的问题及解决方法中的3、类型擦除与多态的冲突和解决方法中我找到了我想要的东西,这一节十分详细的讲解了为什么在子类继承时,泛型不会被擦除–即利用桥方法,将父类的方法与子类的方法做一个转换。
3.疑问
如果是子类在继承时,泛型不会被擦除,那么为什么非要匿名子类?如果建一个类继承泛型类放在包中,平时使用时就使用这个子类可以吗?答案是不行的,或者说是不方便的。如果是想写一个类然后所有地方通用是不可行的,代码如下
public class SubTypeReference<T> extends TypeReference<T>{
//*********
}
这种情况下,子类的泛型类型还是会被擦除,即类型还是Object
,那么不能获取到泛型的类型,则达不到目的。当然了,可以重新很多个通过的子类,例如
public class SubTypeReference<String> extends TypeReference<String>{
//*********
}
这样的话,后面返回的泛型为String类型的,则可以通用这个子类,不过感觉这样意义不大,因为这样的话要重新很多个类放在包里,而且很多情况下,需要转换的泛型类型都更复杂,因此写匿名子类是最合适的,也是最方便的。
4.总结:
java
的泛型并非真正的泛型,只是会在编译阶段判断你的代码是否符合泛型的规定,如果不符合泛型要求,则会报错。而在编译之后,运行时,泛型的类型都是Object
或者指定的泛型继承类,获取的时候再通过强转返回- 通过子类继承的方式可以获取到泛型的内容。是因为父类虽然擦除了泛型的具体类型,但是子类在继承之后生成的字节码,是通过桥方法转换,这样就保留了我们指定的类型