当前位置: 首页 > 知识库问答 >
问题:

为什么一些Java函数能够更改不可变的Kotlin对象?

翟弘
2023-03-14

我很惊讶地看到这个程序甚至可以编译,但结果更让我惊讶:

import java.util.Collections.swap

fun main(args: Array<String>) 
{
    val immutableList = List(2) { it } // contents are [0, 1] 
    swap(immutableList, 0, 1)
    println(immutableList) // prints [1, 0]
}

交换功能在库中实现为:

public static void swap(List<?> list, int i, int j) {
        list.set(i, list.set(j, list.get(i)));
    }

其中List是一个可变的Java列表,而不是不可变的静态编程语言列表。所以我认为其他Java函数也可以工作。例如:

reverse(immutableList)

工作,但其他函数,例如填充函数,甚至不编译:

fill(immutableList, 3)

生成以下错误消息:

类型推断失败:趣味填充(p0:可变列表!,p1:T!):无法将单位应用于(List,Int)类型不匹配:推断的类型为List,但为MutableList!应为

然而,填充函数的List参数具有与反向不同的类型边界:

public static <T> void fill(List<? super T> list, T obj)

public static void reverse(List<?> list)

因此,似乎没有类型边界,Java函数可以随心所欲。

有人能解释一下这是怎么可能的吗?这是设计好的,还是只是互操作的限制?

共有2个答案

凌景辉
2023-03-14

需要注意的是,您的swap版本不可编译。它是反编译实际库代码的结果,该库代码将列表强制转换为原始类型,以便它可以使用它进行读写,而不用考虑通配符。

交换方法使用通配符类型,因此互操作中的接口映射之间没有限制。静态编程语言将List和MutableList映射到Java的List,因此不幸的是,它允许这样的误用。我想有必要避免使用Java库。

您的fill方法使用逆变(消费者)有界类型

傅砚
2023-03-14

不编译的函数与kotlin无关,而是与java如何处理协变和逆变集合有关。

来自Java泛型和集合

您不能将任何内容放入使用扩展通配符声明的类型中——除了值null,它属于每个引用类型

例如,如果您有以下代码。

List<? extends Number> numbers = new ArrayList<Integer>();

那么你可以这样做

 numbers.add(null);

但是如果你想做以下任何一件事

numbers.set(0, Integer.valueOf(10)); // case 1
numbers.set(1, numbers.get(0)); // case 2

在第1种情况下,编译器不会让您这样做,因为编译器无法知道列表的确切类型,在这种情况下是整数列表,在其他一些情况下,它可能会根据某些运行时条件分配双打列表。

在案例2中,编译器无法确认要插入列表中的对象的类型,并产生错误。您可以使用通配符捕获解决案例2中的问题。

第二个是您关于在java方法中修改kotlin列表的问题。

我们必须明白kotlin集合类是相同的旧java集合类,kotlin没有自己的集合实现。kotlin所做的是将java集合接口分为可变和只读。如果你在kotlin中创建一个数组列表,然后检查它的类,你会得到相同的java.util.ArrayList

现在鉴于java没有kotlin那样的可变列表和只读列表的概念,因此无法阻止java方法更改您的只读kotlin列表。因为对于java代码来说,这只是一个列表实现。

以下是《静态编程语言在行动》一书的相关文本

当需要调用Java方法并将集合作为参数传递时,可以直接这样做,无需任何额外步骤。例如,如果您有一个接受Java的Java方法。util。集合作为参数,可以将任何集合或可变集合值作为参数传递给该参数

这对于集合的易变性具有重要影响。因为Java不区分只读集合和可变集合,所以Java代码可以修改集合,即使它在Kotlin端声明为只读集合。Kotlin编译器无法完全分析Java代码中对集合所做的操作,因此Kotlin无法拒绝将只读集合传递给修改它的Java代码的调用。

 类似资料:
  • 问题内容: 我正在尝试了解Python的可变范围方法。在此示例中,为什么f()能够更改的值(x如在其中看到的)main(),但不能更改的值n? 输出: 问题答案: 一些答案在函数调用的上下文中包含单词“ copy”。我感到困惑。 Python不复制对象的函数调用中传递过。 功能参数是名称。调用函数时,Python会将这些参数绑定到您传递的任何对象上(通过调用方作用域中的名称)。 对象可以是可变的(

  • 问题内容: 我有一类具有各种成员变量的类。有一个构造函数,有getter方法,但没有setter方法。实际上,该对象应该是不变的。 现在我注意到了以下几点:当我使用getter方法获得变量列表时,可以添加新值,依此类推- 可以更改。下次调用此变量时,将返回更改的内容。怎么会这样?我没有再设置它,我只是在做它!使用这种行为是不可能的。那么这里有什么区别? 问题答案: 仅仅因为 对 列表 的引用 是不

  • 问题内容: 当我看到以下代码无法按预期工作时,我有些困惑。 我认为Java总是通过引用将变量传递给函数。因此,为什么函数不能重新分配变量? 该程序输出。 问题答案: 对对象的引用是通过Java中的 值 传递的,因此在方法内部分配给局部变量不会更改原始变量。仅局部变量指向新字符串。稍微了解一下ASCII艺术可能会更容易理解。 最初,您有: 首次输入方法setNotNull时,您 将在中 获得null

  • 我正在迭代Java 7循环和Java 8 循环中的列表。Java 8 循环希望其中的变量不会更改。例如: 有人能解释一下为什么吗?是Java 8的弊端吗?

  • 本文向大家介绍Java 中的 String对象为什么是不可变的,包括了Java 中的 String对象为什么是不可变的的使用技巧和注意事项,需要的朋友参考一下 什么是不可变对象? String对象是不可变的,但这仅意味着你无法通过调用它的公有方法来改变它的值。 众所周知, 在Java中, String类是不可变的。那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再

  • 见演示 为什么常量可变lambda不能接受引用参数?