Kotlin method overloading

10,306

Solution 1

Why does it work with Kotlin to begin with... In Java having two methods like:

private static String test() {
    return "";
}

private static <T> T test() {
    return null;
}

would result in a compile time error. And for java devs this is sort of obvious, these methods would have the same type erasure. But this is rule imposed by javac, not by the JVM where this code runs. So javac does not treat two methods as having only a different return type as overloads. Well, kotlin is a different language and since it runs on the JVM (that expects valid byte-code) it allows treating methods with only the return type being different as overloads. I am yet to look at the byte code and understand how that happens; it also seems that this will work only for generic code, so may be type erasure is slightly different in case of kotlin.

Now, things should be obvious why calling such a method from java fails. Kotlin offers a neat solution for this: @JvmName("someDistinctName"). I am not entirely sure how this works under the hood either... yet, though I assume this will create a bridge method.

EDIT

@JvmName will rename the method at the byte-code level.

Solution 2

You can use @JvmName to differentiate the code when it's called from java:

@JvmName("fooString")
fun foo(): String = "foo_1"

fun <T> foo(): T = "foo_2" as T

This will allow calling the String method in Java with ClassKt.fooString(), which resolves the clash.

Solution 3

An easy solution would be writing a helper method in Kotlin and just calling that.


Another way using only Java would be getting a MethodHandle for both methods and using them:

MethodHandle MH_fooString = lookup().findStatic(ClassKt.class, "foo", methodType(String.class));
MethodHandle MH_fooT = lookup().findStatic(ClassKt.class, "foo", methodType(Object.class));

String foo = (String) MH_fooString.invokeExact();

It's not nearly as simple and requires handling exceptions though.

Share:
10,306

Related videos on Youtube

Sergei Rybalkin
Author by

Sergei Rybalkin

Software Engineer - Blue company Interests: Kotlin, Kotlin Compiler, Kotlin adoption, JVM, Java, Cloud koHttp core contributor https://github.com/rybalkinsd/kohttp Kotlin Course Lecturer https://github.com/rybalkinsd/kotlin-boot-camp Java Course Lecturer https://github.com/rybalkinsd/atom

Updated on August 28, 2022

Comments

  • Sergei Rybalkin
    Sergei Rybalkin over 1 year

    This following declaration is legal in Kotlin.

    fun foo(): String = "foo_1"
    fun <T> foo(): T = "foo_2" as T
    

    As bytecode we are getting:

    public final static foo()Ljava/lang/String;
    
    // signature <T:Ljava/lang/Object;>()TT;
    // declaration: T foo<T>()
    public final static foo()Ljava/lang/Object;
    

    It's also possible to call both of these methods from Kotlin.

    The problem comes when I'm trying to call any of them from Java:

    ClassKt.foo()
    

    Ambiguous call. Both methods match ...

    How to avoid such a problem? How to deal with such methods? What if 3-rd party kt library has same issue?

    The example above is a synthetic one.

  • Sergei Rybalkin
    Sergei Rybalkin over 5 years
    Yes, this make sense. I understand that language level and vm bytecode level has different "restrictions". But still - where is 100% interop in this case?
  • Sergei Rybalkin
    Sergei Rybalkin over 5 years
    I came to this example from the more complicated one. If we will add suspend modifier to both kotlin methods - bytecode will become much more interesting.
  • Eugene
    Eugene over 5 years
    and will be slower than a usual method invocation
  • Bubletan
    Bubletan over 5 years
    @Eugene I don't think there will be any significant performance difference if you store the MethodHandles in static final fields. It shouldn't be very hard to optimize.
  • Eugene
    Eugene over 5 years
    this is almost a reflective call, it does not matter where it is stored, and compared to a plain method call it should be significant.
  • Bubletan
    Bubletan over 5 years
    @Eugene It's nothing like a reflective call though as the method is statically known and optimizable by JIT. With enough warm-up I could only see < 1% difference between the two in a simple test I did. Either way, I doubt the performance even matters in this specific case.
  • Roland
    Roland over 5 years
    I gave an answer regarding 'How does erasure work in Kotlin' which also highlights some of this if you are interested (and some links how you could use something similar in Java). There are also other things that aren't 100% compatible, but the other way around, e.g. try to implement an annotation in Kotlin (in Java it works, in Kotlin it doesn't (yet?)).
  • Eugene
    Eugene over 5 years
    @SergeyRybalkin not entirely sure I follow you when you say where is 100% interop in this case?. what exactly do you mean?