嵌套类和内部类
嵌套类
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 都可以省略。