可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am trying to convert an Android app from Java to Kotlin. There are a few singletons in the app. I used a companion object for the singletons without constructor parameters. There is another singleton that takes a constructor parameter.
Java code:
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;
}
}
My solution in 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
}
}
}
Am I missing anything? Thread safety? Laziness ?
There were a few similar questions but I don't like the answers :)
回答1:
Here's a neat alternative from Google's architecture components sample code, which uses the also
function:
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()
}
}
回答2:
I am not entirely sure why would you need such code, but here is my best shot at it:
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!!
}
}
}
This is similar to what you wrote, and has the same API.
A few notes:
Do not use lateinit
here. It has a different purpose, and a nullable variable is ideal here.
What does checkNotNull(context)
do? context
is never null here, this is guarantied by Kotlin. All checks and asserts are already implemented by the compiler.
UPDATE:
If all you need is a lazily initialised instance of class TasksLocalDataSource
, then just use a bunch of lazy properties (inside an object or on the package level):
val context = ....
val dataSource by lazy {
TasksLocalDataSource(context)
}
回答3:
if you want to pass a parameter to the singleton in an easier way I think this is better and shorter
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
}
}
and you call it in this easy way
val api = SingletonConfig.Service(this)?.create(Api::class.java)
回答4:
You can declare a Kotlin object, overloading "invoke" operator.
object TasksLocalDataSource: TasksDataSource {
private lateinit var mDbHelper: TasksDbHelper
operator fun invoke(context: Context): TasksLocalDataSource {
this.mDbHelper = TasksDbHelper(context)
return this
}
}
Anyway I think that you should inject TasksDbHelper to TasksLocalDataSource instead of inject Context
回答5:
• Thread-Safe Solution
- Maximum Simplicity in usage
You can create a class which implements the logic of singleton and holds the singleton instance. It instantiates the instance using double check locking in a synchronized block to eliminate possibility of race condition in multi-thread environments.
Singleton.kt
open class Singleton<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
Simply in target class that should be singleton, write a companion object
which extends above class. Singleton
class is generic and accepts the types of target class and its needed parameter as generic params. It also needs a reference to the constructor of target class that is used for instantiating:
class TasksLocalDataSource private constructor(context: Context) : TasksDataSource {
...
companion object : Singleton<TasksLocalDataSource, Context>(::TasksLocalDataSource)
}
回答6:
If the only parameter you need is the application Context
, then you can initialize it to a top level val
, early in a ContentProvider
, like the Firebase SDK does.
Since declaring a ContentProvider
is a bit cumbersome, I made a library that provides a top level property named appCtx
for all places where you don't need an Activity or other special lifecycle bound context.
回答7:
solution with lazy
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:
Singletons
Singletons are used often enough for a simpler way of creating them to exist. Instead of the usual static instance, getInstance() method and a private constructor, Kotlin uses the object notation.
For consistency, object notation is also used to define static methods.
object CommonApiConfig {
private var commonApiConfig: CommonApiConfig? = null
fun getInstance(): CommonApiConfig {
if (null == commonApiConfig) {
commonApiConfig = CommonApiConfig
}
return CommonApiConfig.commonApiConfig!!
}
}