I am trying to write a class that implements two i...
# getting-started
r
I am trying to write a class that implements two interfaces that have members with different Kotlin signatures but the same JVM signature. It is failing with
Accidental override: The following declarations have the same JVM signature (size()I): fun size(): Int
Code in thread.
Java interface:
Copy code
public interface TreeNode {
  int size();
}
Kotlin interface:
Copy code
interface Map<K, out V> {
  val size: Int
}
My code:
Copy code
class Foo(
  private val theSize: Int,
) : TreeNode, Map<String, Any?> {
  override val size: Int = theSize
  override fun size(): Int = theSize
}
I don't own either of the interfaces I'm trying to implement. Am I just stuffed?
e
https://pl.kotl.in/fV_j4Qo_A a simple setup seems to work fine…
I wonder if it's a problem with Java interop
r
I suspect so
r
I can’t reproduce the error, oddly - what platform and what kotlin version?
v
Yeah, seems to also work with Java interop: https://pl.kotl.in/rgqCwDDS3
But as usual, you can probably use
@JvmName
to fix the problem? If I for example add
@get:JvmName("size")
to
override val size: Int = theSize
, I get also a declaration clash. Not accidental as it was done explicitly, but I'd guess you can use it to avoid the accidental clash.
r
It seems to be particularly implementing
kotlin.collections.Map
and a Java interface... if I create my own Kotlin interface it works ok.
Copy code
package example;

public interface JavaInterface {
    int size();
}
Copy code
package example

class Implementation : Map<String, Any>, JavaInterface {
  override val entries: Set<Map.Entry<String, Any>> get() = TODO("Not yet implemented")
  override val keys: Set<String> get() = TODO("Not yet implemented")
  override val size: Int get() = TODO("Not yet implemented")
  override val values: Collection<Any> get() = TODO("Not yet implemented")
  override fun isEmpty(): Boolean = TODO("Not yet implemented")
  override fun get(key: String): Any? = TODO("Not yet implemented")
  override fun containsValue(value: Any): Boolean = TODO("Not yet implemented")
  override fun containsKey(key: String): Boolean = TODO("Not yet implemented")
  override fun size(): Int = TODO("Not yet implemented")
}
Copy code
e: file:///Users/robert/dev/myproject/src/main/java/example/Implementation.kt:12:3 Accidental override: The following declarations have the same JVM signature (size()I):
    fun size(): Int defined in example.Implementation
    fun size(): Int defined in example.Implementation
But as usual, you can probably use @JvmName to fix the problem?
I don't really understand how - since I'm implementing interfaces in compiled, 3rd party code, surely I have to have the correct JVM name?
Java 17, Kotlin 2.0.10
I've tried scattering
@JvmName
around, haven't found a form that fixes it.
v
Ah, you can even reproduce completely without Java interop: https://pl.kotl.in/BmZWfMU8T
There you could indeed use
@JvmName("getTheSize")
on
fun size()
to fix the problem. But when the methods implements a Java interface, that does not work of course and you get "not applicable to this declaration"
An just having the
val size
without implementing
Map
also works. So somehow if the
val size
implements the
Map.size
, a second
size()
method is added, which cannot implement the Java
size()
method though. 🙈
This is what the decompiler says if you only implement the
Map
interface:
Copy code
// $FF: bridge method
public final int size() {
   return this.getSize();
}
Aaah,
java.util.Map
has an
int size()
method. So for
kotlin.collections.Map
being compiled to
java.util.Map
, this bridge method is compiled in. And that then conflicts with the
size
method added additionally.
d
I think I ran into this exact problem before, and concluded that I couldn't have one class that implemented both interfaces. The work around was to have a class that exposed both interfaces via inner impls:
Copy code
class MyClass {
   private inner class MapImpl : Map<X,Y> {
     ...
   }
   private inner class FooImpl : Foo {
   }

   val asMap: Map<X,Y> get() = MapImpl()
   val asFoo: Foo = get() FooImpl()
}
This is actually somewhat better as a design principal anyway.