224

数组不是Java中的原始类型,但它们也不是对象,那么它们是通过值传递还是通过引用传递?它是否取决于数组包含的内容,例如引用或原始类型?

4

7 回答 7

293

Java 中的所有内容都是按值传递的。对于数组(只不过是一个对象),数组引用是按值传递的(就像对象引用是按值传递一样)。

当您将数组传递给其他方法时,实际上会复制对该数组的引用。

  • 通过该引用对数组内容的任何更改都会影响原始数组。
  • 但是将引用更改为指向新数组不会更改原始方法中的现有引用。

请参阅这篇文章:Java 是“按引用传递”还是“按值传递”?

请参阅此工作示例:

public static void changeContent(int[] arr) {

   // If we change the content of arr.
   arr[0] = 10;  // Will change the content of array in main()
}

public static void changeRef(int[] arr) {
   // If we change the reference
   arr = new int[2];  // Will not change the array in main()
   arr[0] = 15;
}

public static void main(String[] args) {
    int [] arr = new int[2];
    arr[0] = 4;
    arr[1] = 5;

    changeContent(arr);

    System.out.println(arr[0]);  // Will print 10.. 
  
    changeRef(arr);

    System.out.println(arr[0]);  // Will still print 10.. 
                                 // Change the reference doesn't reflect change here..
}
于 2012-10-06T07:47:07.800 回答
168

你的问题是基于一个错误的前提。

数组不是 Java 中的原始类型,但它们也不是对象……”

事实上,Java 中的所有数组都是对象1。每个 Java 数组类型都有java.lang.Object其超类型,并继承ObjectAPI 中所有方法的实现。

...那么它们是通过值传递还是通过引用传递?它是否取决于数组包含的内容,例如引用或原始类型?

简短的回答:1)按价值传递,2)没有区别。

更长的答案:

像所有 Java 对象一样,数组是按值传递的……但值是对数组的引用。因此,当您在被调用方法中为数组的单元格分配某些内容时,您将分配给调用者看到的同一个数组对象。

这不是通过引用传递。 真正的传递引用涉及传递变量的地址。使用真正的传递引用,被调用的方法可以分配给它的局部变量,这会导致调用者中的变量被更新。

但不是在Java中。在 Java 中,被调用的方法可以更新数组的内容,并且可以更新其数组引用的副本,但不能更新保存调用者数组引用的调用者中的变量。因此……Java 提供的不是引用传递。

这里有一些链接解释了按引用传递和按值传递之间的区别。如果你不理解我上面的解释,或者如果你倾向于不同意这些术语,你应该阅读它们。

相关的SO问题:

历史背景:

短语“pass-by-reference”最初是“call-by-reference”,用于区分FORTRAN(call-by-reference)和ALGOL-60(call-by-value)的参数传递语义和点名)。

  • 在按值调用中,参数表达式被评估为一个值,并且该值被复制到被调用的方法中。

  • 在按引用调用中,参数表达式被部分评估为传递给调用方法的“左值”(即变量或数组元素的地址)。然后调用方法可以直接读取和更新变量/元素。

  • 在按名称调用中,实际的参数表达式被传递给调用方法(!!),该方法可以对其进行多次评估(!!!)。这实现起来很复杂,并且可以用来(滥用)编写非常难以理解的代码。直呼只在 Algol-60 中使用过(谢天谢地!)。

更新

实际上,Algol-60 的名称调用类似于将 lambda 表达式作为参数传递。问题是这些不完全是 lambda 表达式(它们在实现级别被称为“thunk”)可以间接修改调用过程/函数范围内的变量状态。这是让他们如此难以理解的部分原因。(例如,参见Jensen 设备上的 Wikipedia 页面。)


1. 链接的问答(Java 中的数组以及它们如何存储在内存中)中没有任何内容表明或暗示数组不是对象。

于 2012-10-06T07:47:44.567 回答
64

数组实际上是对象,所以传递了一个引用(引用本身是按值传递的,还困惑吗?)。快速示例:

// assuming you allocated the list
public void addItem(Integer[] list, int item) {
    list[1] = item;
}

您将从调用代码中看到对列表的更改。但是,您不能更改引用本身,因为它是按值传递的:

// assuming you allocated the list
public void changeArray(Integer[] list) {
    list = null;
}

如果您传递一个非空列表,则在方法返回时它不会为空。

于 2012-10-06T07:46:17.927 回答
13

不,那是错误的。数组是 Java 中的特殊对象。所以它就像传递其他对象一样,您传递引用的值,而不是引用本身。意思是,在被调用例程中更改数组的引用不会反映在调用例程中。

于 2012-10-06T07:47:38.673 回答
4

Java 中的一切都是按值传递的。

在数组的情况下,引用被复制到一个新的引用中,但请记住,Java 中的所有内容都是通过 value 传递的。

看看这篇有趣的文章以获取更多信息......

于 2012-10-06T07:49:22.820 回答
3

数组的最终讨论位于http://docs.oracle.com/javase/specs/jls/se5.0/html/arrays.html#27803。这清楚地表明 Java 数组是对象。这些对象的类在 10.8 中定义。

语言规范的第 8.4.1 节http://docs.oracle.com/javase/specs/jls/se5.0/html/classes.html#40420描述了如何将参数传递给方法。由于 Java 语法源自 C 和 C++,因此行为相似。原始类型按值传递,与 C 一样。传递对象时,对象引用(指针)按值传递,反映了按值传递指针的 C 语法。参见 4.3.1,http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.3

实际上,这意味着在方法内修改数组的内容会反映在调用范围内的数组对象中,但是在方法内为引用重新分配新值对调用范围内的引用没有影响,即这正是您对指向 C 中的结构或 C++ 中的对象的指针所期望的行为。

术语的混淆至少有一部分源于 C 普遍使用之前的高级语言的历史。在以前流行的高级语言中,应尽可能避免通过地址直接引用内存,并且被认为是语言的工作来提供抽象层。这使得该语言有必要明确支持从子例程(不一定是函数)返回值的机制。当提到“通过引用”时,这种机制就是正式的意思。

引入 C 时,它带有一个精简的过程调用概念,其中所有参数都是仅输入的,返回给调用者的唯一值是函数结果。然而,传递引用的目的可以通过显式和广泛使用指针来实现。由于它具有相同的目的,因此将指针作为对值的引用传递的做法通常通俗地称为通过引用传递。如果例程的语义调用要通过引用传递的参数,则 C 的语法要求程序员显式传递指针。按值传递指针是在 C 中实现按引用传递语义的设计模式。

由于在 C 语言中使用原始指针的唯一目的似乎通常是创建崩溃错误,因此后续开发,尤其是 Java,已寻求返回更安全的方法来传递参数。然而,C 的主导地位使得开发人员有责任模仿熟悉的 C 编码风格。结果是类似于指针传递的引用,但实施了更多保护以使其更安全。另一种选择是像 Ada 这样的语言的丰富语法,但这会出现不受欢迎的学习曲线,并减少 Java 的可能采用。

In short, the design of parameter passing for objects, including arrays, in Java,is esentially to serve the semantic intent of pass by reference, but is imlemented with the syntax of passing a reference by value.

于 2012-10-06T08:51:52.860 回答
0

有点诡计……甚至引用在 Java 中也是按值传递的,因此对引用本身的更改被限定在被调用的函数级别。编译器和/或 JVM 通常会将值类型转换为引用。

于 2012-10-06T07:48:03.267 回答