超类中的Require';init块引发IllegalArgumentException[Kotlin]

超类中的Require';init块引发IllegalArgumentException[Kotlin],kotlin,kotlin-null-safety,Kotlin,Kotlin Null Safety,早上好,科特林大师 我有一个继承结构,其中抽象超类实现了一些共享数据检查。编译器不会抱怨,但在执行时JVM会抛出一个IllegalArgumentException 代码 fun main(args:Array){ val foo=子对象(“非空白”) } 抽象类父类( 打开val名称:String=“非空” ) { 初始化{ require(name.isNotBlank()){“Firstname不能为空”} } } 数据类子类( 覆盖val名称:String=“不为空” ):家长( nam

早上好,科特林大师

我有一个继承结构,其中抽象超类实现了一些共享数据检查。编译器不会抱怨,但在执行时JVM会抛出一个IllegalArgumentException

代码
fun main(args:Array){
val foo=子对象(“非空白”)
}
抽象类父类(
打开val名称:String=“非空”
) {
初始化{
require(name.isNotBlank()){“Firstname不能为空”}
}
}
数据类子类(
覆盖val名称:String=“不为空”
):家长(
name=name
)
例外情况如下所示
线程“main”java.lang.IllegalArgumentException中的异常:指定为非null的参数为null:method kotlin.text.StringsKt\uu StringsJVMKt.isBlank,参数$receiver
在kotlin.text.StringsKt__StringsJVMKt.isBlank处(StringsJVM.kt)
在com.systemkern.Parent.(DataClassInheritance.kt:24)
在com.systemkern.Child.(DataClassInheritance.kt:30)
位于com.systemkern.DataClassInheritanceKt.main(DataClassInheritance.kt:17)
谢谢你的时间

name.isNotBlank()
上,最好的,您应该会得到如下的lint警告:

访问构造函数中的非最终属性名

在构建父级时,您正在访问
名称
属性,但是,
名称
在子级中被覆盖。此重写意味着在内部,
父类
子类
都有
名称
的私有字段,并且由于创建
子类
时首先调用
父类
构造函数,
子类
名称
不会被初始化,但是,签入父级
将在执行检查时访问子级
对属性的重写

这听起来可能很复杂,下面是您示例中反编译字节码的相关部分(简化):

name.isNotBlank()
上,您应该会收到如下的lint警告:

访问构造函数中的非最终属性名

在构建父级
时,您正在访问
名称
属性,但是,
名称
在子级
中被覆盖。此重写意味着在内部,
父类
子类
都有
名称
的私有字段,并且由于创建
子类
时首先调用
父类
构造函数,
子类
名称
不会被初始化,但是,签入父级
将在执行检查时访问子级
对属性的重写

这听起来可能很复杂,下面是您示例中反编译字节码的相关部分(简化):


理解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")
    }
   }
子对象的输出(1)

本质上,在构造实际对象之前调用Parent.init,因此引发异常

参考:
[1]

了解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")
    }
   }
子对象的输出(1)

本质上,在构造实际对象之前调用Parent.init,因此引发异常

参考:
[1]

非常感谢您抽出时间回答我的问题:)。。。有没有一种方法可以避免这个问题(除了创建一个单独的检查方法)取决于您实际如何使用这些类。如果您需要通过
Parent
访问
name
属性,但还需要在
Child
的构造函数中为数据类功能访问
name
,那么您就有点麻烦了。也许您可以将
parent
中的属性重命名为其他属性,这样您就不会重写它,而是在每个子类中复制它,并验证
parent
中的属性副本。非常感谢您花时间回答我的问题:)。。。有没有一种方法可以避免这个问题(除了创建一个单独的检查方法)取决于您实际如何使用这些类。如果您需要通过
Parent
访问
name
属性,但还需要在
Child
的构造函数中为数据类功能访问
name
,那么您就有点麻烦了。也许您可以将
parent
中的属性重命名为其他属性,这样您就不会覆盖它,而是在每个子类中复制它,并验证它在
parent
中的副本。
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
    }
}
  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 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