【问题标题】:Kotlin Any type to Java Object typeKotlin 任何类型到 Java 对象类型
【发布时间】:2021-06-25 15:38:08
【问题描述】:

我遇到了一个以前从未遇到过的奇怪情况。在我的公司,我们有很多项目,其中大多数使用 Java,但使用 Kotlin 的项目越来越多。所以我们有这个用 Java 编写的现有应用程序,并且我们正在添加一个用 Kotlin 编写的库。

Kotlin 代码有一个接受 lambda 表达式的函数参数。该 lambda 具有这种类型,“params”参数是一个值为“Any”的映射:

(params: Map<String,Any>) -> List<Item>

在 Java 代码中,我们通过获取“参数”并将它们传递给 Java 函数来实现这个 lambda:

params -> receiverFunction(params)

但是,Java 函数接受值为“Object”的 map 参数:

public List<Item> receiverFunction(final Map<String,Object> params)

我现在收到一个编译错误,其中 Java lambda 中的“params”被视为具有类型 Map&lt;String,capture of ?&gt;,无法传递给 Map&lt;String,Object&gt;

实现这一级别的 Java/Kotlin 互操作性的推荐方法是什么?

【问题讨论】:

    标签: java kotlin


    【解决方案1】:

    您在 Kotlin 端定义的 Map 是只读 Map,因此值类型标记为 out。 Kotlin 的out Any 大致相当于Java 的? extends Object。但是由于您的receiverFunction 将值类型定义为更严格的Object,因此它们不匹配。

    方案一:将Java值类型改为?,相当于? extends object

    解决方案 2:将 Kotlin 参数从 Map 更改为 MutableMap。

    如果receiverFunction 不需要改变地图(它可能不需要),则首选解决方案 1。为了更好地封装,Java 方法可能一开始就应该这样编写,即使不必记住 Kotlin。

    【讨论】:

    • ? extends Object 仅相当于 ?。另外我认为您的意思是值类型,而不是键类型
    • 虽然答案很好,但有正确的解释和替代解决方案:)
    • 所以我将采用 MutableMap 方法,因为在 Kotlin 方面我向现有 Map 添加了一个额外的值,并且考虑到 Kotlin 的只读收集性能很差,而且这是在一个操作中在速度是基本要求的情况下,我正在使用可变性。不过谢谢。
    【解决方案2】:

    简答:Kotlin 的 Map&lt;String, Any&gt; 的正确 Java 等效项是 Map&lt;String, ?&gt;。所以在java方面,你应该让你的函数使用通配符?而不是Object作为地图的值:

    public List<Item> receiverFunction(final Map<String, ?> params) {
        
    }
    

    这会阻止你在这个Map 中插入东西,但这只是公平的,因为 Kotlin 无论如何都不提供MutableMap,所以你不应该被允许在里面放东西。

    更多背景

    Kotlin 有 declaration-site variance。这意味着您可以在类或接口的声明站点对泛型声明约束,就好像在使用该类型的任何地方都应用了约束。

    Kotlin 的 Map 在其声明中使用了这个:interface Map&lt;K, out V&gt;。因此,当从 Kotlin 接收到 Map&lt;String, Any&gt; 时,您得到的实际类型是 Map&lt;String, out Any&gt;,在 Java 中写为 Map&lt;String, ? extends Object&gt;,或者简称为 Map&lt;String, ?&gt;

    Java 的Map&lt;String, Object&gt; 表示一个可变映射,您可以在其中插入Object 类型的值(基本上是任何值)。另一方面,Map&lt;String, ?&gt; 表示您不知道值的确切类型的 Map。 Java 编译器不允许您在其中插入任何内容,因为它不知道是否允许。例如,使用params.put("key", 42) 是非法的,因为地图很可能是Map&lt;String, String&gt;,并且您会用不正确的类型污染地图(这实际上是使用像Map 这样的原始类型而不使用@987654342 的问题@)。

    然而,Java 允许您从该映射中获取值,因为将任何值分配给类型为 Object 的变量是安全的(这就是 Kotlin 术语 out 的来源)。这使得用无约束类型的值表示只读映射变得很方便。

    【讨论】:

      【解决方案3】:

      由于您的应用程序是用 Java 编写的,并且库似乎是第三方的东西,您可以通过使接收器函数中的参数通用来解决这个问题。由于它接受Map&lt;String, Object&gt;,因此您始终可以将具体的Object 替换为泛型类型,因为Java 中的每种类型都隐式扩展Object,因此接受Map&lt;String, Object&gt; 或@987654325 的方法之间没有区别@ 除了传入的 T 实际上可以是任何类型之外,仍然会暴露从 Object 继承的方法。这些是我为重现您的问题并演示解决方案而编写的模拟:

      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));
        }
      }
      

      【讨论】:

      • 这里不需要泛型,因为T 没有任何用处(receiverFunction 内部和外部都没有)。只需使用通配符 ? 就足够了。
      • @Joffrey 同意,完全忘记 Java 甚至还有通配符
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-30
      • 1970-01-01
      • 1970-01-01
      • 2010-11-07
      • 1970-01-01
      相关资源
      最近更新 更多