'Can a Kotlin interface cache a value?
I really like using default implementations for interfaces in Kotlin, especially for common patterns like Observable. Here's my interface,
interface Observable<T>{
// How do I cache this?
val observers: MutableList<Observer<T>>
get() = LinkedList<>()
fun addObserver(o:Observer<T>){
observers.add(o)
}
fun removeObserver(o:Observer<T>){
observers.remove(o)
}
fun notifyObservers(u:T){
for (o in observers){
o.update(u)
}
}
}
The interface refers to a list of observers, but the get() call is returning a new LinkedList() each time. How can I cache the value of observers so that it's only created once? I've tried using kotlin-lazy, but either can't get the syntax right, or it's not meant for interfaces. My IDE complains "Delegated properties are not allowed in interfaces."
UPDATE
Based on Yoav's answer, I've change my interface to
interface Observable<T>{
val observers: MutableList<Observer<T>>
}
And then in the implementing class,
class MyObservable : Observable<String>
private val _observers = LinkedList<Observer<String>>()
override val observers: MutableList<Observer<String>>
get() = _observers
Any tips to make this more succinct?
Solution 1:[1]
According to Kotlin docs:
Interfaces in Kotlin are very similar to Java 8. They can contain declarations of abstract methods, as well as method implementations. What makes them different from abstract classes is that interfaces cannot store state.
Interface can't hold any state as they are fully abstract. Perhaps you should use an abstract class in order to cache the values?
See this question for more information about the reason for interfaces being stateless.
An interface is a contract specifying what its implementer promises to be able to do. It does not need to specify state because state is an implementation detail and only serves to constrain implementers in how this contract is fulfilled. If you want to specify state you might want to rethink you use of interfaces and look at abstract base classes instead.
Solution 2:[2]
While you cannot create stateful variables on an interface directly, you can achieve the same result by defining a companion object containing a cache and do a dynamic lookup on this. Using your example:
interface Observable<T>{
// How do I cache this?
val observers: MutableList<Observer<T>>
get() = cache.computeIfAbsent(this) { mutableListOf<Observer<T>>() } as MutableList<Observer<T>>
fun addObserver(o:Observer<T>){
observers.add(o)
}
fun removeObserver(o:Observer<T>){
observers.remove(o)
}
fun notifyObservers(u:T){
for (o in observers){
o.update(u)
}
}
companion object {
val cache = WeakHashMap<Observable<*>, MutableList<*>>()
}
}
If you want a more efficient cache, you can use a library like Caffeine.
Solution 3:[3]
After thinking about this some more, I discovered a nice pattern. First I tried the direct approach:
interface A {
val prop = compute() // Nope: Property initializers are not allowed in interfaces
/*...*/
}
As you mentioned, it can be done using a custom accessor but must be recomputed each time:
interface A {
val prop get() = compute() // OK, but recomputed each time someProperty is read
/*...*/
}
Then I thought, perhaps we can use a delegated property instead? But this is not allowed either:
interface A {
val prop by { compute() } // Not allowed: Delegated properties are not allowed in interfaces
/*...*/
}
Finally, I found you can achieve the same result by creating a property extension with a delegate:
interface A { /*...*/ }
val A.prop by lazy { compute() }
From a usage standpoint, you just import the extension and can access a.prop as usual.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Yoav Sternberg |
| Solution 2 | breandan |
| Solution 3 | breandan |
