在 Java 中,数组是通过值传递的还是通过引用传递的?

2022-08-31 05:49:37

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


答案 1

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..
}

答案 2

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

数组在Java中不是基元类型,但它们也不是对象......"

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

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

简短的回答:1)通过值,2)它没有区别。

更长的答案:

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

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

但不是在Java中。在 Java 中,被调用的方法可以更新数组的内容,并且可以更新其数组引用的副本,但它无法更新调用方的数组引用的调用方中的变量。因此。。。Java提供的不是逐个引用。

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

相关 SO 问题:

历史背景:

短语“逐个引用传递”最初是“逐个引用调用”,它用于区分FORTRAN(逐个引用调用)的参数传递语义和ALGOL-60的参数传递语义(按值调用和按名称调用)。

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

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

  • 在按名称调用中,实际的参数表达式被传递给调用方法(!!),该方法可以多次计算它(!!!)。这很难实现,并且可能被使用(滥用)来编写非常难以理解的代码。按名称调用只在Algol-60中使用过(谢天谢地!

更新

实际上,Algol-60 的按名称调用类似于将 lambda 表达式作为参数传递。问题是这些不完全是lambda表达式(它们在实现级别被称为“thunks”)可以间接修改调用过程/函数中范围内的变量的状态。这是使它们如此难以理解的部分原因。(例如,请参阅Jensen's Device上的维基百科页面。


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