不可为空的抽象变量能否在 KOTLIN 中引发空指针异常?

Posted

技术标签:

【中文标题】不可为空的抽象变量能否在 KOTLIN 中引发空指针异常?【英文标题】:Can abstract variables that aren't nullable throw Null Pointer Exceptions in KOTLIN? 【发布时间】:2020-08-26 12:54:30 【问题描述】:

免责声明: 这是用 Kotlin 编码的,它不是一些简单的初学者级别的代码。请不要标记它,因为您认为我是一个不知道空指针异常是什么的菜鸟。它比这复杂得多。我之前上传了这个问题,并被建议查找什么是空指针异常,然后将我的问题标记为与空指针异常是什么的问题重复,然后立即关闭。如果您来自 Java,Kotlin 甚至不允许您在不分配对象的情况下创建对象(没有指定它们可以为空并提供空安全检查每次使用它们时)。我的问题没那么简单,请先看一下。我的问题在我读完整本书之前就已经结束了,所以至少在你认为我只是愚蠢之前做到这一点。

信息:

这是一个 Swing 应用程序

我在 Kotlin 中编写代码

我正在尝试为 Swing 的边界创建一个类系统(搜索“干净代码边界”以获取更多信息)。本质上,我有两个类都继承自我创建的 Item 类。

abstract class Item(var text: String = "", var icon: Icon? = null, var items: ArrayList<Item> = ArrayList()) 

    abstract var component: JComponent

    init 
       applyProperties()
        applyItems()
    

    fun applyProperties() 
        when (component) 
            is JMenu -> 
                (component as JMenu).text = text
                (component as JMenu).icon = icon
            
            is JMenuItem -> 
                (component as JMenuItem).text = text
                (component as JMenuItem).icon = icon
            
        
    
    fun applyItems() 
        for (item in items) 
            apply(item)
        
    
    private fun apply(item: Item) 
        component.add(item.component)
    
    companion object 
        fun ArrayList<Item>.addItems(vararg items: Item) 
            this.addAll(items)
        
    

另外两个类除了覆盖组件变量什么都不做,然后applyProperties()中的switch语句可以使用组件变量类型来知道要设置哪些变量。就像现在一样,两个类需要设置的变量是相同的,但我使用的是多态性,所以以后可以添加更多具有不同功能的类。这是其中之一,只是为了向您展示它们几乎不可能是原因。

class Menu(text: String = "", icon: Icon? = null, items: ArrayList<Item> = ArrayList()): Item(text, icon, items) 
   override var component: JComponent = JMenu()

这是我运行的引发空指针异常的代码。

var menu: JMenu = MenuBuilder().set  menu ->
    menu.text = "Save"
    menu.items.addItems(

        MenuItemBuilder().set  menuItem ->
            menuItem.text = "Save"
        .build(),

        MenuItemBuilder().set  menuItem ->
            menuItem.text = "Save As"
        .build()
    )
.build()

apply() 上调用的 applyItems() 方法调用 add(),它接收一个 JComponent 作为输入,然后抛出异常。但是,我输入的组件总是在运行 add() 之前设置。它是一个抽象变量,总是在创建类 Item 的实例时设置。此外,如果我忘记设置它,它甚至不应该让我运行。我在 Kotlin 中对此进行编码,它具有极高的 null 安全性。除非我自己抛出一个空指针异常,否则我不应该得到空指针异常。它甚至不是一个可以为空的变量,那为什么 add() 会抛出异常呢?

如果您想检查任何其他用于执行此操作的类,以查看它们是否可能导致空指针异常,我将它们全部放在下面。

class MenuBuilder 
    var text: String = ""
    var icon: Icon? = null
    var items: ArrayList<Item> = ArrayList()

    fun set(builderFunction: (MenuBuilder) -> Unit): MenuBuilder 
        builderFunction(this)
        return this
    

    fun build() = Menu(text, icon, items)


class MenuItemBuilder 
    var text: String = ""
    var icon: Icon? = null
    var items: ArrayList<Item> = ArrayList()

    fun set(builderFunction: (MenuItemBuilder) -> Unit): MenuItemBuilder 
        builderFunction(this)
        return this
    

    fun build() = MenuItem(text, icon, items)

这些是我用来使创建菜单和菜单项更容易的构建器模式,但是使用键入 MenuItem(//parameters) 应该具有相同的效果(并产生相同的错误)。

【问题讨论】:

问题看起来是合法的,但是你可以将它作为参数而不是抽象的,并在创建子类时将参数传递给超类构造函数。这可能是因为先调用了super,然后在子类中初始化了变量。 【参考方案1】:

possible ways to get NullPointerException in Kotlin 之一是:

超类构造函数调用开放成员,其在派生类中的实现使用未初始化状态

这正是您的示例中发生的情况。在init 块中,它是基类Item 构造函数的一部分,您最终访问了尚未初始化的component 属性,因为派生的Menu 类构造函数已经调用了基类构造函数并且没有已着手初始化其自身的属性。具体情况在https://kotlinlang.org/docs/inheritance.html#derived-class-initialization-order中有详细描述。

如果在基类构造过程中需要component 值,您可以将其与其他参数一起传递给基类构造函数。

【讨论】:

确实有道理。我没有考虑它,因为我只考虑了表面层的代码,并没有考虑编译器运行所有内容的顺序。我更改了它,只是在项目构造函数中添加了一个额外的参数,一切顺利! 请注意,如果您尝试从构造函数(或init 块或初始化程序)访问非最终属性或方法,IDEA 会显示警告 - 以防止此类问题。这也是为什么 Java 和 Kotlin 都不允许构造函数在调用超类构造函数(或另一个实例构造函数)之前执行任何语句的原因。事实上,这不仅仅是空指针;在所有构造函数/初始化程序都运行之前,对象处于不一致的状态,因此也存在更微妙的错误风险。 嗯,我是在 IntelliJ 上制作的,它没有警告我,所以我不知道那里发生了什么,但是是的,我一定会在未来。

以上是关于不可为空的抽象变量能否在 KOTLIN 中引发空指针异常?的主要内容,如果未能解决你的问题,请参考以下文章

Spring(kotlin)控制器接收数据类的不可为空的参数为空

声明为不可为空的 Kotlin 属性即使具有初始化值也可以为空

Kotlin学习快速入门——空安全

使用 Laravel ORM 抽象保存新的不可为空的关系,而不会违反数据库完整性约束

未处理的拒绝(错误):期望不可为空的变量的值:类型的时间:时间!在变量值中

C# 8 中的不可为空的引用类型在运行时可以为空吗?