In Kotlin properties with pre-set custom get/set behavior are implemented using delegated properties. According to the doc, a delegate for a property is just a class with getValue and setValue methods
But when I looked inside the implementation of lazy, I only found value property. So I tried to implement a delegate this way myself. It failed to compile, because getValue and setValue were not implemented explicitly. How does the official implementation of lazy work then?
getValue is declared as an extension function (yes that works too!) on all Lazy<T>s:
Source:
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
As you can see, it returns value, which is exactly what you would expect.
There is no setValue, and that is fine. That just means you can't use lazy { ... } as a property delegate for a var, and you indeed cannot:
var foo by lazy { 10 } // compiler error
After all, this doesn't make much sense anyway.
Related
I have a Kotlin function with this signature:
fun registerDisposer(obj: Any, disposer: Closeable)
What the function does is attach disposer to a phantom reference and arrange it to be closed when obj is garbage-collected (i.e. when the phantom reference object is enqueued). The class of obj is supposed to call it something like this:
class Holder(private val res1: Closeable, private val res2: Closeable) {
init {
registerDisposer(this, object: Closeable {
private val res1 = this#Holder.res1
private val res2 = this#Holder.res2
override fun close() {
res1.close()
res2.close()
}
})
}
}
(Let’s ignore whether this is a good idea to rely on this with general Closeables; the actual resource in question is a pointer managed by native/JNI code – I am trying to follow Hans Boehm’s advice. But all of this is not particularly relevant for this question.)
I am worried that this design makes it too easy to inadvertently pass an object that captures this from the outer scope, creating a reference loop and preventing the object from being garbage-collected at all:
registerDisposer(this, Closeable {
this.res1.close()
this.res2.close()
})
Is there an annotation I can add to the disposer parameter that will trigger a warning in this situation?
As of this writing, the answer seems to be: probably not.
It turns out a registerDisposer function already exists as the register method of java.lang.ref.Cleaner, and it has no such annotation.
In Android, there is a similar annotation for android.os.AsyncTask, but that simply warns at any anonymous object having AsyncTask as its base class, whether it captures this or not. (This makes sense in Java, where anonymous classes always capture this, but not in Kotlin.)
I wanted to try something new with delegated properties in my Kotlin code. What I found in docs is that for custom delegated properties I need to create a class with obligatory methods - getValue and optionally setValue, which are part of interfaces mentioned in the docs:
You can create delegates as anonymous objects without creating new classes using the interfaces ReadOnlyProperty and ReadWriteProperty from the Kotlin standard library
I started digging through Kotlin's built-in delegate functions. I looked into implementation of lazy function which looks like following:
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
And here's where my question stands: Why does it actually works? Lazy interface has only value property and some method determining its initialisation of the value. SynchronizedLazyImpl doesn't have much more than this. None of them have getValue or setValue methods, so why Kotlin doesn't complain and compiles successfully?
Looking at the source for Lazy here, you can see the following on line 37:
/**
* An extension to delegate a read-only property of type [T] to an instance of [Lazy].
*
* This extension allows to use instances of Lazy for property delegation:
* `val property: String by lazy { initializer }`
*/
#kotlin.internal.InlineOnly
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
Essentially, there's a getValue extension function for Lazy instances that just returns the value property. SynchronizedLazyImpl just defines the value property, and getValue is automatically provided.
Below is the code snippet that I came across in gradle's documentation
https://docs.gradle.org/current/userguide/tutorial_using_tasks.html
val hello by tasks.registering {
doLast {
println("Hello Earth")
}
}
hello {
doFirst {
println("Hello Venus")
}
}
In the above, hello is a TaskProvider type which provides task definition/action. The second call to hello is to extend the behavior of the task.
This delegate use looks slightly confusing to me. Following are the questions which are bugging me:
1) On inspecting the decompiled byte-code, I see tasks.registering returns RegisteringDomainObjectDelegateProviderWithAction object which should be used as the delegate and hence should provide getValue() and setValue() methods for delegate to work but as I saw, methods are not provided. Instead the class RegisteringDomainObjectDelegateProviderWithAction has a delegateProvider property of type tasks which is supposed to provide the delegate. Can any one help me understand, how delegation works here?
2) The second call is supposed to add behavior to the hello task. Since hello is a property, how are we able to pass a lambda/behavior to it? What am I missing?
I have already seen kotlin documentation which provides good explanation of delegates but doesn't aid in understanding the above case https://kotlinlang.org/docs/reference/delegated-properties.html
I would appreciate a detailed explanation as I am new to Kotlin.
Regarding the delegate use:
The delegation works via an extension operator method provideDelegate defined on RegisteringDomainObjectDelegateProviderWithAction:
operator fun RegisteringDomainObjectDelegateProviderWithAction<out TaskContainer, Task>.provideDelegate(
receiver: Any?,
property: KProperty<*>
) = ExistingDomainObjectDelegate.of(
delegateProvider.register(property.name, action)
)
The provideDelegate operator allows for more complex logic in delegate creation. As per the docs:
By defining the provideDelegate operator you can extend the logic of creating the object to which the property implementation is delegated. If the object used on the right hand side of by defines provideDelegate as a member or extension function, that function will be called to create the property delegate instance.
Regarding the "passing a lambda to a property":
This is implemented via overloading of the invoke operator as an extension function on the TaskProvider class:
operator fun <T> NamedDomainObjectProvider<T>.invoke(action: T.() -> Unit) =
configure(action)
Basically, the call hello { /* your lambda */ } is desugared into hello.invoke { /* your lambda */ }.
This SO post outlines how to test if a lateinit var has been initialized. However, in the example, the lateinit var is conveniently located within the same class.
How do you do the same thing from outside the class? This is the situation I have:
Foo.kt
class Foo {
lateinit var foo: String
}
Bar.kt
class Bar {
fun doSomething() {
val foo = Foo().foo
if (::foo.isInitialized) { // Unsupported [reference to variables aren't supported yet]
Log.i("TAG", "do something")
}
}
}
What's the workaround for this?
If this was going to work, you'd need to do
val foo = Foo()
if (foo::foo.isInitialized)
//...
The way you're doing it, you're trying to get a property reference of your local variable, which isn't a property. That's why the error says "reference to variables aren't supported yet" rather than "backing field not accessible at this point". Also, you'd be accessing the getter of the lateinit property when assigning the local variable, so it would fail if it weren't initialized yet.
But it doesn't work because of compiler limitations. You could simply add a getter
val fooReady: Boolean get() = ::foo.isInitialized
But I would say the design has very poor encapsulation if outside classes need to check whether a particular public property is initialized yet. In my opinion, any use of isInitialized is a code smell to begin with. If you need to guard calls to the getter with isInitialized, you might as well make the property nullable instead. Then you can use the familiar idioms of null checks instead of resorting to reflection, and it will work in a familiar way even for external classes that access it.
If object of another class has to make a decision based on whether or not the property is initialised, then having this property initialised - or answering whether or not it has already been initialised - is a public business capacity of your object and therefore I would recommend you to simply make it a part of your public API via public fun isFooInitialised(): Boolean function that utilises the fact that the object itself can inspect the state of its lateinit properties.
kotlin file
interface Test {
fun test(message: String, delay: Int =100)
}
class A: Test
{
override fun test(message: String, delay: Int) {
}
}
I find i can't use #JvmOverloads in interface nor class.
if i add a #JvmOverloads in interface,the error is #JvmOverloads annotation cannot be used on interface method,if i add #JvmOverloads in class,the error is platform declaration clash....
However, I seem able to use defaults paramters in kotlin files,like this.
var a=A()
a.test("1234")
But when I use it in a java file, it seems that the method is not overloaded。
A a=new A();
a.test("123");//Compile error
The following version without interface can work
class A
{
#JvmOverloads
fun test(message: String, delay: Int=100) {
}
}
Then I can use it normally in java file
A a=new A();
a.test("123");
But how to maintain the same functionality after add the interface?
This is not a bug. #JvmOverloads annotation simply does not work with abstract methods.
From Kotlin docs:
Normally, if you write a Kotlin function with default parameter values, it will be visible in Java only as a full signature, with all parameters present. If you wish to expose multiple overloads to Java callers, you can use the #JvmOverloads annotation.
The annotation also works for constructors, static methods etc. It can't be used on abstract methods, including methods defined in interfaces.
source: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#overloads-generation
Why?
Because as You can learn from the doc I mentioned, #JvmOverloads instructs compiler to generate bunch of Java overloaded methods, omitting each of the parameters one by one, starting from the last one.
As far as I understand, each overloaded method calls internally method with one more parameter, and this additional parameter has default value. Edit: see comment by #hotkey here
This won't work with abstract methods, because they don't have any body.
Also new Java interface would have more methods, and its implementations would have to implement all of them. Kotlin interface had only one method.
To get to the same result you can make a LegacySupport class in Kotlin that will actually call the function with the default parameter and then you can expose only the return of the function to the java class from this class.