深入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

  1. 这里对象表达式必须用 private 修饰,否则无法被 test 方法调用。因为 kotlin 规定,匿名对象只能作为局部变量使用(在方法内使用),或者作为 private 成员变量,其类型才能被 kotlin 正常识别。如果匿名对象被定义 public 成员变量,或者被当作某个 public 方法的返回类型,那么它会被 kotlin 识别为其父类型/父接口,如果没有声明父类型/父接口,那么只能识别为 Any(所有自定义的成员变量和方法都不可用)。
  2. 因为 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
    
  

  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 - 嵌套类和内部类的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin基础(十三) 嵌套类内部类和匿名内部类

Kotlin基础(十三) 嵌套类内部类和匿名内部类

对比Java学Kotlin嵌套类和内部类

对比Java学Kotlin嵌套类和内部类

对比Java学Kotlin嵌套类和内部类

kotlin学习总结——object关键字数据类密封类嵌套类和内部类