深入kotlin - 嵌套类和内部类
Posted 颐和园
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入kotlin - 嵌套类和内部类相关的知识,希望对你有一定的参考价值。
嵌套类和内部类
嵌套类
kotlin 中,嵌套类和内部类是两种不同的类。所谓嵌套类是指定义在类体内的类。
class OuterClass
private val name: String = "Anna"
class NestedClass
fun nestedMethod() = "Attila"
fun main(args: Array<String>)
println(OuterClass.NestedClass().nestedMethod()) // 打印: Attila
跟 java 的内部类差不多。 kotlin 嵌套类对应于 java 的静态内部类,因此在嵌套类中无法访问外部类的成员,但可以访问位于同一外部类中的其它嵌套类(因为它们同属于 static 的)。
内部类
内部类不同,需要使用 inner 关键字:
class OuterClass
private var name: String = "Dorin"
inner class InnerClass
fun innerMethod() = this@OuterClass.name
fun main(args: Array<String>)
println(OuterClass().InnerClass().innerMethod())
和嵌套类不同,内部类可以通过 this@ 引用外部类的成员(当内部类被创建时,会自动持有一个外部类实例的引用)。注意,同时内部类不允许直接通过外部类类名访问内部类,而是必须通过外部类的实例进行访问。
显然,kotlin 嵌套类相当于 java 的静态内部类(有static ),而 kotlin 内部类则对应于 java 的非静态内部类(无 static)。
当外部类和内部类的属性或方法出现命名冲突时,访问方法:
- 访问外部类成员:this@外部类名.属性名
- 访问内部类成员:this@内部类名.属性名
- 访问局部变量:局部变量名
局部嵌套类
在方法内部定义的类,仅作用于方法内部,外部无法访问。
fun getName():String
class LocalNestedClass
val name: String = "Agaston"
var localNestedClass = LocalNestedClass()
return localNestedClass.name
对象表达式(匿名内部类)
在 java 中,存在匿名内部类的概念。kotlin 中,用对象表达式
取代了匿名内部类。这是因为 java 匿名内部类存在一个巨大缺陷:
Java 运行时将匿名内部类当作是它所继承或实现的父类或接口来使用。因此,如果你在匿名内部类中增加了父类/父接口之外的额外方法,java 运行时无论如何都是无法正常使用的。
而 kotlin 表达式就是为了解决这个缺陷而出现的:
interface MyInterface
fun print(i:Int)
abstract class MyAbstractClass
abstract val age: Int
abstract fun printMyAbstractClass()
fun main(args:Array<String>)
var myObject = object: MyInterface
override fun print(i: Int)
println("i = $i")
myObject.print(200) // 打印: i = 200
对象表达式用 object : 开头,后面是要实现/继承的父类/父接口。如果没有父类/父接口,则只需要 object 即可:
var myobject2 = object
init
println("init called")
var name = "Stephen"
fun method() = "method()"
println(myobject2.method()) // 打印:init called 和 Stephen
如果它需要实现多个接口或父类,用逗号分隔,注意 MyAbstractClass() 的写法,这是实例化了一个抽象类,而非继承:
var myobject = object: MyInterface, MyAbstractClass()
override fun print(i: Int)
println("i = $i")
override val age: Int
get() = 30
override fun printMyAbstractClass()
println("printMyAbstractClass")
这就突破了 java 匿名内部类只能继承一个父类/父接口的限制,因为你可以无限制地增加新的接口。
对象表达式作用域
注意如下代码:
class MyClass
private var myObject = object // 1
fun output()
println("output invoked")
fun test()
println(myObject.javaClass)
myObject.output() // 2
fun main(args:Array<String>)
var myClass = MyClass()
myClass.test() // 打印: output invoked
- 这里对象表达式必须用 private 修饰,否则无法被 test 方法调用。因为 kotlin 规定,匿名对象只能作为局部变量使用(在方法内使用),或者作为 private 成员变量,其类型才能被 kotlin 正常识别。如果匿名对象被定义 public 成员变量,或者被当作某个 public 方法的返回类型,那么它会被 kotlin 识别为其父类型/父接口,如果没有声明父类型/父接口,那么只能识别为 Any(所有自定义的成员变量和方法都不可用)。
- 因为 myObject 被声明为了private 成员变量,因此在 test 方法中可以正确识别出其类型,因而可以调用 myObject.output。如果打印 myObject.javaClass,可以看到 myObject 的真实类型为:MyClass$myObject$1。如果你将 myObject 的可见性修改为 public/internal,那么 myObject.output() 一句将报错:Unresolved reference: output。
外部变量可见
Java 的匿名内部类无法访问外部局部变量(除非是 final 修饰)。而kotlin 对象表达式则突破了这一限制:
fun main(args:Array<String>)
var i=24
var myObject = object
fun test()
i++ // 1
- 在对象表达式内部访问了外部局部变量 i。注意与 java 不同, i 无需声明为 final,且 i 对于 myObject 来说是可变的,可以修改其值。
对象表达式 vs lambda 表达式
在 kotlin 中,如果对象表达式实现的是一个函数式接口(该接口只有一个抽象方法),我们可以将它转换成 lambda 表达式。比如如下对象表达式:
button.addActionListener(object: ActionListener
override fun actionPerformed(e: ActionEvent?)
println("Button clicked")
)
根据 IDE 提示,我们可以将对象表达式转变为 lamda 表达式:
button.addActionListener(ActionListener println("Button clicked"))
其中,ActionListener 这个类型是可以推断的,因此连 ActionListener 都可以省略。
以上是关于深入kotlin - 嵌套类和内部类的主要内容,如果未能解决你的问题,请参考以下文章