data class 算是 kotlin 的一大特性,很多人对它非常喜欢。在设计 Kotlin 的时候,充分借鉴了 Java 语言中在写 JavaBean 中非常繁琐的特点,特意设计了 data class 这样的类型,用来定义数据类。
在项目中,我们通常会用 data class 来定义网络接口返回的响应数据体,一般写法如下:
data class User(var userId:Int,var name:String)
在使用 Gson 解析的时候,可以正常的生成 User 对象。省去了我们写 JavaBean 的大量代码。
但是这样写有两个问题,一是这个 User 类并没有无参构造器,如果我们需要实例化一个 User 对象的时候,需要传递很多参数,而且 User 类的参数一般会特别的多。
第二个问题是一个惨痛问题,在 Gson 解析的时候,如果 json 字符串中没有的字段,会被赋值为 null ,为此会引发空检测异常。比如在解析 {} 的时候,可以得到一个 User 对象,但是 userId 和 name 都是 null ,而我们明明定义的是 String 不是 String? 啊。
为了分析这个问题,我查了一个 Gson 的代码。
首先 ReflectiveTypeAdapterFactory 是引用类型的 Adapter 工厂。Adapter 里包含了一个 ObjectConstructor 对象,ObjectConstructor 的 construct() 用来创建对应类型的实例。
com.google.gson.internal.bind.ReflectiveTypeAdapterFactory
@Override public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type){
Class<? super T> raw = type.getRawType();
if (!Object.class.isAssignableFrom(raw)) {
return null; // it's a primitive!
}
ObjectConstructor<T> constructor = constructorConstructor.get(type);
return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
}
public static final class Adapter<T> extends TypeAdapter<T> {
@Override public T read(JsonReader in) throws IOException {
...
T instance = constructor.construct();
...
}
}
我们可以简化代码为两行,就是 Gson 需要找到一个合适的对象构造器,也就是 ObjectConstructor 对象,找到合适的对象构造器,就可以通过调用 construct() 方法来构造对象了。
ObjectConstructor<T> constructor = constructorConstructor.get(type);
T instance = constructor.construct();
我们再看 ConstructorConstructor.get() 方法是如何来获取一个合适的 ObjectConstructor 对象的。
首先前两步是从一个 instanceCreators 的 Map 里找。如果找不到,找默认构造器,newDefaultConstructor() 这个方法。如果这个类没有默认的构造器,那么从 newDefaultImplementationConstructor() 找,这个是找这个类的默认实现的构造器,这里主要是一些集合,比如 List 是一个抽象类,它的默认实现是 ArrayList 。如果最后还找不到,Gson 会通过 newUnsafeAllocator() 方法去找。
public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {
final Type type = typeToken.getType();
final Class<? super T> rawType = typeToken.getRawType();
// first try an instance creator
@SuppressWarnings("unchecked") // types must agree
final InstanceCreator<T> typeCreator = (InstanceCreator<T>) instanceCreators.get(type);
if (typeCreator != null) {
return new ObjectConstructor<T>() {
@Override public T construct() {
return typeCreator.createInstance(type);
}
};
}
// Next try raw type match for instance creators
@SuppressWarnings("unchecked") // types must agree
final InstanceCreator<T> rawTypeCreator =
(InstanceCreator<T>) instanceCreators.get(rawType);
if (rawTypeCreator != null) {
return new ObjectConstructor<T>() {
@Override public T construct() {
return rawTypeCreator.createInstance(type);
}
};
}
ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType);
if (defaultConstructor != null) {
return defaultConstructor;
}
ObjectConstructor<T> defaultImplementation = newDefaultImplementationConstructor(type, rawType);
if (defaultImplementation != null) {
return defaultImplementation;
}
// finally try unsafe
return newUnsafeAllocator(type, rawType);
}
无参构造器的处理其实很简单,就是通过反射,找到个类的无参构造器,并且实例化对象。
com.google.gson.internal.ConstructorConstructor
private <T> ObjectConstructor<T> newDefaultConstructor(Class<? super T> rawType) {
try {
final Constructor<? super T> constructor = rawType.getDeclaredConstructor();
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
return new ObjectConstructor<T>() {
@Override public T construct() {
try {
Object[] args = null;
return (T) constructor.newInstance(args);
}catch(...){
...
}
}
}catch(NoSuchMethodException e){
return null
}
}
这里的代码,我们也可以简化一下,这样就好理解了。
final Constructor<? super T> constructor = rawType.getDeclaredConstructor();
Object[] args = null;
return (T) constructor.newInstance(args);
我们在上面分析了无参构造器的情况,跳过了默认实现类的构造器的情况(我们自己写的类明显不符合这种 case),最后我们再来看 newUnsafeAllocator() 这个方法。
private <T> ObjectConstructor<T> newUnsafeAllocator(
final Type type, final Class<? super T> rawType) {
return new ObjectConstructor<T>() {
private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
@SuppressWarnings("unchecked")
@Override public T construct() {
try {
Object newInstance = unsafeAllocator.newInstance(rawType);
return (T) newInstance;
} catch (Exception e) {
throw new RuntimeException(("Unable to invoke no-args constructor for " + type + ". "
+ "Register an InstanceCreator with Gson for this type may fix this problem."), e);
}
}
};
}
这里我们也可以简化一下:
private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
Object newInstance = unsafeAllocator.newInstance(rawType);
于是我们重点看一下 UnsafeAllocator.newInstance() 方法。代码如下
try {
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
Field f = unsafeClass.getDeclaredField("theUnsafe");
f.setAccessible(true);
final Object unsafe = f.get(null);
final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
return new UnsafeAllocator() {
@Override
@SuppressWarnings("unchecked")
public <T> T newInstance(Class<T> c) throws Exception {
assertInstantiable(c);
return (T) allocateInstance.invoke(unsafe, c);
}
};
} catch (Exception ignored) {
}
我们也可以简化为:
Unsafe unsafe = sun.misc.Unsafe.getUnsafe()
Object object = unsafe.allocateInstance(clazz)
先通过 Unsafe 的静态方法 getUnsafe() 获得一个 Unsafe 对象,再调用 Unsafe 的成员方法 allocateInstance(Class<?> clazz) 获取 clazz 的对象。allocateInstance 方法是一个 native 方法。从方法名字上和设计上,可以大概猜出,它是直接在内存中开辟一个对象空间,所以通过这个方式获取的对象,成员变量都是初始值。
于是我们找到了我们定义的 data class ,明明声明的时候是不可空的,但是实际跑起来的时候空指针满天飞。
两种解决方案,一种是加问号,一种是提供无参构造器。
data class User(var id : Int?, var name : String?)
data class User(var id :Int = 0 , var name :String = "")
其中,无参构造器的写法,要求每一个参数都需要有默认值。
- EOF -
本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处,尊重他人劳动。
转载请注明:文章转载自 Binkery 技术博客 [https://binkery.com]
本文标题: Kotlin 暗坑之一个 data class 不可空类型为 null 的惨案
本文地址: https://binkery.com/archives/2020.08.16-Kotlin-一个DataClass不可空类型为null的惨案.html