1
BUG重现与原因分析
下面这段代码会抛出类型转换异常(ClassCastException
),JVM
给出的解释是:不能将Double
类型对象转换String
类型 (java.lang.Double connot be cast to java.lang.String
)。
public class JsonUtilTest{
@Test
public void testToObjectArray() {
String jsonArray = "[222.22,11.22,12.24]";
List<String> list = JsonUtils.fromJsonArray(jsonArray, String.class);
String item = list.get(0);
}
}
根据异常栈信息得知类型转换异常发生在String item = list.get(0);
这行代码。
可是解析都正常,为什么调用List
的get
方法却抛出类型转换异常呢?
这就不得不提泛型的"类型擦除"了。
List<String>
经过类型擦除后变为裸类型List
, 而List
存储的元素类型变为Object
类型,上面的代码编译后等价于:
public class JsonUtilTest{
@Test
public void testToObjectArray() {
String jsonArray = "[222.22,11.22,12.24]";
List list = JsonUtils.fromJsonArray(jsonArray, String.class);
String item = (String)list.get(0);
}
}
由此可以定位到问题就出在JsonUtils
的fromJsonArray
方法。fromJsonArray
将json
解析为Double
类型的数组了, 所以会抛出ClassCastException
异常,Double
类型对象强制转为String
类型失败。
JsonUtils
工具类是笔者为项目封装的一个Json
解析工具类,目的是适配多个json
解析框架。
例子中调用
JsonUtils
的
fromJsonArray
方法可能是调用
GsonParser
的
fromJsonArray
方法,也可能是调用
JacksonParser
的
fromJsonArray
方法,会根据项目中依赖了哪个
json
解析框架决定。
假设我们项目中使用的是Gson
,那么调用JsonUtils
的fromJsonArray
方法最终会调用GsonParser
的fromJsonArray
方法, GsonParser
实现的fromJsonArray
方法如下:
public class GsonParser implements JsonParser{
@Override
public <T> List<T> fromJsonArray(String jsonStr, Class<T> tClass) {
GsonBuilder gsonBuilder = new GsonBuilder();
//.registerTypeAdapter(Date.class, new DateTypeAdapter(null))
//.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeTypeAdapter(null))
//.addDeserializationExclusionStrategy(new GsonExclusionStrategy());
return gsonBuilder.create().fromJson(jsonStr, new TypeToken<List<T>>(){}.getType());
}
}
问题就出现在new TypeToken<List<T>>(){}.getType()
这行,这行代码编译后会生成一个继承TypeToken
的匿名内部类, 但由于TypeToken
指定的参数化类型为List<T>
,将getType()
方法返回的Type
对象传给Gson
框架, Gson
框架是不知道List<T>
的参数化类型T
是什么的。Gson
框架只知道将json
解析为一个List
,但不知道 List
的参数化类型T
是什么,所以就根据json
的信息将其转换为Double
类型了。
我们来看个例子:
public class GsonTypeTokenTest{
private <T> void getTypeToken2() {
Type type = new TypeToken<List<T>>() {}.getType();
System.out.println(type);
}
private void getTypeToken1() {
Type type = new TypeToken<List<String>>() {}.getType();
System.out.println(type);
}
@Test
public void testTypeToken() {
getTypeToken1();
getTypeToken2();
}
}
上面代码输出的结果如下:
java.util.List<java.lang.String>
java.util.List<T>
从结果可以看出,getTypeToken2
方法我们无法获取到List
的参数化类型T
的实际类型,而getTypeToken1
方法中指定了List
的参数化类型为String
, 因此能够获取到。
2
BUG修复
如果只是使用Gson
解析框架,修改该BUG
的办法很简单,将GsonParser
的fromJsonArray
方法改为如下即可:
public <T> List<T> fromJsonArray(String jsonStr, TypeToken<List<T>> type){
.....
return gsonBuilder.create().fromJson(jsonStr, type.getType());
}
因为笔者写的JsonUtils
工具类要适配多种解析框架,因此我们不能使用Gson
框架的TypeToken
, 也不能使用Jackson
框架的TypeReference
,而是抽象出一个中间类。
- 1、自定义
TypeReference
public abstract class TypeReference<T> {
protected final Type _type;
protected TypeReference() {
Type superClass = this.getClass().getGenericSuperclass();
if (superClass instanceof Class) {
throw new IllegalArgumentException("TypeReference constructed without actual type information");
} else {
this._type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
}
}
public Type getType() {
return this._type;
}
}
- 2、修改
JsonParser
接口的fromJsonArray
方法
public interface JsonParser {
<T> String toJsonString(T obj, boolean serializeNulls, String pattern);
<T> T fromJson(String jsonStr, Class<T> tClass);
<T> List<T> fromJsonArray(String jsonStr, TypeReference<List<T>> typeReference);
}
- 3、修改
GsonParser
与JacksonParser
的fromJsonArray
方法
GsonParser
的fromJsonArray
方法修改后如下:
public class GsonParser implements JsonParser {
@Override
public <T> List<T> fromJsonArray(String jsonStr, TypeReference<List<T>> typeReference) {
GsonBuilder gsonBuilder = new GsonBuilder();
//.registerTypeAdapter(Date.class, new DateTypeAdapter(null))
//.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeTypeAdapter(null))
//.addDeserializationExclusionStrategy(new GsonExclusionStrategy());
return gsonBuilder.create().fromJson(jsonStr, typeReference.getType());
}
}
JacksonParser
的fromJsonArray
方法修改后如下:
public class JacksonParser implements JsonParser {
@Override
public <T> List<T> fromJsonArray(String jsonStr, TypeReference<List<T>> typeReference) {
ObjectMapper objectMapper = new ObjectMapper();
// ......
try {
return objectMapper.readValue(jsonStr, new com.fasterxml.jackson.core.type.TypeReference() {
@Override
public Type getType() {
return typeReference.getType();
}
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
- 4、修改
JsonUtils
的fromJsonArray
方法
public class JsonUtils {
private static JsonParser chooseJsonParser;
static {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
classLoader.loadClass("com.google.gson.GsonBuilder");
chooseJsonParser = new GsonParser();
} catch (ClassNotFoundException e) {
try {
classLoader.loadClass("com.fasterxml.jackson.databind.ObjectMapper");
chooseJsonParser = new JacksonParser();
} catch (ClassNotFoundException ex) {
throw new RuntimeException("未找到任务json包,请先在当前项目的依赖配置文件中加入 gson或fackson");
}
}
}
public static <T> String toJsonString(T obj) {
return toJsonString(obj, false, null);
}
public static <T> String toJsonString(T obj, boolean serializeNulls) {
return toJsonString(obj, serializeNulls, null);
}
public static <T> String toJsonString(T obj, boolean serializeNulls, String datePattern) {
return chooseJsonParser.toJsonString(obj, serializeNulls, datePattern);
}
public static <T> T fromJson(String jsonStr, Class<T> tClass) {
return chooseJsonParser.fromJson(jsonStr, tClass);
}
// 修改后的fromJsonArray方法
public static <T> List<T> fromJsonArray(String jsonStr, TypeReference<List<T>> typeReference) {
return chooseJsonParser.fromJsonArray(jsonStr, typeReference);
}
}