深入kotlin- 伴生对象和扩展

Posted 颐和园

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入kotlin- 伴生对象和扩展相关的知识,希望对你有一定的参考价值。

伴生对象

在 kotlin 中,类没有 static 方法的概念,这与 java 不同。kotlin 用 package 级别的函数来取代静态方法(在字节码层级,这就是静态方法)。

所谓伴生对象其实就是位于 class 中的 object,使用 companion object 关键字声明:

class A 
	comopanion object: MyObject 
		var a:Int = 100
		fun method() 
	

你可以通过 类名.对象名.方法名调用:

A.MyObject.method()

甚至你还可以通过 类名.方法名调用:

A.method()
println(A.a)

这就类似了静态成员的调用方法。在 kotlin 中,我们可以利用伴生对象实现类的静态成员。同时,kotlin 还规定了同一个类中只允许定义一个伴生对象,从而避免了多个伴生对象的命名冲突。因为类中只有一个伴生对象,所以伴生对象的名字是不必要的,完全可以省略:

class A 
	comopanion object 
		var a:Int = 100
		fun method() 
	

同时 kotlin 提供一个固定的对象名 Companion。通过 javap -c 反编译可以看到,伴生对象本质上是一个静态属性,它的类型是一个内部类,而它的成员则是实例成员。但是,你可以通过 @JvmStatic 注解将它们变成静态成员:

class A 
	comopanion object 
		var a:Int = 100
		@JvmStatic
    fun method() 
	

这样,method() 方法中 jvm 层面上来说是 static 的了,但是这并对使用层面上没有任何影响,调用方式不变。

可见性

Kotlin 中有 4 种访问修饰符。

private

  • 如果是顶层声明(包级别),则只能在该文件内可用。
  • 如果是类成员,只能在当前类可用

protected

  • 不能用于顶层声明(包括函数和类)。
  • 如果是类成员,当前类和子类可用。

internal

  • 只能在同一模块(project)下可用。
  • 如果是类成员,在同一模块下可用

public

  • 如果是顶层声明(包级别)不添加任何访问修饰符,就是 public。
  • 如果是类成员,任何地方可用。

对于局部变量,没有可见性的概念,不能使用访问修饰符。

扩展

等同于 swift 中的 extension,但形式上不同(更加简化)。

class A 
fun A.add(a: Int, b: Int) = a+b
...
let a = A()
let num = a.add(1,2)

以上代码为类 A 动态地扩展了新方法 add。

不支持多态

open class A
class B:A()
fun A.method()="A"
fun B.method()="B"
fun method(obj: A)
  println(obj.method())

fun main(args: Array<String>)
  method(A()) // 打印 A
  method(B()) // 打印 A -> 扩展不支持多态

没错 method(B()) 一句打印的结果证明了,扩展不支持多态,因为扩展的方法是静态的,不是动态的,因此尽管传入了一个 B 对象,但在 obj.method() 方法在编译时就已经决定了它会指向 A 类的 method 扩展方法,而不是 B 类的 method 方法。也就是说,kotlin 编译器在编译时将 obj.method() 一句自动编译成了 A 的扩展方法。而字节码一旦构建就无法改变。所以无论运行时你传入 A 对象还是 B 对象,这个方法只会执行 A 对象的 method 方法。

不支持重写,但可以重载

扩展不支持对原有方法对覆盖。但可以对原有方法进行重载(参数列表不同)。

可空扩展

扩展可以扩展可空类型。通过这种方式,可以简化该类型的判空操作,从而使调用者减少不必要的判空操作。

fun Any?.toString():String
	if(null==this)
		return "null"
	
	return toString()

注意,这不是重写,不会覆盖原有类的 toString 方法。

扩展属性

val A.name:String
	get()="hello"

扩展伴生对象

class A
	companion object Obj

fun A.Obj.method()  println("hello") 
...

A.method() // 打印 hello, 注意,伴生对象的目的就是作为 java static 成员的替代物,因此可以直接通过类名调用

分发接收者/扩展接收者

除了在顶层进行扩展外,也可以在类的内部定义,则这个类被 kotlin 叫做分发接收者(dispatch receiver)。被扩展的类叫做扩展接收者(extension receiver)。当两个接收者出现命名冲突时,后者优先级更高。

class A 
	fun method()
		println("A.method")
	

class B 
	fun method2()
	fun A.hello() // 这是对 A 的扩展,所以 A 中的所有成员对此方法可见,同时它是定义在 B 中的,所以 B 的所有成员可见
		method() // 调用 A 的方法
		method2() // 调用 B 的方法
	
  fun world(b: B)
    b.hello()
  
  fun A.output():String 
    return toString()
  

注意,B 中有一个方法对 A 进行了扩展,所以该扩展可同时访问 A 和 B 的成员。同时,该扩展方法在 B 中可见,在 B 以外不可见。

此外,在 A 的扩展方法 output 中出现了一个命名冲突 toString()。因为 A 和 B 都有 toString 方法。这种情况下,优先调用扩展接收者(即 A)的 toString 方法。如果你想调用 B (分发接收者)的 toString 方法,则可以:

return this@B.toString()

以上是关于深入kotlin- 伴生对象和扩展的主要内容,如果未能解决你的问题,请参考以下文章

深入kotlin- 伴生对象和扩展

Kotlin学习——扩展(扩展函数和属性以及伴生对象)

Kotlin基础 4.companion object(伴生对象),更新中

kotlin伴生对象(java静态成员)

在 kotlin 中命名伴生对象有啥意义

R8 去除反射所需的 Kotlin 伴生对象