内部类
内部类类似于嵌套类,但内部类的对象会保持对外部类的引用。
一个 inner
类具有与外部类的隐式链接。在下面的示例中,Hotel
类类似于 嵌套类 中的 Airport
,但它使用了 inner
类。请注意,reception
是 Hotel
的一部分,但 callReception()
是嵌套类 Room
的成员函数,它在没有限定符的情况下访问 reception
:
// InnerClasses/Hotel.kt
package innerclasses
import atomictest.eq
class Hotel(private val reception: String) {
open inner class Room(val id: Int = 0) {
// 从外部类中使用 'reception':
fun callReception() =
"Room $id Calling $reception"
}
private inner class Closet : Room()
fun closet(): Room = Closet()
}
fun main() {
val nycHotel = Hotel("311")
// 需要外部对象来
// 创建内部类的实例:
val room = nycHotel.Room(319)
room.callReception() eq
"Room 319 Calling 311"
val sfHotel = Hotel("0")
val closet = sfHotel.closet()
closet.callReception() eq "Room 0 Calling 0"
}
由于 Closet
继承了内部类 Room
,Closet
也必须是一个 inner
类。嵌套类不能继承自 inner
类。
Closet
是 private
的,因此它只在 Hotel
的作用域内可见。
inner
对象会保持对其关联的外部对象的引用。因此,在创建 inner
对象时,必须先有外部对象。您不能在没有 Hotel
对象的情况下创建 Room
对象,就像您在 nycHotel.Room()
中看到的那样。
不允许使用 inner
data
类。
限定的 this
类的一个好处是 this
引用。在访问属性或成员函数时,您无需显式地说“当前对象”。
对于简单的类,this
的含义是明显的,但对于 inner
类,this
可能指的是 inner
对象或外部对象。为了解决这个问题,Kotlin 提供了 限定的 this
语法:this
后跟 @
和目标类的名称。
考虑三个级别的类:一个包含 inner
类 Seed
的外部类 Fruit
,Seed
本身包含一个 inner
类 DNA
:
// InnerClasses/QualifiedThis.kt
package innerclasses
import atomictest.eq
import typechecking.name
class Fruit { // 隐式标签 @Fruit
fun changeColor(color: String) =
"Fruit $color"
fun absorbWater(amount: Int) {}
inner class Seed { // 隐式标签 @Seed
fun changeColor(color: String) =
"Seed $color"
fun germinate() {}
fun whichThis() {
// 默认为当前类:
this.name eq "Seed"
// 为了明确,可以多余地
// 限定默认的 this:
this@Seed.name eq "Seed"
// 必须明确访问 Fruit:
this@Fruit.name eq "Fruit"
// 无法访问进一步内部的类:
// this@DNA.name
}
inner class DNA { // 隐式标签 @DNA
fun changeColor(color: String) {
// changeColor(color) // 递归
this@Seed.changeColor(color)
this@Fruit.changeColor(color)
}
fun plant() {
// 调用外部类的函数
// 不需要限定:
germinate()
absorbWater(10)
}
// 扩展函数:
fun Int.grow() { // 隐式标签 @grow
// 默认为 Int.grow() 的接收者:
this.name eq "Int"
// 多余的限定:
this@grow.name eq "Int"
// 仍然可以访问所有内容:
this@DNA.name eq "DNA"
this@Seed.name eq "Seed"
this@Fruit.name eq "Fruit"
}
// 外部类的扩展函数:
fun Seed.plant() {}
fun Fruit.plant() {}
fun whichThis() {
// 默认为当前类:
this.name eq "DNA"
// 多余的限定:
this@DNA.name eq "DNA"
// 其他必须是明确的:
this@Seed.name eq "Seed"
this@Fruit.name eq "Fruit"
}
}
}
}
// 扩展函数:
fun Fruit.grow(amount: Int) {
absorbWater(amount)
// 调用 Fruit 的版本 changeColor():
changeColor("Red") eq "Fruit Red"
}
// 内部类的扩展函数:
fun Fruit.Seed.grow(n: Int) {
germinate()
// 调用 Seed 的版本 changeColor():
changeColor("Green") eq "Seed Green"
}
// 内部类的扩展函数:
fun Fruit.Seed.DNA.grow(n: Int) = n.grow()
fun main() {
val fruit = Fruit()
fruit.grow(4)
val seed = fruit.Seed()
seed.grow(9)
seed.whichThis()
val dna = seed.DNA()
dna.plant()
dna.grow(5)
dna.whichThis()
dna.changeColor("Purple")
}
Fruit
、Seed
和 DNA
都有名为 changeColor()
的函数,但这不是继承关系。由于它们具有相同的名称和签名,唯一区分它们的方法是使用限定的 this
,就像在 DNA
的 changeColor()
中所看到的那样。在 plant()
中,可以在没有名称冲突的情况下无需限定就可以调用两个外部类
中的函数。
尽管它是一个扩展函数,grow()
仍然可以访问外部类中的所有对象。grow()
可以在任何地方调用 Fruit.Seed.DNA
隐式接收者可用的地方,例如在 DNA
的扩展函数内部。
内部类继承
内部类可以从不同的外部类中继承另一个内部类。在这里,BigEgg
中的 Yolk
是从 Egg
中的 Yolk
派生而来的:
// InnerClasses/InnerClassInheritance.kt
package innerclasses
import atomictest.*
open class Egg {
private var yolk = Yolk()
open inner class Yolk {
init { trace("Egg.Yolk()") }
open fun f() { trace("Egg.Yolk.f()") }
}
init { trace("New Egg()") }
fun insertYolk(y: Yolk) { yolk = y }
fun g() { yolk.f() }
}
class BigEgg : Egg() {
inner class Yolk : Egg.Yolk() {
init { trace("BigEgg.Yolk()") }
override fun f() {
trace("BigEgg.Yolk.f()")
}
}
init { insertYolk(Yolk()) }
}
fun main() {
BigEgg().g()
trace eq """
Egg.Yolk()
New Egg()
Egg.Yolk()
BigEgg.Yolk()
BigEgg.Yolk.f()
"""
}
BigEgg.Yolk
明确地将 Egg.Yolk
作为其基类,并覆盖了它的 f()
成员函数。函数 insertYolk()
允许 BigEgg
将其自己的 Yolk
对象向上转型为 Egg
中的 yolk
引用,因此当 g()
调用 yolk.f()
时,使用的是被覆盖的 f()
版本。第二次调用 Egg.Yolk()
是 BigEgg.Yolk
构造函数的基类构造函数调用。您可以看到在调用 g()
时使用了被覆盖的 f()
版本。
作为对象构造的回顾,请研究 trace
输出,直到它变得有意义。
本地和匿名内部类
在成员函数内部定义的类称为本地内部类。这些也可以使用对象表达式匿名创建,或者使用 SAM 转换。在所有情况下,不使用 inner
关键字,但是它被暗示:
// InnerClasses/LocalInnerClasses.kt
package innerclasses
import atomictest.eq
fun interface Pet {
fun speak(): String
}
object CreatePet {
fun home() = " 家!"
fun dog(): Pet {
val say = "汪汪"
// 本地内部类:
class Dog : Pet {
override fun speak() = say + home()
}
return Dog()
}
fun cat(): Pet {
val emit = "喵喵"
// 匿名内部类:
return object: Pet {
override fun speak() = emit + home()
}
}
fun hamster(): Pet {
val squeak = "吱吱"
// SAM 转换:
return Pet { squeak + home() }
}
}
fun main() {
CreatePet.dog().speak() eq "汪汪 家!"
CreatePet.cat().speak() eq "喵喵 家!"
CreatePet.hamster().speak() eq "吱吱 家!"
}
本地内部类可以访问函数中的其他元素以及外部类对象中的元素,因此在 speak()
中可以使用 say
、emit
、squeak
和 home()
。
您可以通过使用对象表达式来识别匿名内部类,就像在 cat()
中所见。它返回一个从 Pet
继承的类的 object
,该类覆盖了 speak()
。匿名内部类更小、更直接,不会创建只在一个地方使用的命名类。更紧凑的是 SAM 转换,就像在 hamster()
中看到的那样。
由于内部类保持对外部类对象的引用,因此本地内部类可以访问封闭类的所有成员:
// InnerClasses/CounterFactory.kt
package innerclasses
import atomictest.*
fun interface Counter {
fun next(): Int
}
object CounterFactory {
private var count = 0
fun new(name: String): Counter {
// 本地内部类:
class Local : Counter {
init { trace("Local()") }
override fun next(): Int {
// 访问本地标识符:
trace("$name $count")
return count++
}
}
return Local()
}
fun new2(name: String): Counter {
// 匿名内部类的实例:
return object: Counter {
init { trace("Counter()") }
override fun next(): Int {
trace("$name $count")
return count++
}
}
}
fun new3(name: String): Counter {
trace("Counter()")
return Counter { // SAM 转换
trace("$name $count")
count++
}
}
}
fun main() {
fun test(counter: Counter) {
(0..3).forEach { counter.next() }
}
test(CounterFactory.new("Local"))
test(CounterFactory.new2("Anon"))
test(CounterFactory.new3("SAM"))
trace eq """
Local() Local 0 Local 1 Local 2 Local 3
Counter() Anon 4 Anon 5 Anon 6 Anon 7
Counter() SAM 8 SAM 9 SAM 10 SAM 11
"""
}
Counter
跟踪一个 count
并返回下一个 Int
值。new()
、new2()
和 new3()
分别创建 Counter
接口的不同实现。new()
返回一个具有命名内部类的实
例,new2()
返回一个匿名内部类的实例,而 new3()
使用 SAM 转换 创建一个匿名对象。所有生成的 Counter
对象都隐含地访问外部对象的元素,因此它们是内部类而不仅仅是嵌套类。从输出中可以看出,count
在所有 Counter
对象之间共享。
对于 init
子句,SAM 转换有限制,例如,它不支持 init
子句。
- -
在 Kotlin 中,文件可以包含多个顶层类和函数。因此,很少需要本地类,因此如果确实需要本地类,它们应该是基本且直观的。例如,可以创建一个简单的 data
类,该类仅在函数内部使用。如果本地类变得复杂,那么您可能应该将其从函数中分离出来,使其成为常规类。
练习和解答可在 www.AtomicKotlin.com 找到。