赞
踩
偶然在一次debug中发现了一个按常理不该出现的NPE,用以下简化示例为例:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "kotlin.Lazy.getValue()" because "<local1>" is null
对应的数据模型如下:
class Book(
val id: Int,
val name: String?
) {
val summary by lazy { id.toString() + name }
}
发生在调用book.summary中。第一眼我是很疑惑了,怎么by lazy也能是null,因为summary本身就是一个委托属性,所以看看summary是怎么初始化的吧,反编译为java可知,在构造函数初始化,这完全没啥问题。
public final class Book { @NotNull private final Lazy summary$delegate; private final int id; @Nullable private final String name; @NotNull public final String getSummary() { Lazy var1 = this.summary$delegate; Object var3 = null; return (String)var1.getValue(); } ...略去其他 public Book(int id, @Nullable String name) { this.id = id; this.name = name; this.summary$delegate = LazyKt.lazy((Function0)(new Function0() { // $FF: synthetic method // $FF: bridge method public Object invoke() { return this.invoke(); } @NotNull public final String invoke() { return Book.this.getId() + Book.this.getName(); } })); } }
所以唯一的可能性就是构造函数并未执行。而这块逻辑是存在json的解析的,而Gson与kotlin的空安全问题老生常谈了,便立马往这个方向排查。
直接找到Gson里的ReflectiveTypeAdapterFactory
类,它是用于处理普通 Java 类的序列化和反序列化。作用是根据对象的类型和字段的反射信息,生成相应的 TypeAdapter
对象,以执行序列化和反序列化的操作。 然后再看到create
方法,这也是TypeAdapterFactory的抽象方法
@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! } FilterResult filterResult = ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw); if (filterResult == FilterResult.BLOCK_ALL) { throw new JsonIOException( "ReflectionAccessFilter does not permit using reflection for " + raw + ". Register a TypeAdapter for this type or adjust the access filter."); } boolean blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE; // If the type is actually a Java Record, we need to use the RecordAdapter instead. This will always be false // on JVMs that do not support records. if (ReflectionHelper.isRecord(raw)) { @SuppressWarnings("unchecked") TypeAdapter<T> adapter = (TypeAdapter<T>) new RecordAdapter<>(raw, getBoundFields(gson, type, raw, blockInaccessible, true), blockInaccessible); return adapter; } ObjectConstructor<T> constructor = constructorConstructor.get(type); return new FieldReflectionAdapter<>(constructor, getBoundFields(gson, type, raw, blockInaccessible, false)); }
最后到了ObjectConstructor<T> constructor = constructorConstructor.get(type);
这一句,这很明显是一个类的构造器,继续走到里面的get方法
public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) { final Type type = typeToken.getType(); final Class<? super T> rawType = typeToken.getRawType(); // ...省略其他部分逻辑 // First consider special constructors before checking for no-args constructors // below to avoid matching internal no-args constructors which might be added in // future JDK versions ObjectConstructor<T> specialConstructor = newSpecialCollectionConstructor(type, rawType); if (specialConstructor != null) { return specialConstructor; } FilterResult filterResult = ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, rawType); ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType, filterResult); if (defaultConstructor != null) { return defaultConstructor; } ObjectConstructor<T> defaultImplementation = newDefaultImplementationConstructor(type, rawType); if (defaultImplementation != null) { return defaultImplementation; } ... // Consider usage of Unsafe as reflection, return newUnsafeAllocator(rawType); }
先来看看前三个Constructor,
最后,只能走到了newUnsafeAllocator()
private <T> ObjectConstructor<T> newUnsafeAllocator(final Class<? super T> rawType) { if (useJdkUnsafe) { return new ObjectConstructor<T>() { @Override public T construct() { try { @SuppressWarnings("unchecked") T newInstance = (T) UnsafeAllocator.INSTANCE.newInstance(rawType); return newInstance; } catch (Exception e) { throw new RuntimeException(("Unable to create instance of " + rawType + ". " + "Registering an InstanceCreator or a TypeAdapter for this type, or adding a no-args " + "constructor may fix this problem."), e); } } }; } else { final String exceptionMessage = "Unable to create instance of " + rawType + "; usage of JDK Unsafe " + "is disabled. Registering an InstanceCreator or a TypeAdapter for this type, adding a no-args " + "constructor, or enabling usage of JDK Unsafe may fix this problem."; return new ObjectConstructor<T>() { @Override public T construct() { throw new JsonIOException(exceptionMessage); } }; } }
方法内部调用了UnsafeAllocator.INSTANCE.newInstance(rawType);
我手动尝试了一下可以创建出对应的实例,而且和通常的构造函数创建出来的实例有所区别
很明显,summary的委托属性是null的,说明该方法是不走构造函数来创建的,里面的实现是通过
Unsafe
类的allocateInstance
来直接创建对应ClassName的实例。
看到这便已经知道缘由了,那如何解决这个问题?
回到上面的Book反编译后的java代码,可以看到只要调用了构造函数即可,所以添加一个默认的无参构造函数便是一个可行的方案。改动如下:
class Book(
val id: Int = 0,
val name: String? = null
) {
val summary by lazy { id.toString() + name }
}
或者手动加一个无参构造函数
class Book(
val id: Int,
val name: String?
) {
constructor() : this(0, null)
val summary by lazy { id.toString() + name }
}
而且要特别注意一定要提供默认的无参构造函数,不然通过newUnsafeAllocator
创建的实例就导致kotlin的空安全机制就完全失效了
用moshi吧,用一个对kotlin支持比较好的json解析库即可。
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。