5

So, I have some Java methods with these signatures (removed annotations and code body for the sake of simplicity):

public class JavaClass {
  public static <E extends CharSequence> E join(E... array) { ... }
  public static <E extends CharSequence> E join(CharSequence separator, E... array) { ... }
}

And I have some code in Kotlin, which calls the 'join' method:

class KtClass {
    fun test(vararg array: String) {
        JavaClass.join(*array)
    }
}

So far so good; it will spread the varargs and call the former method signature. Okie dokie!

The problem comes if, for example, I want to call the latter method signature, with a 'separator' argument:

class KtClass {
    fun test(vararg array: String) {
        JavaClass.join("<br>", *array)
    }
}

This code won't compile. The compiler can't get to decide which method to call. The error:

Error:(5, 13) Kotlin: Cannot choose among the following candidates without completing type inference: public open fun join(vararg array: String!): String! defined in JavaClass public open fun join(separator: CharSequence!, vararg array: String!): String! defined in JavaClass

I can't even name the arguments because Kotlin doesn't let argument naming for non-Kotlin functions.

EDIT: Replaced the E generic type parameter with plain String references in the Java method headers, and it worked! So I guess this to be an incompatibility of type inference with generic types or something like that?



I'm pretty sure this has to be something with the spread operator (*). But I can't pass the varargs parameter array to the join function if I don't use it.

How can I solve this without touching the Java code?

YES, I KNOW that there is Array.joinToString extension function, but this would solve only this particular case. I need to know a generic solution.

  • Please check if the unchecked casts described [here](http://stackoverflow.com/questions/40722976/how-to-deal-with-an-overload-resolution-ambiguity-of-functions-with-generics) can help you. – hotkey Jan 05 '17 at 19:41

3 Answers3

2

I don't think that's Kotlin specific. The problem is that the generic argument, E is of type CharSequence so your call becomes something like join("separator", "word1", "word2") which is, indeed, ambiguous since the first argument, of type E == CharSequence is the same as the type of the other args.

Oliver Dain
  • 9,617
  • 3
  • 35
  • 48
  • But this ambiguity doesn't happen with the Java compiler... I can call `JavaClass.join("some separator", someArray)` and it will select the correct method. – João Vitor Verona Biazibetti Jan 05 '17 at 19:45
  • And I can see why that happens because of the spread operator... but, AFAIK, it is the only way to pass a Kotlin varargs parameter to a Java method receiving varargs – João Vitor Verona Biazibetti Jan 05 '17 at 19:55
  • I **think** what's happening here is that the spread operator de-sugars to `join("a", "b", "c")` rather than `join("a", arrayOfOtherArgs)` so it's not the same as the Java call. Doesn't this work `join("
    ", array)`? I don't have the same libs available but to test I did this and it worked: ` val varargs = arrayOf("a", "b", "c'")` and then `String.format("my format string: %s", varargs)`. In other words, as far as i can tell you can pass a Kotlin array to a varargs method without the spread operator.
    – Oliver Dain Jan 05 '17 at 20:03
  • AFAIK, yes, a Kotlin array can be passed to a varargs method without the spread operator, but a Kotlin varargs method parameter can't, and here's where the problem lies... as you said, it is "de-sugaring" and since the separator is of the same type as the array elements, it does not know which method to call – João Vitor Verona Biazibetti Jan 05 '17 at 20:14
  • 1
    Interesting. I see what you mean. Sorry, don't have an answer. BTW, note that my `String.format` example was flawed as `String.format`'s varargs are of type `Object` so it's just passing that whole array as a single argument. – Oliver Dain Jan 05 '17 at 21:32
1

It looks like you will need to create a helper class in Java to bridge the interop issue. e.g.:

public class JavaClassInterop {
    public static <E extends CharSequence> E joinSeparatedBy(CharSequence separator,
                                                             E... array) {
        return JavaClass.join(separator, array);
    }
}

Then you can call both:

import JavaClass.join
import JavaClassInterop.joinSeparatedBy

fun main(args: Array<String>) {
    join(*args)
    joinSeparatedBy("<br>", *args)
}
mfulton26
  • 29,956
  • 6
  • 64
  • 88
1

tldr

Probably you work incorrectly with nullable types

I was struggling with this error for a while, but finally came up with a solution. I had this at first

user.items.apply {
        removeAll(otherItems)
        removeAll(otherItems2)
    }

The items collection is a MutableSet? so it is nullable, and otherItems collections are nullable too. So after adding ? before the apply, and passing non nullable collection to the removeAll function, the errors disappeared.

user.items?.apply {
        removeAll(otherItems.orEmpty())
        removeAll(otherItems2.orEmpty())
    }
Andras Kloczl
  • 8,415
  • 2
  • 21
  • 23