在超类的 init 块中要求引发 IllegalArgumentException [Kotlin]
Require in superclass' init block raises IllegalArgumentException [Kotlin]
早上好 Kotlin 大师们。
我有一个继承结构,其中抽象超类实现了一些共享数据检查。编译器没有抱怨,但是在执行时 JVM 抛出 IllegalArgumentException
代码
fun main(args: Array<String>) {
val foo = Child("NOT_BLANK")
}
abstract class Parent(
open val name: String = "NOT_BLANK"
) {
init {
require(name.isNotBlank()) { "Firstname must not be blank" }
}
}
data class Child(
override val name: String = "NOT_BLANK"
) : Parent(
name = name
)
异常如下所示
Exception in thread "main" java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.text.StringsKt__StringsJVMKt.isBlank, parameter $receiver
at kotlin.text.StringsKt__StringsJVMKt.isBlank(StringsJVM.kt)
at com.systemkern.Parent.<init>(DataClassInheritance.kt:24)
at com.systemkern.Child.<init>(DataClassInheritance.kt:30)
at com.systemkern.DataClassInheritanceKt.main(DataClassInheritance.kt:17)
感谢您的宝贵时间
祝一切顺利
在 name.isNotBlank()
,您应该会收到这样的 lint 警告:
Accessing non-final property name in constructor
您正在构建 Parent
时访问 name
属性,但是 name
在 Child
中被覆盖。此覆盖意味着在内部,Parent
和 Child
类 都有 name
的私有字段,并且由于 Parent
构造函数是当Child
已创建,Child
的 name
尚未初始化,但 Parent
中的签入将访问 Child
的覆盖属性 进行检查时。
这听起来可能很复杂,以下是您的示例(已简化)的反编译字节码的相关部分:
public abstract class Parent {
@NotNull
private final String name;
@NotNull
public String getName() {
return this.name;
}
public Parent(@NotNull String name) {
super();
this.name = name;
if(!StringsKt.isBlank(this.getName())) { // uses getName, which is overridden in
// Child, so Child's field is returned
throw new IllegalArgumentException("Firstname must not be blank");
}
}
}
public final class Child extends Parent {
@NotNull
private final String name;
@NotNull
@Override
public String getName() {
return this.name;
}
public Child(@NotNull String name) {
super(name); // calls super constructor
this.name = name; // sets own name field
}
}
了解 kotlin 初始化程序执行顺序的一些代码[1]
open class Parent {
private val a = println("Parent.a")
constructor(arg: Unit=println("Parent primary constructor default argument")) {
println("Parent primary constructor")
}
init {
println("Parent.init")
}
private val b = println("Parent.b")
}
class Child : Parent {
val a = println("Child.a")
init {
println("Child.init 1")
}
constructor(arg: Unit=println("Child primary constructor default argument")) : super() {
println("Child primary constructor")
}
val b = println("Child.b")
constructor(arg: Int, arg2:Unit= println("Child secondary constructor default argument")): this() {
println("Child secondary constructor")
}
init {
println("Child.init 2")
}
}
Child(1)
的输出
Child secondary constructor default argument
Child primary constructor default argument
Parent primary constructor default argument
Parent.a
Parent.init
Parent.b
Parent primary constructor
Child.a
Child.init 1
Child.b
Child.init 2
Child primary constructor
Child secondary constructor
本质上,Parent.init 在构造实际 object 之前被调用,因此抛出异常。
参考:
[1] https://medium.com/keepsafe-engineering/an-in-depth-look-at-kotlins-initializers-a0420fcbf546
早上好 Kotlin 大师们。
我有一个继承结构,其中抽象超类实现了一些共享数据检查。编译器没有抱怨,但是在执行时 JVM 抛出 IllegalArgumentException
代码
fun main(args: Array<String>) {
val foo = Child("NOT_BLANK")
}
abstract class Parent(
open val name: String = "NOT_BLANK"
) {
init {
require(name.isNotBlank()) { "Firstname must not be blank" }
}
}
data class Child(
override val name: String = "NOT_BLANK"
) : Parent(
name = name
)
异常如下所示
Exception in thread "main" java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.text.StringsKt__StringsJVMKt.isBlank, parameter $receiver
at kotlin.text.StringsKt__StringsJVMKt.isBlank(StringsJVM.kt)
at com.systemkern.Parent.<init>(DataClassInheritance.kt:24)
at com.systemkern.Child.<init>(DataClassInheritance.kt:30)
at com.systemkern.DataClassInheritanceKt.main(DataClassInheritance.kt:17)
感谢您的宝贵时间
祝一切顺利
在 name.isNotBlank()
,您应该会收到这样的 lint 警告:
Accessing non-final property name in constructor
您正在构建 Parent
时访问 name
属性,但是 name
在 Child
中被覆盖。此覆盖意味着在内部,Parent
和 Child
类 都有 name
的私有字段,并且由于 Parent
构造函数是当Child
已创建,Child
的 name
尚未初始化,但 Parent
中的签入将访问 Child
的覆盖属性 进行检查时。
这听起来可能很复杂,以下是您的示例(已简化)的反编译字节码的相关部分:
public abstract class Parent {
@NotNull
private final String name;
@NotNull
public String getName() {
return this.name;
}
public Parent(@NotNull String name) {
super();
this.name = name;
if(!StringsKt.isBlank(this.getName())) { // uses getName, which is overridden in
// Child, so Child's field is returned
throw new IllegalArgumentException("Firstname must not be blank");
}
}
}
public final class Child extends Parent {
@NotNull
private final String name;
@NotNull
@Override
public String getName() {
return this.name;
}
public Child(@NotNull String name) {
super(name); // calls super constructor
this.name = name; // sets own name field
}
}
了解 kotlin 初始化程序执行顺序的一些代码[1]
open class Parent {
private val a = println("Parent.a")
constructor(arg: Unit=println("Parent primary constructor default argument")) {
println("Parent primary constructor")
}
init {
println("Parent.init")
}
private val b = println("Parent.b")
}
class Child : Parent {
val a = println("Child.a")
init {
println("Child.init 1")
}
constructor(arg: Unit=println("Child primary constructor default argument")) : super() {
println("Child primary constructor")
}
val b = println("Child.b")
constructor(arg: Int, arg2:Unit= println("Child secondary constructor default argument")): this() {
println("Child secondary constructor")
}
init {
println("Child.init 2")
}
}
Child(1)
的输出Child secondary constructor default argument
Child primary constructor default argument
Parent primary constructor default argument
Parent.a
Parent.init
Parent.b
Parent primary constructor
Child.a
Child.init 1
Child.b
Child.init 2
Child primary constructor
Child secondary constructor
本质上,Parent.init 在构造实际 object 之前被调用,因此抛出异常。
参考: [1] https://medium.com/keepsafe-engineering/an-in-depth-look-at-kotlins-initializers-a0420fcbf546