数组在 Java 中是按值传递还是按引用传递?[复制]

Are arrays passed by value or passed by reference in Java? [duplicate]

提问人:Froskoy 提问时间:10/6/2012 最后编辑:xDanFroskoy 更新时间:6/29/2023 访问量:285536

问:

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

Java 数组 按引用传递

评论

18赞 aroth 10/6/2012
数组是对象,是的,但 Java 中没有任何内容是通过引用传递的。所有参数传递都是按值传递的。对于 Object,传递的是按值传递的对 Object 的引用(即指针)。按值传递引用与按引用传递不同。
0赞 Eng.Fouad 10/6/2012
您可能会发现这很有用: stackoverflow.com/a/9404727/597657
0赞 myx 3/18/2019
我无法为这个问题添加答案,但我写了一个代码片段,可能有助于理解下面的答案:write.as/1wjcm7m50w41k.md
0赞 Sam Ginrich 5/21/2023
Java 不考虑引用,其他语言会考虑

答:

65赞 Tudor 10/6/2012 #1

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

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

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

评论

0赞 aleroot 10/6/2012
不,在 Java 中,一切都是通过值传递的!JAva 中不存在引用传递,因为它在 ANSI C 中不存在,这就是指针存在的原因......
2赞 Tudor 10/6/2012
@aleroot:我说引用了方法,否则你看不到变化,不是说 java 是按引用传递的!是的,引用是通过值传递的,但这不是重点。
2赞 aleroot 10/6/2012
@Tudor你的句子不清楚......
0赞 Tudor 10/6/2012
@aleroot:好的,我又补充了一些评论......
2赞 Stephen C 10/29/2016
“但是,你不能改变引用本身,因为它是按值传递的” - 实际上,你可以(本地)更改引用。您无法更改的是调用上下文中从何处获取引用的变量。只有当人们将引用和保存引用的变量混为一谈时,这才会令人困惑。
313赞 Rohit Jain 10/6/2012 #2

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

评论

6赞 David Bandel 3/19/2022
为什么不直接说数组是通过引用传递的呢?
1赞 Stephen C 9/3/2022
因为这不是真的。通过引用调用意味着不同的东西。
0赞 Sam Ginrich 12/3/2022
没有这个话题的答案。问题是,数组是否是对象
13赞 sakthisundar 10/6/2012 #3

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

评论

0赞 Froskoy 10/6/2012
谢谢。那么,是否必须取消引用每个数组访问?这是否意味着在 Java 中使用数组和使用任何其他类型的列表一样慢,除了您可以在其中存储基元类型,而不需要取消引用?
0赞 noisesmith 12/11/2013
不可以,因为存储在堆中的数据中是连续的,这意味着迭代查找在 CPU 时间方面要便宜得多。列表不保证连续存储。
0赞 Stephen C 8/23/2019
“所以这就像传递其他对象一样,你传递的是引用的值,而不是引用本身。这在两个方面具有误导性/错误。1)在这方面,数组不是“特殊对象”。在参数传递语义方面,它们的行为与非数组对象完全相同。2)“参考价值”和“参考价值”是一回事。你应该说的是,你传递的是引用的值,而不是引用指向的对象的值。
0赞 AndrewF 12/25/2019
数组速度更快,但这并不是因为“列表不能保证连续存储”——它们更快,因为它们的 API 形状、内存、访问和实现是直接内置在语言中的(而不是动态的)。Java 规范定义了它们的存储方式,并定义了访问它们的方式。访问不涉及调用方法(如 、 、 等)的开销。从理论上讲,你可以将 int[] 实现为 Object,使用连续的内存块等,并编写一个编译器来使用它而不是原生数组。它仍然比本机阵列慢得多。#get#set#iterator#size
174赞 Stephen C 10/6/2012 #4

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

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

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

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

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

更长的答案:

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

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

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

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

相关 SO 问题:

历史背景:

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

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

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

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

更新

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


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

评论

1赞 Stephen C 12/3/2022
那不是引文。对不起,但我认为继续这个讨论没有任何意义。我只是对“Java 是通过引用传递的”辩论不感兴趣。如果你想写出你自己对这个问题的答案,请随意。但我将要求模组删除这些关于此答案的评论,因为“不再需要”。
1赞 Jeff Watkins 10/6/2012 #5

有点把戏......在 Java 中,甚至引用也是按值传递的,因此对引用本身的更改在被调用的函数级别进行范围。编译器和/或 JVM 通常会将值类型转换为引用。

评论

0赞 Sam Ginrich 4/6/2022
认为是Kernighan和Ritchie想把指针称为值。在这种思想的继承中,Java 是面向引用的,而不是面向对象的,这是“理解”的关键。
4赞 aleroot 10/6/2012 #6

Java中的一切都是通过值传递的。

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

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

5赞 Steven McGrath 10/6/2012 #7

数组的权威讨论位于 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 语言中原始指针的唯一目的似乎是创建崩溃的 bug,因此后续开发,尤其是 Java,一直在寻求回归更安全的方式来传递参数。然而,C 语言的主导地位使得开发人员有责任模仿熟悉的 C 编码风格。结果是引用的传递方式与指针类似,但通过更多保护实现,使其更安全。另一种选择是像 Ada 这样的语言的丰富语法,但这会带来不受欢迎的学习曲线,并减少 Java 的采用可能性。

简言之,在Java中,对象(包括数组)的参数传递设计,本质上是服务于通过引用传递的语义意图,但通过值传递引用的语法来实现。

评论

1赞 Stephen C 10/6/2012
“由于 Java 语法派生自 C 和 C++,因此行为是相似的。”相似的语法并不意味着相似的语义。
0赞 Steven McGrath 10/6/2012
我引用了较旧的规范,因为它仍然是正确的,而且我不知道 OP 使用的是哪个版本。参数传递在 8.4.1 中描述如下:当调用方法或构造函数 (§15.12) 时,实际参数表达式的值在执行方法或构造函数的主体之前初始化新创建的参数变量,每个声明的 Type 都是。DeclaratorId 中显示的 Identifier 可用作方法或构造函数主体中的简单名称,以引用形式参数。
0赞 Steven McGrath 10/6/2012
关于sytax,Java,C和C++之间的并行绝非偶然,该设计旨在简化C和C++程序员的过渡。语言设计是人类交流的问题,而不是数学的严谨性,将熟悉的语法与不熟悉的语义混合在一起会造成过度的复杂性。我们正在努力建立一个易于采用的系统。
0赞 Stephen C 10/6/2012
你错过了我的观点。我相信您知道两种相关语言具有相同语法但语义不同的情况。我的观点是,相同的语法并不意味着相同的语义,无论语言是否相关。
0赞 Stephen C 10/6/2012
此外,谈论“引用传递的语义意图”是对该意图的假设,这些假设与FORTRAN,C,C++等中50%的引用传递用例不一致。例如,方法。请记住,经典 FORTRAN 中的调用不涉及指针及其风险。(你甚至可以争辩说,C根本不做引用调用。它正在做的是显式创建传递指针......按价值...必须以某些方式使用,以避免“未指定的行为”。swap(int &a, int &b)