lateinit 在 Kotlin 中的存在,因此您可以在创建包含它们的类时无法初始化的变量上具有不可为空的类型。
使用您的示例,如果您没有使用 lateinit,则必须将 subject 设为可为空,因为它必须使用值进行初始化。
public class MyTest {
var subject: TestSubject? = null
}
这会迫使你每次使用它时都进行空检查,这很丑陋,所以你可以将其标记为lateinit。
在 Java 中,您并没有真正遇到这个问题,因为一切都可以为空,并且声明一个未初始化的字段并没有什么特别的:
public class JavaTest {
TestSubject subject;
}
这会将subject 初始化为null,因此它实际上等同于非lateinit Kotlin 示例。
Kotlin 中的 lateinit 版本和 Java 版本之间唯一真正的区别是,当您尝试访问 Kotlin 中未初始化的属性时,您会遇到更具体的异常,即 UninitializedPropertyAccessException,这将使调试变得更容易而不是寻找通用NullPointerException 的原因。
如果您真的想要这种稍微不同的行为,您可以将您的 Java 属性包装在某种包装器中,但我认为这样做不值得语法开销。一个非常基本的(例如,不是线程安全的)方法是:
有一个通用的属性包装类:
public class Property<T> {
private T value = null;
public T get() {
if (value == null)
throw new UninitializedPropertyAccessException("Property has not been initialized");
return value;
}
public void set(T value) {
if (value == null)
throw new IllegalArgumentException("Value can't be null");
this.value = value;
}
}
在你的类中使用这个包装器:
public class JavaTest {
Property<TestSubject> subject = new Property<>();
}
然后这种用法会给你一个未初始化的异常:
JavaTest test = new JavaTest();
test.subject.get();
而这个运行良好:
JavaTest test = new JavaTest();
test.subject.set(new TestSubject());
test.subject.get();
编辑:这与lateinit 在 Kotlin 中的工作方式非常相似,如果您将示例的字节码反编译为 Java,您会得到以下结果:
public final class MyTest {
@NotNull
public TestSubject subject;
@NotNull
public final TestSubject getSubject() {
TestSubject var10000 = this.subject;
if(this.subject == null) {
Intrinsics.throwUninitializedPropertyAccessException("subject");
}
return var10000;
}
public final void setSubject(@NotNull TestSubject var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.subject = var1;
}
}
基本上,编译器将用于检查属性访问的代码放在类本身中(+ 使用一些辅助方法)而不是使用包装器,这样效率更高。