Kotlin Docs → Calling Kotlin from Java → Default methods in interfaces states
Note that if an interface with
@JvmDefault
methods is used as a delegate, the default method implementations are called even if the actual delegate type provides its own implementations.
and gives the following example:
interface Producer {
@JvmDefault fun produce() {
println("interface method")
}
}
class ProducerImpl: Producer {
override fun produce() {
println("class method")
}
}
class DelegatedProducer(val p: Producer): Producer by p {
}
fun main() {
val prod = ProducerImpl()
DelegatedProducer(prod).produce() // prints "interface method"
}
I find this behavior very surprising and undesirable, and not using @JvmDefault
is no good option if one wants good support of Java 8+ client code. (I would even argue that @JvmDefault
should be the default when targetting JDK 8+.)
I see two issues with this behavior:
A) The main issue is that I think that @Jvm*
annotations should not change semantics, particularly not inside the Kotlin world.
There seems to be no need for this issue. I can easily hand-craft a delegating implementation that is immune to @JvmDefault
, so the compiler should be able to do this, too:
interface Producer {
fun nonDefault() {
println("interface method")
}
@JvmDefault
fun jvmDefault() {
println("interface method")
}
}
class ProducerImpl : Producer {
override fun nonDefault() {
println("class method")
}
override fun jvmDefault() {
println("class method")
}
}
class ManuallyDelegatingProducer(val p: Producer) : Producer {
override fun nonDefault() {
p.nonDefault()
}
override fun jvmDefault() {
p.jvmDefault()
}
}
val p = ProducerImpl()
ManuallyDelegatingProducer(p).nonDefault() // prints "class method"
ManuallyDelegatingProducer(p).jvmDefault() // prints "class method"
B) The second issue is about the delegation behavior itself, not the difference in behavior with or without @JvmDefault
annotation. It is merely theoretical, since given a non-experimental and thus unchangeable behavior without the annotation and my case for unchanged semantics with the annotation, retaining the established behavior necessarily follows. But I include it for completeness of discussion.
One might argue (though I wouldn’t) that when an interface method is a default method, an implementing class does not have to delegate, and so calling the default implementation is correct. But even then, there would be no need for a difference with or without annotation, since delegation could be implemented like this:
class InheritingProducer(val p: Producer) : Producer
class RedundantlyInheritingProducer(val p: Producer) : Producer {
override fun nonDefault() {
super.nonDefault()
}
override fun jvmDefault() {
super.jvmDefault()
}
}
val p = ProducerImpl()
InheritingProducer(p).nonDefault() // prints "interface method"
InheritingProducer(p).jvmDefault() // prints "interface method"
RedundantlyInheritingProducer(p).nonDefault() // prints "interface method"
RedundantlyInheritingProducer(p).jvmDefault() // prints "interface method"
What is the rationale behind the current behavior? Is there any chance of changing this behavior before @JvmDefault
becomes non-experimental?