Reputation: 6263
I have an odd situation I've never run into before. At my company we have a mix of projects with most in Java but a growing number in Kotlin. So we have this existing application written in Java, and we are adding a library written in Kotlin.
The Kotlin code has a function argument that takes in a lambda expression. That lambda has this type, with the "params" argument being a map with values of "Any":
(params: Map<String,Any>) -> List<Item>
In the Java code, we implement this lambda by taking "params" and passing them to a Java function:
params -> receiverFunction(params)
However, the Java function accepts a map argument with values of "Object":
public List<Item> receiverFunction(final Map<String,Object> params)
I now get a compile error, where "params" in the Java lambda is seen as having type Map<String,capture of ?>
, which cannot be passed to Map<String,Object>
.
What is the recommended way to achieve this level of Java/Kotlin interoperability?
Upvotes: 3
Views: 2782
Reputation: 37700
Short answer: the correct Java equivalent of Kotlin's Map<String, Any>
is Map<String, ?>
. So on java side, you should make your function take a wildcard ?
instead of Object
for the map's values:
public List<Item> receiverFunction(final Map<String, ?> params) {
}
This prevents you from inserting things in this Map
, but it is only fair because Kotlin doesn't provide a MutableMap
anyway, so you shouldn't be allowed to put stuff in it.
Kotlin has declaration-site variance. This means you can declare constraints on your generics at the declaration site of the class or interface, and it's as if the constraint was applied everywhere the type is used.
Kotlin's Map
uses this in its declaration: interface Map<K, out V>
. Therefore, when receiving a Map<String, Any>
from Kotlin, the actual type you're getting is Map<String, out Any>
, which in Java is written Map<String, ? extends Object>
, or in short Map<String, ?>
.
Java's Map<String, Object>
represents a mutable map in which you can insert values of type Object
(basically any value). A Map<String, ?>
on the other hand represents a Map
for which you don't know the exact type of the values. The Java compiler won't let you insert anything in it, because it cannot know if it's allowed. For instance it's illegal to do params.put("key", 42)
because the map could very well be a Map<String, String>
, and you would be polluting the map with an improper type (this is actually the problem with using raw types like just Map
without <>
).
However, Java lets you get values out of this map, because it's safe to assign any value to a variable of type Object
(that's where the Kotlin terminology out
comes from). This makes it convenient to express a read-only map with values of unconstrained type.
Upvotes: 3
Reputation: 93591
The Map you defined on the Kotlin side was a read-only Map, so the value types are marked out
. Kotlin's out Any
is roughly equivalent to Java's ? extends Object
. But since your receiverFunction
defines the value type as the more strict Object
, they are not a match.
Solution 1: Change Java value type to ?
, which is equivalent to ? extends object
.
Solution 2: Change the Kotlin parameter from Map to MutableMap.
If receiverFunction
doesn't need to mutate the map (it probably doesn't), Solution 1 is much preferred. And the Java method should probably have been written that way in the first place for better encapsulation, even without having to keep Kotlin in mind.
Upvotes: 5
Reputation: 743
As your application is the one written in Java and the library seems to be a third-party thing, you could solve this by making the params in the receiver function generic. Since it accepts a Map<String, Object>
, you can always replace the concrete Object
with a generic type, as every type in Java implicitly extends Object
, so there is no difference between the method accepting a Map<String, Object>
or a Map<String, T>
except that the T passed in can be of literally any type will still exposing methods inherited from Object
. These are the mocks I've written to reproduce your issue and demonstrate the solution:
Test.kt
class Item
fun theFunction(params: (Map<String,Any>) -> List<Item>) {}
Main.java
import java.util.List;
import java.util.Map;
public class Main {
public static <T> List<Item> receiverFunction(Map<String, T> params) {
return List.of();
}
public static void main(String[] args) {
TestKt.theFunction(params -> receiverFunction(params));
}
}
Upvotes: 0