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)
}
Инициализация синглтона будет ленивой и поточно-ориентированной.