Kotlin - 单例、伴生对象、对象表达式 object

一、概览

1.1 object 关键字的几种用法

修饰类(对象声明)

是一个饿汉单例,定义一个类并创建它的实例。

修饰伴生对象(静态调用)

伴随着类而存在的对象,在类加载的时候被实例化,多个实例会共享。

对象表达式(匿名内部类)

可以多继承多实现,访问外部函数中的局部变量不用加 final。

1.2 几种“Java静态方法”的方式 

Kotlin 极度弱化了静态方法这个概念,提供了比静态方法更好用的语法特性,同样是通过【类名.函数名】来调用。

object

适合写“全是静态方法的工具类”。(Kotlin v1.9 版本新增的 data object 是针对密封类/密封接口使用场景的优化,toString() 不会打印 HashCode 等无用信息,让输出更有意义)

companion object适合给类定义“专门的静态方法”。
顶层函数

假设定义顶层函数 method() 的文件名为 Helper.kt,编译器会创建一个 HelperKt.java,在Java中就可以 HelperKt.method() 调用了。

@JvmStatic 注解给上面的单例类或伴生对象添加注解后,在Java中就可以当做静态方法调用了。

二、单例(对象声明)

定义一个类并创建它的实例。可以继承类实现接口、包含属性和方法,但是不能手动声明构造。由于不能使用构造函数,可以使用 init()。

 Kotlin声明:可以声明在顶层、也可以声明在类中(见下方和伴生对象对比)

object Demo{
    val name = "单例"
    fun show() = println(name)
}

Demo.show()
println(Demo.name)

反编译成Java代码:(饿汉)

public final class Demo {
    //成员变量
    private static String name;
    //通过静态字段提供实例
    public static final Demo INSTANCE;
    //私有化构造
    private Demo() { }
    //静态代码块中初始化
    static {
        Demo var0 = new Demo();
        INSTANCE = var0;
        name = "单例";
    }
    //成员方法
    public final void show() {
        String var1 = name;
        System.out.println(var1);
    }
    //生成的getter、setter
    public final String getName() { return name; }
    public final void setName(@NotNull String var1) {
        Intrinsics.checkNotNullParameter(var1, "<set-?>");
        name = var1;
    }
}

 模仿Java方式写Kotlin带参数单例:

class Demo private constructor(
    private var id: Long,
    private var name: String
) {
    companion object {
        private var instance: Demo? = null
        fun getInstance(id: Long, name: String) {
            instance ?: synchronized(this) {
                instance ?: Demo(id, name).also { instance = it }
            }
        }
    }
}

三、伴生对象(静态)

3.1 伴生对象 & 类中单例

  • 伴随着类而存在的对象,在类加载的时候被实例化,多个实例会共享。Kotlin 中没有 static 静态的概念,可以使用顶层函数和常量。顶层函数不能访问类中私有的成员,在伴生对象中需要用外部实例来访问(静态内部类无法直接访问外部类中的非静态成员),例如Outter().num。
  • 伴生对象的名称可以省略(默认为Companion),不管伴生对象的名称是否手动声明,都可以直接用外部类名调用伴生对象中的属性和方法。
  • 当类中成员和伴生对象中成员重名的时候,类名调用的是伴生对象中的,实例调用的是类中的。
class Outer {
    companion object Inner {    //Inner名称可以省略
        var str: String = ""
        fun show(){}
    }
}

fun main() {
    Outer.str = ""
    Outer.show()
    Outer.Inner.str = "" //编译器会提示 Inner 是多余的
    Outer.Inner.show() //编译器会提示 Inner 是多余的
    val obj: Outer.Inner = Outer.Inner
    obj.str = ""
    obj.show()
}

伴生对象

类中单例对象声明

相同

都是一个静态内部类,通过(外部类名.内部类名.成员名)的方式调用。

构造都是 private

都可以继承类、实现接口、拥有属性和函数

不同

①外部类中创建并持有伴生对象的实例

②定义的属性会成为外部类的私有静态字段,声明private便不会。函数还留在内部。

③一个类中只能拥有一个伴生对象

①自己就是实例

②持有自己的属性和函数

③一个类中可以拥有多个单例对象

Kotlin代码:

class Demo {
    //伴生对象
    companion object One {    //不取名的话,默认名称是 Companion
        var a = 1
        val b = 2
        fun one() = println("伴生对象")
    }
    //单例
    object Two {
        var c = 3    //可以自定义getter、setter
        val d = 4
        fun two() = println("单例")
    }
}

 反编译成Java代码:

public final class Demo {
    //伴生对象中的成员变量(外部类持有)
    private static int a = 1;
    private static final int b = 2;
    //持有伴生对象实例(外部类持有)
    public static final Demo.One One = new Demo.One((DefaultConstructorMarker)null);
    
    //伴生对象
    public static final class One {
        //私有化构造
        private One() { }
        // $FF: synthetic method    合成的方法
        public AA(DefaultConstructorMarker $constructor_marker) {
            this();
        }
        //成员方法
        public final void one() {
            String var1 = "伴生对象";
            System.out.println(var1);
        }
        //生成的getter、setter
        public final int getA() { return Demo.a; }
        public final void setA(int var1) { Demo.a = var1; }
        public final int getB() { return Demo.b; }
    }
    
    //单例
    public static final class Two {
        //通过静态字段提供实例
        public static final Demo.Two INSTANCE;
        //私有化构造
        private Two() { }
        //静态代码块中初始化
        static {
            Demo.Two var0 = new Demo.Two();
            INSTANCE = var0;
            c = 3;
            d = 4;
        }
        //成员变量
        private static int c;
        private static final int d;
        //成员方法
        public final void two() {
            String var1 = "单例";
            System.out.println(var1);
        }
        //生成的getter、setter
        public final int getC() { return c; }
        public final void setC(int var1) { c = var1; }
        public final int getD() { return d; }
    }

}

在伴生对象中的定义的 val/var 反编译后的权限修饰符是private,因此会生成一个静态对象和 getter/setter,成本远超一个静态参数的价值:

  • 静态常量:由于 val 反编译是 private static final,因此会生成 getter。使用 consta val 反编译是 public static final,就不会生成 getter 了。
  • 静态变量:由于 var 反编译是 private static,因此会生成 getter&setter。使用 @JvmFeild 反编译是 public static,就不会生成 getter&setter。

3.2 静态工厂

interface Car {
    val brand: String
    companion object {
        operator fun invoke(type: CarType): Car {
            return when (type) {
                CarType.AUDI -> Audi()    
                CarType.BMW -> BMW()
            }
        }
    }
}

Car(CarType.BMW)

3.3 扩展方法

虽然是在伴生对象上扩展,实际相当于给外部类增加了静态方法。

fun Outter.Companion.show(){}
Outter.show()

3.4 Java互调

反编译成 Java 代码发现伴生对象是外部类持有的一个静态实例,属性和方法的权限都是private,全都需要通过Outter().Inner.XXX来调用的,想要通过 类名.XXX 调用,可以使用注解 @JvmStatic 修饰函数、@JvmField 修饰属性,就在外部类中增加了对应的 public static 字段和方法。

//Kotlin
class Demo {
    companion object Three {
        @JvmField
        var a = 1
        @JvmField
        val b = 2
        @JvmStatic
        fun three() = println("单例")
    }
//Java
class Demo {
    public static int a = 1;
    public static final int b = 2;
    public static final void three() { Three.three(); }
}

四、对象表达式(匿名内部类)

解决一Java 在运行时将匿名内部类当作是它所继承/实现的父类/接口来使用,因此在匿名内部类中增加了父类/父接口之外的额外方法是无法正常使用的。Kotlin 用对象表达式取代了 Java 的匿名内部类解决了这一点。
解决二

访问外部函数中的局部变量也不用加 final(详见:Lambda闭包)。对SAM(单抽接口)一般使用 Lambda 更便捷,当需要重写多个抽象方法时,只能选择匿名对象了。

4.1 赋值给变量

4.1.1 无继承无实现

val obj = object {
    init { println() }
    val name = ""
    fun show() { println() }
}

4.1.2 单个父类型

abstract class A{}
interface B{}
val obj1: A = object : A() {}
val obj2: B = object : B {}

4.1.3 多个父类型

顶层变量

成员变量

声明为 private 的变量其类型才能被正常识别(IDE显示类型为 <anonymous object : AA, BB>)。
声明为 public 的变量或当作函数返回值时,会被 Kotlin 识别为父类/父接口类型,由于有多个父类型需要显示声明为其中的一个,如果声明没有就会被识别为 Any 类型从而不能调用成员变量/方法。
局部变量不存在可见性修饰符,能被正常识别(IDE显示类型为 <anonymous object : AA, BB>)。
abstract class AA{ abstract fun aa() }
interface BB{ fun bb() }
//用作顶层变量
val obj1: AA = object: AA(), BB {
    override fun aa() {}
    override fun bb() {}
}
private val obj2 = object: AA(), BB {
    override fun aa() {}
    override fun bb() {}
}
//用作成员变量
class Demo {
    val obj3: AA = object: AA(), BB {
        override fun aa() {}
        override fun bb() {}
    }
    private val obj4 = object: AA(), BB {
        override fun aa() {}
        override fun bb() {}
    }
}
//用作局部变量
fun show(){
    val obj = object : AA(), BB {
        override fun aa() {}
        override fun bb() {}
    }
}

4.2 匿名对象传参、闭包

fun outer() {
    var num = 0    //被访问的局部可以声明为 var,而不是有 final 的 val
    val obj = object {
        fun inner() { num += 1 }
    }
}

​//Android中设置点击监听时,访问外部局部变量
fun show() {
    var num = 0
    //用对象表达式写
    xxx.setOnClickListener(object : View.OnClickListener{
        override fun onClick(v: View){ num += 1 }
    })
    //用 Lambda 简写(仅针对只有一个抽象方法的接口,多方法重写只能用对象表达式)
    xxx.setOnClickListener{ view -> num += 1 }
}
  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值