Kotlin与Java比较:对象

前言

Kotlin作为JVM系的语言,起源于Java又不同于Java。通过在语言层面比较两者的区别,可以使得开发者能够快速学习,融会贯通。

匿名对象

有时需要对某个类做轻微的改动并获取它的对象,Kotlin与Java提供了不同的支持。

  • Java
    在Java中提供了匿名内部类对这一需求的支持,即在初始化类的地方覆写基类的实现。
class Person
{
    public void show()
    {
        System.out.println(“Person”);
    }
}

class Main{
    public void test(new Person(){    //匿名内部类
          @override
          show(){
              //xxx
          }
    });
}

匿名内部类首先是内部类,是在类中定义的类,同时匿名表示没有名字,直接覆盖其实现后new出来的。匿名内部类的使用非常广泛,特别是那种需要修改某一个类的实现,且只需要这个实现的一个对象的时候。需要注意的是,在Java中,匿名内部类只能访问外部类的final变量。

Kotlin

在Kotlin中与匿名内部类对应的是对象表达式,即通过object关键字去定义与初始化匿名类。其写法结构为:

object : 基类名(主构造函数参数){
      覆盖类的实现
}

例如:

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { …… }
    override fun mouseEntered(e: MouseEvent) { …… }
})

由于object后仅有基类的名称,故覆盖后的类是没有名字的,即匿名的。

若有多个基类,可以通过逗号分隔:

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

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

若仅仅需要一个简单对象,无基类,则可以这样写:

fun foo() {
    val adHoc = object {
        var x: Int = 0
        var y: Int = 0
    }
    print(adHoc.x + adHoc.y)
}

该匿名对象可以作为私有作用域的对象,也可以作为公有作用域的对象。前者返回的是匿名对象类型,后者返回的是匿名对象声明的基类,若无基类则返回Any类型。

class C {
    // 私有函数,所以其返回类型是匿名对象类型
    private fun foo() = object {
        val x: String = "x"
    }

    // 公有函数,所以其返回类型是 Any
    fun publicFoo() = object {
        val x: String = "x"
    }

    fun bar() {
        val x1 = foo().x        // 没问题
        val x2 = publicFoo().x  // 错误:未能解析的引用“x”
    }
}

单例对象与静态方法

  • Java
    单例即一个类的唯一实例,在Java中为了获得单例常会使用设计模式中的到单例模式来获得单例。
// 一种单例的实现
public class SingletonDemo {
    private static SingletonDemo instance;
    private SingletonDemo(){

    }
    public static SingletonDemo getInstance(){
        if(instance==null){
            instance=new SingletonDemo();
        }
        return instance;
    }
}

与单例类似的为静态类,但它们也有区别:

  • 单例可以延迟加载或控制,而静态类在被第一次初始化时加载

  • 单例可以被override,而静态类不可以

  • 单例易于被测试

  • 单例与静态类的回收时机不同(待补充)

  • Kotlin
    在Kotlin中为了使用单例或类似于静态类,可以使用对象声明。对象声明相比于对象表达式,在写法上主要是在object后添加了类名,并且不能将该声明放在局部作用域中,但是它们可以嵌套到其他对象声明或非内部类中。其例子为:

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

// 使用该对象,直接通过类名使用
DataProviderManager.registerDataProvider(……)

若对象声明也有基类,在其名称后添加冒号与基类即可:

object DefaultListener : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { …… }

    override fun mouseEntered(e: MouseEvent) { …… }
}

对于Kotlin的这一设计,是通过对象声明的名称来对方法进行调用,与Java的静态成员相比,还是略有不同。因为Java是通过外部的类名访问静态成员,而Kotlin的对象声明是通过声明的类名来访问成员。为此,Kotlin还有一个新的概念,即伴生对象。

class NumberTest {
    companion object Obj {  
        var flag = false
        fun plus(x:int, y:int): Int {... }
    }
}

通过在类内部的对象声明前添加companion关键字,即为伴生对象。这个对象是属于外部类的,通过外部类的类名即可调用该对象的成员:

NumberTest.plus(1, 2)    
NumberTest.flag

这就与Java中使用通过类名访问静态成员类似。

对于有名称的伴生对象,可以通过外部类名访问,访问语法为:

//外部类类名.半生对象名.方法
NumberTest.Obj.plus(1, 2)   
//外部类类名.半生对象名.成员
NumberTest.Obj.flag

对于无名称的伴生对象,可以通过外部类名访问,访问语法为:

class NumberTest {
    companion object Obj {  
        var flag = false
        fun plus(x:int, y:int): Int {... }
    }
}

//外部类类名.Companion.方法
NumberTest.Companion.plus(1, 2)   
//外部类类名.Companion名.成员
NumberTest.Companion.flag

注意,伴生对象成员形如Java类中的静态成员,但实际上在运行时它们是真实对象的成员

对象表达式/对象声明/伴生对象的区别

对象表达式和对象声明之间有一个重要的语义差别:

  • 对象表达式是在使用他们的地方立即执行(及初始化)的;
  • 对象声明是在第一次被访问到时延迟初始化的;
  • 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配。

你可能感兴趣的:(Kotlin与Java比较:对象)