我正在尝试将Android应用程序从Java转换为Kotlin。应用程序中有一些单身人士。我在没有构造函数参数的情况下为单例使用了伴随对象。还有另一个单例采用构造函数参数。
Java代码:
public class TasksLocalDataSource implements TasksDataSource {
private static TasksLocalDataSource INSTANCE;
private TasksDbHelper mDbHelper;
// Prevent direct instantiation.
private TasksLocalDataSource(@NonNull Context context) {
checkNotNull(context);
mDbHelper = new TasksDbHelper(context);
}
public static TasksLocalDataSource getInstance(@NonNull Context context) {
if (INSTANCE == null) {
INSTANCE = new TasksLocalDataSource(context);
}
return INSTANCE;
}
}
我在kotlin的解决方案:
class TasksLocalDataSource private constructor(context: Context) : TasksDataSource {
private val mDbHelper: TasksDbHelper
init {
checkNotNull(context)
mDbHelper = TasksDbHelper(context)
}
companion object {
lateinit var INSTANCE: TasksLocalDataSource
private val initialized = AtomicBoolean()
fun getInstance(context: Context) : TasksLocalDataSource {
if(initialized.getAndSet(true)) {
INSTANCE = TasksLocalDataSource(context)
}
return INSTANCE
}
}
}
我错过了什么吗?线程安全?懒惰?
有一些类似的问题,但我不喜欢答案:)
答案 0 :(得分:62)
这是Google的架构组件sample code的一个简洁替代方案,它使用also
函数:
class UsersDatabase : RoomDatabase() {
companion object {
@Volatile private var INSTANCE: UsersDatabase? = null
fun getInstance(context: Context): UsersDatabase =
INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
}
private fun buildDatabase(context: Context) =
Room.databaseBuilder(context.applicationContext,
UsersDatabase::class.java, "Sample.db")
.build()
}
}
答案 1 :(得分:17)
我不完全确定你为什么需要这样的代码,但这是我最好的镜头:
BackColor
这与您编写的内容类似,并且具有相同的API。
一些注意事项:
请勿在此处使用int sum = 0;
for(int i = 1; i < N; i *= 2)
for(int j =0; j <i; j++)
sum++;
。它有不同的用途,可以在这里使用可空的变量。
int sum = 0;
for(int i = 0; i < N; i *= 2)
for(int j =0; j <i; j++)
sum++;
做了什么? class TasksLocalDataSource private constructor(context: Context) : TasksDataSource {
private val mDbHelper = TasksDbHelper(context)
companion object {
private var instance : TasksLocalDataSource? = null
fun getInstance(context: Context): TasksLocalDataSource {
if (instance == null) // NOT thread safe!
instance = TasksLocalDataSource(context)
return instance!!
}
}
}
在这里永远不会为空,这是Kotlin的保证。所有检查和断言都已由编译器实现。
如果您只需要一个懒惰的初始化类lateinit
的实例,那么只需使用一堆惰性属性(在对象内或包级别上):
checkNotNull(context)
答案 2 :(得分:6)
Thread-Safe Solution
#Write Once; Use Many;
可以创建一个实现单例逻辑的类,该类也包含单例实例。它使用Double-Check Locking在同步块中实例化实例,以消除在多线程环境中出现竞争状况的可能性。
SingletonHolder.kt
open class SingletonHolder<out T, in A>(private val constructor: (A) -> T) {
@Volatile
private var instance: T? = null
fun getInstance(arg: A): T {
return when {
instance != null -> instance!!
else -> synchronized(this) {
if (instance == null) instance = constructor(arg)
instance!!
}
}
}
}
Usage
现在在每个应该为单例的类中,在类之上编写一个companion object
。 SingletonHolder
是一个通用类,它接受目标类及其要求的参数作为通用参数。它还需要引用用于实例化的目标类的构造函数:
class MyManager private constructor(context: Context) {
fun doSomething() {
...
}
companion object : SingletonHolder<MyManager, Context>(::MyManager)
}
最后:
MyManager.getInstance(context).doSomething()
答案 3 :(得分:4)
如果您想以更简单的方式将参数传递给单例,我认为这是更好且更短的
object SingletonConfig {
private var retrofit: Retrofit? = null
private const val URL_BASE = "https://jsonplaceholder.typicode.com/"
fun Service(context: Context): Retrofit? {
if (retrofit == null) {
retrofit = Retrofit.Builder().baseUrl(URL_BASE)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit
}
}
您就可以轻松地调用它
val api = SingletonConfig.Service(this)?.create(Api::class.java)
答案 4 :(得分:2)
您可以声明Kotlin对象overloading "invoke" operator。
object TasksLocalDataSource: TasksDataSource {
private lateinit var mDbHelper: TasksDbHelper
operator fun invoke(context: Context): TasksLocalDataSource {
this.mDbHelper = TasksDbHelper(context)
return this
}
}
无论如何,我认为你应该将TasksDbHelper注入TasksLocalDataSource而不是注入Context
答案 5 :(得分:1)
如果您正在寻找具有多个参数的基本 SingletonHolder 类。我创建了 SingletonHolder 泛型类,该类支持仅创建带有一个参数、两个参数和三个参数的单例类的一个实例。
link Github of the base class here
非参数(Kotlin 的默认设置):
object AppRepository
一个参数(来自上面链接中的示例代码):
class AppRepository private constructor(private val db: Database) {
companion object : SingleArgSingletonHolder<AppRepository, Database>(::AppRepository)
}
// Use
val appRepository = AppRepository.getInstance(db)
两个参数:
class AppRepository private constructor(private val db: Database, private val apiService: ApiService) {
companion object : PairArgsSingletonHolder<AppRepository, Database, ApiService>(::AppRepository)
}
// Use
val appRepository = AppRepository.getInstance(db, apiService)
三个参数:
class AppRepository private constructor(
private val db: Database,
private val apiService: ApiService,
private val storage : Storage
) {
companion object : TripleArgsSingletonHolder<AppRepository, Database, ApiService, Storage>(::AppRepository)
}
// Use
val appRepository = AppRepository.getInstance(db, apiService, storage)
超过 3 个参数:
为了实现这种情况,我建议创建一个配置对象来传递给单例构造函数。
答案 6 :(得分:0)
如果您唯一需要的参数是应用程序Context
,则可以像Firebase SDK一样在val
的早期将其初始化为顶级ContentProvider
。
由于声明ContentProvider
有点麻烦,I made a library that provides a top level property named appCtx
对于不需要活动或其他特殊生命周期绑定上下文的所有地方。
答案 7 :(得分:0)
懒惰的解决方案
class LateInitLazy<T>(private var initializer: (() -> T)? = null) {
val lazy = lazy { checkNotNull(initializer) { "lazy not initialized" }() }
fun initOnce(factory: () -> T) {
initializer = factory
lazy.value
initializer = null
}
}
val myProxy = LateInitLazy<String>()
val myValue by myProxy.lazy
println(myValue) // error: java.lang.IllegalStateException: lazy not inited
myProxy.initOnce { "Hello World" }
println(myValue) // OK: output Hello World
myProxy.initOnce { "Never changed" } // no effect
println(myValue) // OK: output Hello World
答案 8 :(得分:0)
class CarsRepository(private val iDummyCarsDataSource: IDummyCarsDataSource) {
companion object {
private var INSTANCE: CarsRepository? = null
fun getInstance(iDummyCarsDataSource: IDummyCarsDataSource): CarsRepository {
if (INSTANCE == null) {
INSTANCE = CarsRepository(
iDummyCarsDataSource = iDummyCarsDataSource)
}
return INSTANCE as CarsRepository
}
}
}
答案 9 :(得分:0)
方法synchronized()
在通用标准库中被标记为已弃用,因此可以选择以下方法:
class MySingleton private constructor(private val param: String) {
companion object {
@Volatile
private var INSTANCE: MySingleton? = null
@Synchronized
fun getInstance(param: String): MySingleton = INSTANCE ?: MySingleton(param).also { INSTANCE = it }
}
}
答案 10 :(得分:0)
我看到了所有答案。我知道这是一个重复的答案,但是如果我们在方法声明中使用synced关键字,它将把整个方法同步到对象或类。并且 synchronized 块尚未被弃用。
您可以使用以下实用程序类来获取单例行为。
open class SingletonWithContextCreator<out T : Any>(val creator: (Context) -> T) {
@Volatile
private var instance: T? = null
fun with(context: Context): T = instance ?: synchronized(this) {
instance ?: creator(context).apply { instance = this }
}
}
您可以将上述类扩展为您要创建单例的任何类。
在您的情况下,以下是使 TasksLocalDataSource 类单例的代码。
companion object : SingletonWithContextCreator<TasksDataSource>(::TasksLocalDataSource)
答案 11 :(得分:-5)
Singletons
单身经常被用来创建它们的简单方法。 Kotlin使用对象表示法,而不是通常的静态实例,getInstance()方法和私有构造函数。 为了保持一致性,对象表示法也用于定义静态方法。
object CommonApiConfig {
private var commonApiConfig: CommonApiConfig? = null
fun getInstance(): CommonApiConfig {
if (null == commonApiConfig) {
commonApiConfig = CommonApiConfig
}
return CommonApiConfig.commonApiConfig!!
}
}