關注“Java藝術”一起來充電吧!
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);
}
}
公衆號:Java藝術
掃碼關注最新動態