-2

我在网上读到一些错误地指出int []Java 中的标准等数组作为副本传递,而不是传递对数组的引用,类似于基本的数值类型,当我认为我正在修改一个副本时,最终覆盖了一个数组。我是否可以将此作为设计选择,以使大约 90 年代中期 Java 的目标受众更简单?(使对象在语法上看起来与 C 数组相同,或者数组真的不是 Java 中的“对象”类型?)

也就是说,他们为什么不这样做:

Array array = new Array(<size>);

此外,为什么他们不让所有内容(文字除外)通过引用传递以确保一致性?(int然后 s 将作为对 的引用而int不是作为 的值传递int,因此在该方法中修改作为方法参数的变量将修改原始变量的值等)

链接到 Java 中关于按引用传递与按值传递的讨论

4

6 回答 6

12

Java 中没有任何内容通过引用传递——但您需要知道变量的值是什么。

类型变量的值int[]不是数组。它是对数组的引用。所有数组都是引用类型,即使它是一个基元数组。

当你写:

int[] x = { 1, 2, 3, 4, 5 };
someMethod(x);

然后 x 的按值传递给someMethod。它是对数组的引用——该方法可以更改数组的内容,但不能更改x自身的值,如果 Java 使用传递引用,它可以做到这一点。

一旦你理解了任何表达式的值要么是一个引用,要么是一个原始值,很多事情就会变得更加清晰。例如,使用以下代码:

int[] x = { 1, 2, 3, 4, 5 };
int[] y = x;
y[0] = 10;
System.out.println(x[0]);

你期望结果是什么?赋值运算符将值从 RHS 复制到 LHS - 所以xy是对同一数组的引用。因此最后一行打印出 10。这种“复制值”与用于参数传递的原理完全相同。

这里没有不一致 - 您只需要了解发生了什么,以及按值传递和按引用传递的真正含义。

编辑:我真的很惊讶对此仍有任何疑问。

来自 Java 语言规范,第 8.4.1 节(形式参数)

当调用方法或构造函数时(第 15.12 节),实际参数表达式的值在执行方法或构造函数的主体之前初始化新创建的参数变量,每个声明的类型

这实际上是按值传递语义的定义:参数的用作参数的初始

这些值可能是引用,也可能是原始值 - 它不会改变它是传递的值的事实。

编辑:您使用的定义虽然在我看来表达得很糟糕,但当然并不意味着声称 Java 按值传递所有内容具有误导性。

它说,对于按值传递:

这会复制变量,如果变量像数组一样大,可能会非常缓慢且效率低下。

请注意,它是在谈论 C 和 C++(尽管实际上 C++也确实有通过引用传递)。它谈到了制作变量的副本。这正是 Java 所做的。不同之处在于,在 Java 中,变量的值始终是原始值或引用。它永远不是一个对象。

一旦你理解了这个关键点(而且它不仅仅对参数传递很重要),就可以完全直接地正确地说 Java 通过值传递所有内容。当调用方法或构造函数时,会计算参数并将用作参数的初始值。这就是按值传递,这就是 Java 对所有参数所做的。

至于您链接到的 PDF - IMO 对这个主题的讨论非常糟糕。它使通过引用的方式变得一团糟,它无缘无故地特例 String(String 恰好是不可变的,但其他类也可以)并且通常是错误的。如果我提供一些支持我观点的链接会有所帮助吗?

还有数百个。您不是第一个对此感到困惑的人,但 Java 确实通过值传递所有内容,甚至是引用。

于 2009-08-10T21:58:04.567 回答
7

Java 中的一切都是按值传递的。一切。在对象类型(数组是)的情况下,通过值传递的是引用。不,这与传递引用不同。

于 2009-08-10T21:58:14.443 回答
2

当然可以。不可否认,内存地址也是一个值。所以按引用传递实际上只是按值传递的一个特例。

+1 以获得关于 SO 的最菲利斯人答案。

顺便说一句,我想我可能也有一部分答案

“他们为什么不让所有内容都通过引用传递以确保一致的行为?”

在这种情况下,系统如何处理参数是文字的调用,比如'result = f(2);' ?

那么该文字是否也不能通过引用传递?如果是这样,那是否会通过一些更新引用的调用来让你的文字改变值的可能性?在这种情况下,术语“文字”可能会变得有些不合适,并且编译器无法实现许多可能的代码优化?

于 2009-08-10T22:13:02.447 回答
1

是的,int[]是一个对象。是的,语法是为了让 C/C++ 程序员参与进来。不,这不是一个糟糕的设计选择,这两个陈述并不矛盾。

于 2009-08-10T21:59:18.263 回答
1

至少从性能的角度来看,通过引用传递数组是很有意义的。否则,您将需要大量不必要的复制。

话虽如此,我同意应该有一个不可变的数组对象。

于 2009-08-10T21:59:46.497 回答
0

对于 Java 对象“按值传递”意味着什么,这是一个常见的误解。所有 Java 对象、数组和原语都是按值传递的,但在对象和数组的情况下,传递的值实际上是指向原始对象的指针(或者,如果您愿意,可以引用)。

这就是区别。在 Java 和 C++ 中,我都可以编写这个函数:

void myFunc(int[] arr) {
    arr[0] = 17;
}
myFunc(myLocalArray);

这在任何一种语言中都会导致存储在 arr[0] 中的值在 myFunc 的“arr”变量和 myLocalArray 中都为 17。

当然,这非常令人兴奋,但与“通过引用”无关。

通过引用传递数组意味着,在像 C++(但不是 Java)这样的语言中,我可以这样做:

void myFunc(int[] arr) {
    arr = null;
}
myFunc(myLocalArray);

在这里,我用一个完全不同的对象(在这种情况下为 null,但它可以是任何东西)完全替换了 myLocalArray 对象。我不仅修改了对象(例如,更改了存储在特定索引处的值),而且从根本上删除了 myLocalArray 甚至指向合法数组的所有证据。

在 Java 中,这是永远做不到的。

因此,Java 中的基本规则是:

  1. 对象内的所有编辑(例如,调用改变状态的函数、直接改变状态、改变数组中的值等)都作用于原始对象(即,在调用函数中)。如果没有您专门制作,则不会制作任何副本。
  2. 用新对象完全覆盖本地对象永远不会影响调用函数中的对象。
于 2009-08-10T22:09:42.233 回答