对比Java学Kotlinobject 关键字

Posted 陈蒙_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对比Java学Kotlinobject 关键字相关的知识,希望对你有一定的参考价值。

Java:啥?Object?这个我熟呀,我的祖先类……

Kotlin:打住,此 Object 非彼 Object,我的 Object 是个关键字,而不是某个具体的类,你说的 Object 相当于我的 Any

Java:额(⊙o⊙)…为啥要有 object 关键字?

Kotlin:铁子,问到点子上了,当你想稍微修改某个现有的类,但是呢你又不想通过继承或组合的方式新建一个类,这是你可以用 object 修饰表达式来实现。

Kotlin:还有就是我没有 static 关键字,但是很多场景下又需要跟类相关而跟实例无关的功能,这是可以用 object 来完成。

Java:说的有点抽象

Kotlin:下面详细说下

两种用法

Kotlin 的 object 关键字有两种用法,一个是作为右值表达式的前缀,一个是作为类的前缀修饰符。

object 表达式

object 表达式一般用于对现有类进行稍微修改、因为是临时使用一次而不值得新建类完成的场景,我们称其为匿名对象。

Any 类型的匿名对象

先来直观感受下用法:

val o = object {
    val hello = "Hello"
    val world = "World"

    override fun toString(): String {
        return "$hello, $world"
    }
}

val r = object : Runnable {
    override fun run() {
        TODO("Not yet implemented")
    }
}

r.run()

这时 Kotlin 同学举手示意要发言:这个我也行呀,你看:

Object o = new Object() {
    String hello = "Hello";
    String world = "World";

    @Override
    public String toString() {
        return hello + ", " + world;
    }
};

// 还有这种匿名对象:
Runnable r = new Runnable() {
    @Override
    public void run() {
        
    }
};

r.run();

看起来确实挺像的,但是,在 Kotlin 中我们可以随意加其他方法,并能引用到这些方法:

val o = object {
    val hello = "Hello"
    val world = "World"

    override fun toString(): String {
        return "$hello, $world"
    }

    fun length(): Int {
        return toString().length
    }
}

val l = o.length() // 注意此行代码,在 kotlin 中可以正确运行

但是 Java 中这种调用是不可行的,会直接编译报错:

Object o = new Object() {

    String hello = "Hello";
    String world = "World";

    @Override
    public String toString() {
        return hello + ", " + world;
    }

    public int length() {
        return toString().length();
    }
};

int l = o.length(); // 编译报错:Cannot resolve method 'length' in 'Object'

具体类型的匿名对象

可以看出上述 object 表达式是祖先类 Any 的匿名对象。我们不仅可以对 Any,还能对某个具体的类/接口定义匿名对象,具体用法是 object 关键字+冒号:

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { /*...*/ }

    override fun mouseEntered(e: MouseEvent) { /*...*/ }
})

更进一步,可以在定义匿名类的时候同时实现继承关系:

open class A(x: Int) {
    public open val y: Int = x
}

interface B { /*...*/ }

val ab: A = object : A(1), B {
    override val y = 15
}

匿名对象做返回值

匿名对象类型除了以普通变量的形式出现,还可以当做函数的返回值:

class C {
    private fun getObject() = object { // private 方法,所以 printX() 方法可以引用成员变量 x
        val x: String = "x"
    }

    fun printX() {
        println(getObject().x) // 可以引用到 x
    }
}

但是并不是所有场景下都可以引用匿名对象的成员的,如下场景是可以的:

  • 当匿名对象是局部变量时;
  • 作为成员函数返回值或成员变量出现时,成员需是 private 且不是 inline 的;
    当成员不是 private 或者是 inline是,我们无法引用到匿名对象的成员,只会把它当做未进行过修改的类型进行解析,这个类型由成员显式声明的类型决定:
interface A {
    fun funFromA() {}
}
interface B

class C {
    // public成员方法,返回类型为 Any,无法引用到 x
    fun getObject() = object {
        val x: String = "x"
    }

    // public成员方法,返回类型为 A,无法引用到 x
    fun getObjectA() = object: A {
        override fun funFromA() {}
        val x: String = "x"
    }

    // public成员方法,返回类型是显式声明的类型 B,无法引用到方法 funFromA() 和 x
    // 而且这种同时存在多个基类的场景必须要显式声明返回类型 B,否则会报错 Right-hand side has anonymous type. Please specify type explicitly
    fun getObjectB(): B = object: A, B { 
        override fun funFromA() {}
        val x: String = "x"
    }
}

闭包

与 Java 中的匿名类一样,Kotlin 的匿名对象也可以引用到匿名对象外部的变量,从而实现闭包的效果,而且不需要像 Java 那样外部变量必须是 final 的:

fun countClicks(window: JComponent) {
    var clickCount = 0
    var enterCount = 0

    window.addMouseListener(object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            clickCount++
        }

        override fun mouseEntered(e: MouseEvent) {
            enterCount++
        }
    })
    // ...
}

object 声明

object 关键字另一种用法是修饰类或者类的成员,但是不能修饰局部变量。其中最常见的是直接修饰类,这时被修饰的类就是个线程安全的单例:

object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }

    val allDataProviders: Collection<DataProvider>
        get() = // ...
}

在 Kotlin 中使用上述单例:

DataProviderManager.registerDataProvider(...)

在 Java 中使用上述单例:

DataProviderManager.INSTANCE.registerDataProvider(...)

companion 关键字

可以在类里面用 companion object 声明对象:

    interface Factory<T> {
        fun create(): T
    }

    class MyClass {
        companion object Company : Factory<MyClass> {
            override fun create(): MyClass = MyClass()
        }
    }

    val f: Factory<MyClass> = MyClass
    f.create()
    val f1: Factory<MyClass> = MyClass.Company
    f1.create()

需要注意的是:

  • 类里面最多只能有一个 companion object;
  • 上述例子中的自定义名称 Company 可以省略,不影响使用,如果省略 Company 则调用的时候可以使用 MyClass.Companion,或者直接使用 MyClass 均可,MyClass 就代指其内部的 companion object;
  • 外部类的成员可以引用 companion object 里面的成员;
  • companion object 的用法看起来跟 Java 的 static 关键字有点像,但是其本质上还是一个对象,上述示例中 Company 实现了 Factory 接口也印证了这一点,如果要用真正的 static 字段,需要使用 @JVMStatic 注解,且 @JVMStatic 注解只能用于 object 或 companion object 修饰的对象的成员;

表达式和声明的区别

  • 表达式在声明的地方就会被立即初始化和执行;
  • 声明是延迟初始化的,即第一次被使用的时候才会初始化;

以上是关于对比Java学Kotlinobject 关键字的主要内容,如果未能解决你的问题,请参考以下文章

对比Java学Kotlinobject 关键字

对比Java学Kotlinobject 关键字

Kotlinobject 对象 ( object 关键字 | 对象声明 | 对象表达式 | 伴生对象 )

Kotlinobject 对象 ( object 关键字 | 对象声明 | 对象表达式 | 伴生对象 )

对比Java学Kotlin枚举

对比Java学Kotlin枚举