Kotlin поток безопасной родной ленивый синглтон с параметром

В Java мы можем написать безопасные для синглетонов, используя двойную проверку блокировки и изменчивость:

    public class Singleton {
        private static volatile Singleton instance;

        public static Singleton getInstance(String arg) {
        Singleton localInstance = instance;
        if (localInstance == null) {
            synchronized (Singleton.class) {
                localInstance = instance;
                if (localInstance == null) {
                    instance = localInstance = new Singleton(arg);
                }
            }
        }
        return localInstance;
    }
}

Как мы можем написать это в kotlin?


Об объекте

object A {
    object B {}
    object C {}
    init {
        C.hashCode()
    }
}

Я использовал kotlin декомпилятор, чтобы получить это

public final class A {
   public static final A INSTANCE;

   private A() {
      INSTANCE = (A)this;
      A.C.INSTANCE.hashCode();
   }
   static {
      new A();
   }

   public static final class B {
      public static final A.B INSTANCE;
      private B() {
         INSTANCE = (A.B)this;
      }
      static {
         new A.B();
      }
   }

   public static final class C {
      public static final A.C INSTANCE;
      private C() {
         INSTANCE = (A.C)this;
      }
      static {
         new A.C();
      }
   }
}

Все объекты имеют конструктор, вызываемый в static блок. Исходя из этого, мы можем думать, что это не лень.

Закройте правильный ответ.

    class Singleton {
        companion object {
            val instance: Singleton by lazy(LazyThreadSafetyMode.PUBLICATION) { Singleton() }
        }
    }

декомпилированные:

public static final class Companion {
      // $FF: synthetic field
      private static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.property1(new PropertyReference1Impl(Reflection.getOrCreateKotlinClass(Singleton.Companion.class), "instance", "getInstance()Lru/example/project/tech/Singleton;"))};

      @NotNull
      public final Singleton getInstance() {
         Lazy var1 = Singleton.instance$delegate;
         KProperty var3 = $$delegatedProperties[0];
         return (Singleton)var1.getValue();
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }

Я надеюсь, что разработчики Kotlin сделают реализацию без отражения в будущем...

3 ответа

Решение

Kotlin имеет эквивалент вашего Java-кода, но более безопасный. Ваша проверка двойной блокировки не рекомендуется даже для Java. В Java вы должны использовать внутренний класс на static, который также объясняется в идиоме держателя инициализации по требованию.

Но это Java. В Kotlin просто используйте объект (опционально, ленивый делегат):

object Singletons {
    val something: OfMyType by lazy() { ... }

    val somethingLazyButLessSo: OtherType = OtherType()
    val moreLazies: FancyType by lazy() { ... }
}

Затем вы можете получить доступ к любой переменной-члену:

// Singletons is lazy instantiated now, then something is lazy instantiated after.  
val thing = Singletons.something // This is Doubly Lazy!

// this one is already loaded due to previous line
val eager = Singletons.somethingLazyButLessSo

// and Singletons.moreLazies isn't loaded yet until first access...

Kotlin преднамеренно избегает путаницы, которую люди имеют с одиночками в Java. И избегает "неправильных версий" этого паттерна - которых много. Вместо этого он предоставляет более простую и безопасную форму синглетонов.

Учитывая использование lazy()Если у вас есть другие участники, каждый из них по отдельности будет ленивым. И так как они инициализируются в лямбду, передается lazy() Вы можете делать вещи, о которых вы просили, о настройке конструктора и для каждого свойства элемента.

В результате у вас ленивая загрузка Singletons объект (при первом доступе к экземпляру), а затем более медленная загрузка something (при первом доступе участника) и полная гибкость в построении объекта.

Смотрите также:

В качестве примечания обратите внимание на библиотеки типов реестра объектов для Kotlin, которые похожи на внедрение зависимостей, предоставляя вам одиночные файлы с параметрами внедрения:

Объявление объекта именно для этого:

object Singleton {
    //singleton members
}

Он ленив и поточно-ориентирован, он инициализируется при первом вызове, так же как статические инициализаторы Java.

Вы можете объявить object на верхнем уровне или внутри класса или другого объекта.

Для получения дополнительной информации о работе с object с Java, пожалуйста, обратитесь к этому ответу.


Что касается параметра, если вы хотите достичь точно такой же семантики (первый вызов getInstance принимает его аргумент для инициализации синглтона, следующие вызовы просто возвращают экземпляр, отбрасывая аргументы), я бы предложил эту конструкцию:

private object SingletonInit { //invisible outside the file
    lateinit var arg0: String
}

object Singleton {
    val arg0: String = SingletonInit.arg0
}

fun Singleton(arg0: String): Singleton { //mimic a constructor, if you want
    synchronized(SingletonInit) {
        SingletonInit.arg0 = arg0
        return Singleton
    }
}

Основным недостатком этого решения является то, что для его скрытия требуется определить синглтон в отдельном файле. object SingletonInit и вы не можете ссылаться Singleton непосредственно, пока это не инициализировано.

Также см. Аналогичный вопрос о предоставлении аргументов для синглтона.

Я недавно написал статью на эту тему. TL;DR Вот решение, к которому я пришел:

1) Создать SingletonHolder учебный класс. Вы должны написать это только один раз:

open class SingletonHolder<out T, in A>(creator: (A) -> T) {
    private var creator: ((A) -> T)? = creator
    @Volatile private var instance: T? = null

    fun getInstance(arg: A): T {
        val i = instance
        if (i != null) {
            return i
        }

        return synchronized(this) {
            val i2 = instance
            if (i2 != null) {
                i2
            } else {
                val created = creator!!(arg)
                instance = created
                creator = null
                created
            }
        }
    }
}

2) Используйте это в своих синглетонах:

class MySingleton private constructor(arg: ArgumentType) {
    init {
        // Init using argument
    }

    companion object : SingletonHolder<MySingleton, ArgumentType>(::MySingleton)
}

Инициализация синглтона будет ленивой и поточно-ориентированной.

Другие вопросы по тегам