Kotlin object的几种用法示例详解

手机APP/开发
396
0
0
2023-07-01
标签   Kotlin
目录
  • 1.object:匿名内部类
  • 2.object: 伴生对象
  • 3.单例模式

1.object:匿名内部类

在Android最常用的匿名内部类之一就是点击事件,用Java语言写的话就是下面这样:



public interface OnClickListener {
    void onClick(View v);
}
button.setOnClickListener(new View.OnClickListener(){
    @Override
    public void onClick(View v){
    }
});

上面的代码在Kotlin中就要这么写

interface OnClickListener {
    fun onClick(v: View)
}
button.setOnClickListener(object : View.OnClickListener {
    override fun onClick(v: View) {
    }
})

上面的代码中object: View.OnClickListener就是匿名内部类的使用方式。

这里的代码为了说明【object: 匿名内部类】的用法所以写的比较啰嗦,如果用Lambda写法的话可以这么写

button.setOnClickListener {
}

上面是匿名内部类常用的一种方式,还有另一种用法就是:在继承一个抽象类的同时还可以实现多个接口,直接看代码:

interface Behavior {
    fun sleep()
}
interface Characteristics {
    fun gender()
}
abstract class Person {
    abstract fun walk()
}
fun main() {
    // 这个匿名内部类,在继承了Person类的同时,还实现了Behavior(行为)、Characteristics(特征)两个接口
    val item = object : Person(), Behavior, Characteristics {
        override fun sleep() {
        }
        override fun gender() {
        }
        override fun walk() {
        }
    }
}

对上面的代码说明:在继承了Person类的同时,还实现了Behavior(行为)、Characteristics(特征)两个接口并分别重写了他们的方法,这种用法灵活性比较高。

2.object: 伴生对象

Kotlin 当中没有 static 关键字,所以我们没有办法直接定义静态方法和静态变量。不过,Kotlin 还是为我们提供了伴生对象,来帮助实现静态方法和变量。

先看一种嵌套类的特殊情况

class Person {
    object InnerSingleton {
        fun foo() {}
    }
}
//反编译后的Java代码
public final class Person {
    public static final class InnerSingleton {
        @NotNull
        public static final Person.InnerSingleton INSTANCE;
        public final void foo() {
        }
        private InnerSingleton() {
        }
        static {
            Person.InnerSingleton var = new Person.InnerSingleton();
            INSTANCE = var;
        }
    }
}

由以上代码可知定义的foo方法并不是一个静态方法,那要如何实现这个静态方法呢,可以foo方法上面加入注解@JvmStatic,这样反编译后foo方法就是一个静态方法了

class Person {
    object InnerSingleton {
        @JvmStatic
        fun foo() {}
    }
}
//反编译后的Java代码
public final class Person {
    public static final class InnerSingleton {
        @NotNull
        public static final Person.InnerSingleton INSTANCE;
        @JvmStatic
        public static final void foo() {
        }
        private InnerSingleton() {
        }
        static {
            Person.InnerSingleton var = new Person.InnerSingleton();
            INSTANCE = var;
        }
    }
}

经过这么改动Kotlin和Java的实现方式就都可以这么写

Person.InnerSingleton.foo()

此时还有一个问题,foo方法的InnerSingleton有点多余,它并没有实际意义,想直接调用Person.foo()要怎么做?

在object前面加入一个companion关键字即可

//Kotlin代码
class Person {
    companion object InnerSingleton {
        @JvmStatic
        fun foo() {
        }
    }
}
//反编译后的代码
public final class Person {
    @NotNull
    public static final Person.InnerSingleton InnerSingleton = new Person.InnerSingleton((DefaultConstructorMarker)null);
    @JvmStatic
    public static final void foo() {
        InnerSingleton.foo();
    }
    public static final class InnerSingleton {
        @JvmStatic
        public final void foo() {
        }
        private InnerSingleton() {
        }
        // $FF: synthetic method
        public InnerSingleton(DefaultConstructorMarker $constructor_marker) {
            this();
        }
    }
}

加入companion就实现了Person.foo()的调用。companion object就是Kotlin中的伴生对象, 它其实是嵌套单例的一种也是情况。

通过上面的代码可以得出一个结论:当伴生对象存在时如果还存在

@JvmStatic修饰的方法或者属性,那么Kotlin编译成Java代码后它就会被挪到外部的类中变成静态成员。嵌套单例是

object单例的一种特殊情况,伴身对象是嵌套单例的一种特殊情况。

3.单例模式

Kotlin中定义一个普通的单例模式非常简单只需要在类名前面用object声明即可

object Utils{
}

这样就能实现单例,但是Java中的单例是比较复杂的,并且还有懒汉式和饿汉式之分,这样真的就能定义吗?

我们看一下这段代码反编译成Java的代码看一看就很清楚了:

public final class Utils {
    @NotNull
    public static final Utils INSTANCE;
    private Utils() {
    }
    static {
        Utils var = new Utils();
        INSTANCE = var;
    }
}

static中的代码由虚拟机决定只会运行一次,同时在保证线程安全的前提下也保证了INSTANCE只运行一次。

但是这里有2个缺陷:

  • 不支持懒加载
  • 不能传递参数,在Android中会经常用到context的情况,不能传参这个用法就没有意义

那要如何解决?

  • 单例模式——借助懒加载委托
class Person(val name: String) {
}
fun main() {
    val user by lazy { Person("张三") }
    println("user is name ${user.name}")
}

上面的代码使用了by lazy{ }创建了Person对象,而Person对象最终创建的时机是在println创建的,也就是说懒加载委托的特点就是只有在使用这个对象是才会创建实例。

  • 单例模式—— 伴生对象 Double Check

直接看代码,代码是从《朱凯·Kotlin编程第一课》直接拷贝过来的

class UserManager private constructor(name: String) {
    companion object {
        @Volatile
        private var INSTANCE: UserManager? = null
        fun getInstance(name: String): UserManager =
        // 第一次判空
        INSTANCE ?: synchronized(this) {
            // 第二次判空
            INSTANCE ?: UserManager(name).also { INSTANCE = it }
        }
    }
}
fun main() {
    // 使用
    UserManager.getInstance("Tom")
}

这里首先定义了一个INSTANCE并且用private修饰这样就可以保证不能被外界直接访问,同时添加了@Volatile注解可以保证INSTANCE的可见性,而 getInstance() 方法当中的 synchronized,保证了 INSTANCE 的原子性。因此,这种方案还是线程安全的。

另外还要注意的一点是初始化定义的INSTANCE的默认值时null你这也就保证了只有在使用它的时候才会被实例化,也就是说实现懒加载的模式。

这中实现方式也是可以传参的,在getInstance()方法中定义需要的属性即可实现传参

这个代码其实跟Java的懒汉模式 + synchronized 同步锁非常相似

public class UserManager {
    private static volatile UserManager mInstance = null;
    private UserManager() {
    }
    public static UserManager getInstance() {
        if (mInstance == null) {
            synchronized (UserManager.class) {
                if (mInstance == null) {
                    mInstance = new UserManager();
                }
            }
        }
        return mInstance;
    }
}

上面介绍了3中单例模式的实现方式,他们的使用场景如下:

  • 如果单例占用内存很小并且堆内存不敏感也不需要传参,直接使用object即可,例如常用且无需传参的的Utils类;
  • 如果单例占用内存很小并且不需要传参,但是它的内部属性会触发消耗资源的网络请求和数据库查询,这是就需要用object搭配by lazy{ };
  • 如果工程比较简单,只有少数的单例场景、懒加载需求、需要传递参数就需要使用Double Check的方式了,例如PreferenceUtils。