前言
最近学习Kotlin,所以也在对比Kotlin和java的差异,在java里有23种设计模式,但在Kotlin里面的写法是不相同的,所以在此记下笔记和心得。
单例模式
单例模式的核心是确保某一个类有且只有一个实例,并且自行实例化,向整个系统提供这个唯一实例
应用场景
单例模式在Android里运用也是比较广泛的
这个是为了减少内存消耗(只有一个实例)和方便管理数据(多个实例内存不共享)存在的
比如我们很常用的Glide:
我们Glide.with就是调用一个单例来着
//Glide.java
public static RequestManager with(Context context) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(context);
}
//RequestManagerRetriever.java
private static final RequestManagerRetriever INSTANCE = new RequestManagerRetriever();
public static RequestManagerRetriever get() {
return INSTANCE;
}
以及我们对各种工具类的封装都会用到
常见的单例模式写法
常见的单例模式有以下多种写法,下面会一一对比优劣势及Kotln与java写法上的差异
不过核心都是:
- 构造方法私有化,不能通过new 关键字来创建
- 在该类内部创建唯一的实例化对象
- 一个共有方法起获取到这一个唯一的实例化对象
饿汉式
饿汉式就是声明静态对象时初始化,一开始就存在,比较费内存
//java
public class SingletonByJava {
private static SingletonByJava instance = new SingletonByJava();
private SingletonByJava(){
}
public static SingletonByJava getInstance(){
return instance;
}
}
//kotlin
object SingletonByKotlin {
}
没错,饿汉式的声明在Kotlin非常简单,只需使用object这个关键字,这个关键字的意思是声明一个对象
因为在Kotlin里面是没有静态方法的,而用object关键字声明会被编译成:
一个类拥有一个静态成员来持有对自己的引用,并且这个静态成员的名称为INSTANCE,当然这个INSTANCE是单例的
这里我们来通过Kotlin对应的字节码转成java代码就能很容易看出
public final class SingletonByKotlin {
public static final SingletonByKotlin INSTANCE;
static {
SingletonByKotlin var0 = new SingletonByKotlin();
INSTANCE = var0;
}
}
使用上也和正常的单例模式一样
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.e(TAG,"java"+SingletonByJava.getInstance().toString())
Log.e(TAG,"Kotlin"+SingletonByKotlin.toString())
Log.e(TAG,"java"+SingletonByJava.getInstance().toString())
Log.e(TAG,"Kotlin"+SingletonByKotlin.toString())
}
}
懒汉式
懒汉式对于饿汉式的差别的就是创建是不实例化对象,等到使用时再实例化,可以节省一些内存,但是线程不安全,可能会出现创建多个实例。
//java
public class SingletonByJava {
private static SingletonByJava instance;
private SingletonByJava(){
}
private static SingletonByJava getInstance(){
if (instance == null){
instance = new SingletonByJava();
}
return instance;
}
}
//Kotlin
//主构造函数私有化
class SingletonByKotlin private constructor(){
companion object {
private var instance: SingletonByKotlin? = null
//自定义属性访问器
get() {
if (field == null)
field = SingletonByKotlin()
return field
}
//这里不能使用getInstance作为方法名,因为companion object内部已经有了getInstance这个方法了
fun get(): SingletonByKotlin{
return instance!!
}
}
}
静态内部类式
写法基本同java
//java
public class SingletonByJava {
private static class SingletonHolder{
private static SingletonByJava instance = new SingletonByJava();
}
public static SingletonByJava getInstance(){
return SingletonHolder.instance;
}
}
//Kotlin
class SingletonByKotlin {
companion object {
val instance = SingletonHolder.holder
}
private object SingletonHolder{
val holder = SingletonByKotlin()
}
}
线程安全懒汉式
众所周知,懒汉式是线程不安全的,要想让其线程安全,那么就需要使用同步锁
在Kotlin中,如果你需要将方法声明为同步,使用@Synchronized注解
//java
public class SingletonByJava {
private static SingletonByJava instance;
private SingletonByJava(){
}
//使用同步锁
public static synchronized SingletonByJava getInstance(){
if (instance == null){
instance = new SingletonByJava();
}
return instance;
}
}
//Kotlin
class SingletonByKotlin private constructor(){
companion object {
private var instance: SingletonByKotlin? = null
get() {
if (field == null)
field = SingletonByKotlin()
return field
}
@Synchronized
fun get(): SingletonByKotlin{
return instance!!
}
}
}
双重校验锁式(Double Check Lock)
这个网上建议和使用最多的方法,因为前面的线程安全懒汉式其实也不能算是完全安全的
//java
public class SingletonByJava {
private volatile static SingletonByJava instance;
private SingletonByJava(){
}
public static SingletonByJava getInstance(){
if (instance == null){
synchronized (SingletonByJava.class){
if (instance == null){
instance = new SingletonByJava();
}
}
}
return instance;
}
}
//Kotlin
class SingletonByKotlin private constructor(){
companion object {
@Volatile
private var instance: SingletonByKotlin? = null
get() {
if (field == null){
synchronized(SingletonByKotlin::class){
if (field == null)
field = SingletonByKotlin()
}
}
return field
}
@Synchronized
fun get(): SingletonByKotlin{
return instance!!
}
}
}
//极短版Kotlin写法
class SingletonByKotlin private constructor(){
companion object {
val instance: SingletonByKotlin by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED){
SingletonByKotlin()
}
}
}
极短版的Kotlin写法用到的知识点比较多,慢慢解释吧
首先是用了高阶函数和自身语法的一些优化,高阶函数简单的说就是将函数用作参数或返回值的函数,然后又使用了Kotlin的委托。在Kotlin的标准库里,内置了很多工厂方法来实现属性的委托。
延迟属性 lazy
lazy() 是一个函数, 接受一个 Lambda 表达式作为参数, 返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托。 第一次调用 get() 会执行已传递给 lazy() 的 lamda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
//LazyThreadSafetyMode.KT
public enum class LazyThreadSafetyMode {
/**
* Locks are used to ensure that only a single thread can initialize the [Lazy] instance.
*加锁来保证只有一个线程可以初始化实例
*/
SYNCHRONIZED,
/**
* Initializer function can be called several times on concurrent access to uninitialized [Lazy] instance value,
* but only the first returned value will be used as the value of [Lazy] instance.
*/
PUBLICATION,
/**
* No locks are used to synchronize an access to the [Lazy] instance value; if the instance is accessed from multiple threads, its behavior is undefined.
*
* This mode should not be used unless the [Lazy] instance is guaranteed never to be initialized from more than one thread.
*/
NONE,
}
可以看到我们传入的是mode = LazyThreadSafetyMode.SYNCHRONIZED,而LazyThreadSafetyMode.SYNCHRONIZED从注释中可以看出是加锁保证单例线程安全
然后走的LazyThreadSafetyMode.SYNCHRONIZED是SynchronizedLazyImpl,而SynchronizedLazyImpl实现了Lazy接口
Lazy接口
public interface Lazy<out T> {
/**
* Gets the lazily initialized value of the current Lazy instance.
* Once the value was initialized it must not change during the rest of lifetime of this Lazy instance.
* 获取当前实例化对象
* 一旦被实例化后,那么该对象就不会再被改变
*/
public val value: T
/**
* Returns `true` if a value for this Lazy instance has been already initialized, and `false` otherwise.
* Once this function has returned `true` it stays `true` for the rest of lifetime of this Lazy instance.
* 返回true表示,已经延迟实例化过了,false 表示,没有被实例化,
* 一旦方法返回true,该方法会一直返回true,且不会再继续实例化
*/
public fun isInitialized(): Boolean
}
SynchronizedLazyImpl的实现
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
//判断是否已经初始化过,如果初始化过直接返回,不在调用高级函数内部逻辑
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
} else {
val typedValue = initializer!!()//调用高级函数获取其返回值
_value = typedValue //将返回值赋值给_value,用于下次判断时,直接返回高级函数的返回值
initializer = null
typedValue
}
}
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
private fun writeReplace(): Any = InitializedLazyImpl(value)
}
我们可以看出,SynchronizedLazyImpl 重写了Lazy接口的value属性,并重新定义了其属性访问器,这个逻辑和java的双重检验是一致的。
所以我们先再回首看下DCL的实现代码
val instance: SingletonByKotlin by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED){
SingletonByKotlin()
}
lazy(mode: LazyThreadSafetyMode, initializer: () -> T)
这个是高阶函数,传入一个常量和一个函数,按道理来说写法应该是这样的:
val instancne: SingletonByKotlin by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED,{SingletonByKotlin()})
但由于Kotlin自身语法的原因,你这样写是不可以的,你要把函数写在后面。
然后可以看到我们使用了lazy实际上只是实例化了SynchronizedLazyImpl对象而已
并没有进行值的获取,那么它是怎么拿到高阶函数的返回值的?
这个就是Kotlin的委托了,通过by关键字,在 by 后面的表达式是该委托, 因为属性对应的 get()和 set()会被委托给它的 getValue() 和 setValue() 方法。 属性的委托不必实现任何的接口,但是需要提供一个 getValue() 函数及setValue()(是val的话就没有写了)
这里Lazy已经帮我们写好了getValue()了
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
所以整体逻辑就是,我把SingletonByKotlin()传过去告诉怎么实例,然后在lazy进行了双重检查(逻辑类似),然因为使用的是委托,后续调用都是返回结果了(妥妥的DLC没毛病)
网友评论