在 Kotlin 中,内部类的目的是什么?

22

我刚开始学习Kotlin。我正在学习Kotlin中的嵌套类和内部类概念。以下是一个嵌套类的示例。

 fun main(args: Array<String>) {
   val demo = Outer.Nested().foo() // calling nested class method
   print(demo)
}
class Outer {
   class Nested {
      fun foo() = "Hello world"
   }
}

输出结果如下:

Hello world

下面是内部类的示例:inner class.

fun main(args: Array<String>) {
   val demo = Outer().Nested().foo() // calling inner class method
   print(demo)
}
class Outer {
   private val welcomeMessage: String = "Hello world"
   inner class Nested {
      fun foo() = welcomeMessage
   }
}

输出结果为:

Hello world

我的问题是嵌套类何时可以执行与内部类相同的操作。 内部类的目的是什么?为什么我们在实际应用中需要内部类?


11
嵌套类不能像内部类一样执行相同的操作。内部类隐式地引用其外部类实例,而嵌套类则没有。删除inner关键字后,将导致编译错误。 - JB Nizet
8
inner”就好比在Java中省略“static”。 - Minn
@JBNizet 先生,附上简单的示例会更有帮助。 - Gaju Kollur
8
你自己发布了这个例子,请看一下你内部类的代码:它访问了外部类的welcomeMessage属性。一个嵌套类无法做到这一点。 - JB Nizet
2个回答

24

嵌套类本身是一个相当直观的概念,即一个概念在逻辑上依赖于另一个更广泛的概念。当您发现自己正在描述一个更广泛概念的构建块,并且不希望它单独出现在您的业务逻辑中时,您会想要声明一个新的嵌套类。因此,这样一个类的实例是通过其所嵌套的类的定义来访问的。

以以下结构为例:

class Parrot(val name: String): Animal {

    //vals and vars useful for a parrot

    class HealthyConditions {
        val featherColour: String // Does not really apply to Dogs, Cats, Fish, etc.
        val cageSize: Size // Does not really apply to Dogs, Cats, Fish, etc.
        val weightInGrammes: Int
        val dailySleepInHours: Int

        fun isUnderweight(currentWeightInGrammes: Int): Boolean {
            return currentWeightInGrammes < weightInGrammes
        }
    }
}

现在,对于任何描述的动物而言,健康居住条件的概念已经普及并且非常重要,但是针对不同种类的动物,需要考虑多个完全不同的因素。试图为所有这些动物一次性准备一个通用的HealthyConditions<Animal>类很可能会导致难以维护和阅读的代码,充满了ifs 和 elses。因此,对于每种Animal,开发人员定义简明、整洁的嵌套类可能更有意义。将来,它们将通过Parrot.HealthyConditions()Cat.HealthyConditions()等进行访问,而不是通过HealthyConditions.getForSpecies(animal)


内部类是基于嵌套类的Kotlin概念。当某些细节非常特定于给定概念时,它们被描述为嵌套类,但如果这些细节根据更广泛的概念实例而变化会怎么样呢?那么仅通过类定义访问它可能不足以正确操作。因此,内部类是通过调用其所属类的实例来访问的。

让我们回到我们的Parrot吧。

class Parrot(val name: String, var age: Int): Animal {

    //vals and vars useful for a parrot

    inner class HealthyConditions {
        val featherColour: String // Does not really apply to Dogs, Cats, Fish, etc.
        val cageSize: Size // Does not really apply to Dogs, Cats, Fish, etc.
        val weightInGrammesByAge: Map<Int, Int>
        val dailySleepInHoursByAge: Int<Int, Int>
        
        /** variable 'age' from the Parrot class can be accessed only because the HealthyConditions class is marked inner!  */
        fun isUnderweight(currentWeightInGrammes: Int): Boolean {
            return weightInGrammesByAge[age] > currentWeightInGrammes
        }
    }
}
健康的鹦鹉应该有多重在其一生中会发生变化。孵化时几百克的重量是可以接受的,但成年个体低于2公斤可能需要注意。因此,是否X克的鹦鹉体重不足不能轻易回答,但如果我们问Bob鹦鹉是否需要增加一些体重,我们可以利用我们了解的关于Bob的知识来确定答案。为了做到这一点,将通过Parrot("Bob", 5).HealthyConditions()访问HealthyConditions类。 现在,你可能仍然想知道访问父类属性对内部类是否真的有用。毕竟,在讨论嵌套类时,我们决定每个动物都应该有自己的健康状况实现。对于Dog类,狗的品种与年龄一样重要,以确定其正确的体重。对于其他物种,性别也将非常重要。这意味着,如果内部类无法直接访问其父级属性,则Healthcheck接口的checkIfUnderweight()函数将不得不接受大量不同,可能是可空的变量,以便适用于系统中所有不同类型的动物。 我花了很多时间谈论概念,没有太多的代码示例,但据我所知,您不是在实施方面遇到困难,而是在首次实施代码的理由上。此外,请原谅我所想出的“动物类”示例可能看起来很简单-事实上,这是我想出的第一个。 无论如何,我希望这篇简短的阅读材料能帮助您更好地了解概念及其用例。安全编码,愿错误远离您!

2
非常好的写作。喜欢“鹦鹉”和生活条件的例子,很有创意! - Yogesh Umesh Vaity
4
@YogeshUmeshVaity 感谢您留下友善的话语!很高兴我的回答仍然相关且有用。我花了一些时间重新阅读这篇两年前的文章,并纠正了几个语法错误。 - ryfterek
嗨,我是一个OOP的新手,在阅读了您的答案后,我有一个新问题。为什么我们不能只声明一个名为ParrotHealthyConditions的类,而不将它嵌套在Parrot类中?应该像这样:class Parrot { val healthCondition:ParrotHealthyCondition? = null} - Archsx
@Archsx 你好!当然,我们可以放弃将类嵌套在彼此中的能力,并按照您的建议定义类。但是,正如我在最初的回复中所指出的那样,如果我们看到两个类之间存在明显的层次结构,使一个类成为另一个类的优越构造,则可以嵌套类。类嵌套的一个非常常见的实际例子是Builder模式,其中我们将一个类“Builder”嵌套在另一个类中,以逻辑上表示该Builder仅是其嵌套类的实用程序。 - ryfterek

9

Kotlin官方文档相当易于理解:

标记为inner的嵌套类可以访问其外部类的成员。Inner类会携带一个指向外部类对象的引用:

也许您还不理解这意味着什么。

具体而言,在您的代码示例中,只有在将Nested标记为inner时,才能从Nested中访问定义在Outer中的welcomeMessage。尝试删除inner,您的代码将无法编译。


3
我意识到这并没有解释如何使用这个功能。但是,鉴于提问者错误地认为“嵌套类可以执行与内部类相同的操作”,我觉得这应该足以作为一个答案。什么时候使用这个功能是一个不同的问题。我可以列出很多其他示例,而不是只有使用不同语言特性的“Hello world”。这不应该成为它们存在的原因。 - Steven Jeuris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接