我一直认为Java使用的是按引用传递。然而,我看了一篇博客文章,声称Java使用的是按值传递。我不认为我理解作者所做的区分。
这是什么意思?
我一直认为Java使用的是按引用传递。然而,我看了一篇博客文章,声称Java使用的是按值传递。我不认为我理解作者所做的区分。
这是什么意思?
public static void main(String[] args) {
Dog aDog = new Dog("Max");
Dog oldDog = aDog;
// we pass the object to foo
foo(aDog);
// aDog variable is still pointing to the "Max" dog when foo(...) returns
aDog.getName().equals("Max"); // true
aDog.getName().equals("Fifi"); // false
aDog == oldDog; // true
}
public static void foo(Dog d) {
d.getName().equals("Max"); // true
// change d inside of foo() to point to a new Dog instance "Fifi"
d = new Dog("Fifi");
d.getName().equals("Fifi"); // true
}
aDog.getName()
仍将返回"Max"
。在函数foo
中,Dog
"Fifi"
作为对象引用按值传递,因此main
中的aDog
值未更改。如果按引用传递,则在调用foo
后,main
中的aDog.getName()
将返回"Fifi"
。public static void main(String[] args) {
Dog aDog = new Dog("Max");
Dog oldDog = aDog;
foo(aDog);
// when foo(...) returns, the name of the dog has been changed to "Fifi"
aDog.getName().equals("Fifi"); // true
// but it is still the same dog:
aDog == oldDog; // true
}
public static void foo(Dog d) {
d.getName().equals("Max"); // true
// this changes the name of d to be "Fifi"
d.setName("Fifi");
}
Fifi
是调用foo(aDog)
后狗的名字,因为对象的名称是在foo(...)
内设置的。任何foo
对d
执行的操作都是针对实际上执行在aDog
上的操作,但是不可能更改变量aDog
本身的值。foo
覆盖了d
的值并显示所有输入到一个函数都是按值传递,但foo(aDog);
确实没有改变aDog
的值。 - user21820d = new Dog("Fifi");
时,它覆盖了输入变量d
,该变量存储一个引用,但不是“通过引用传递的对象”。与C中函数签名中的&d
相比,后者是按引用传递的。 - user21820我刚刚注意到你引用了我的文章。
Java规范指出,在Java中所有的内容都是按值传递的。在Java中并不存在所谓的“按引用传递”。
理解这一点的关键在于,例如:
Dog myDog;
它并不是一只狗,实际上它是指向一只狗的指针。在Java中使用“引用”这个术语非常具有误导性,这也是导致大部分混淆的原因。他们所谓的“引用”在大多数其他语言中更像是我们称之为“指针”的东西。
这意味着当你拥有
Dog myDog = new Dog("Rover");
foo(myDog);
你实际上是将创建的Dog
对象的地址传递给了foo
方法。
(我说“实际上”是因为Java指针/引用不是直接地址,但最好这样考虑。)
假设Dog
对象驻留在内存地址42处。这意味着我们将42传递给该方法。
如果该方法被定义为:
public void foo(Dog someDog) {
someDog.setName("Max"); // AAA
someDog = new Dog("Fifi"); // BBB
someDog.setName("Rowlf"); // CCC
}
让我们看看发生了什么。
someDog
设置为值42someDog
指向的Dog
(地址为42的Dog / code>对象)
- 要求该
Dog
(地址为42)更改其名称为Max
- 在“BBB”行
- 创建一个新的
Dog
。 假设他在地址74处
- 我们将参数
someDog
分配给74
- 在“CCC”行
- 跟随
someDog
指向的Dog
(地址为74的Dog / code>对象)
- 要求该
Dog
(地址为74)更改其名称为Rowlf
- 然后,我们返回
现在让我们考虑方法外发生的事情:
我的狗有变化吗?
这是关键。
请记住, myDog
是一个指针,而不是实际的Dog
,答案是否定的。 myDog
仍然具有值42; 它仍然指向原始的Dog
(但请注意,由于“AAA”行,它的名称现在为“Max” - 仍然是同一只狗; myDog
的值未更改。)
跟随地址并更改末尾的内容是完全有效的;但是,这不会更改变量。
Java的工作方式与C完全相同。 您可以分配一个指针,将指针传递给方法,在方法中跟随指针并更改所指向的数据。但是,调用者不会看到您对指针指向的内容所做的任何更改。(在使用传递按引用语义的语言中,方法函数可以更改指针,并且调用者会看到该更改。)
在支持传递按引用方式的C ++、Ada、Pascal和其他语言中,您实际上可以更改传递的变量。
如果Java具有传递按引用语义,则我们上面定义的 foo
方法在分配 someDog
时会更改 myDog
指向的位置。
将引用参数视为传递的变量的别名。当分配该别名时,传递的变量也会被分配。
更新
在评论中的讨论需要一些澄清...
在C中,您可以编写
void swap(int *x, int *y) {
int t = *x;
*x = *y;
*y = t;
}
int x = 1;
int y = 2;
swap(&x, &y);
这在C语言中并不是一个特殊情况。两种语言都使用按值传递的语义。在这里,调用方创建了额外的数据结构来帮助函数访问和操作数据。
函数被传递指向数据的指针,并按照这些指针来访问和修改该数据。
在Java中采用类似的方法,调用方设置辅助结构可能会是这样的:
void swap(int[] x, int[] y) {
int temp = x[0];
x[0] = y[0];
y[0] = temp;
}
int[] x = {1};
int[] y = {2};
swap(x, y);
(或者,如果您想要这两个示例都演示另一种语言没有的功能,则创建一个可变的IntWrapper类来代替数组)在这些情况下,C和Java都在模拟传递引用。他们仍然传递值(指向整数或数组的指针),并在被调用的函数内部跟随这些指针来操作数据。
按引用传递全部关乎函数的声明/定义以及它如何处理其参数。引用语义适用于对该函数的每个调用,调用站点只需要传递变量,而不需要额外的数据结构。
这些模拟需要调用站点和函数进行合作。毫无疑问,这是有用的,但仍然是按值传递。
让我通过一个示例来解释一下:
public class Main {
public static void main(String[] args) {
Foo f = new Foo("f");
changeReference(f); // It won't change the reference!
modifyReference(f); // It will modify the object that the reference variable "f" refers to!
}
public static void changeReference(Foo a) {
Foo b = new Foo("b");
a = b;
}
public static void modifyReference(Foo c) {
c.setAttribute("c");
}
}
声明一个名为f
的引用,类型为Foo
,并将其赋值为一个具有属性"f"
的新Foo
对象。
Foo f = new Foo("f");
从方法的角度来看,声明了一个类型为Foo
、名为a
的引用,并将其初始赋值为null
。
public static void changeReference(Foo a)
当调用changeReference
方法时,引用a
将被赋值为作为参数传递的对象。
changeReference(f);
声明一个名为b
的引用,类型为Foo
,并将其赋值为一个具有属性"b"
的新Foo
对象。
Foo b = new Foo("b");
a = b
对引用a
进行了新的赋值,而不是f
,它指向具有属性"b"
的对象。
当调用modifyReference(Foo c)
方法时,创建了一个名为c
的引用,并将其赋值为具有属性"f"
的对象。
c.setAttribute("c");
将更改引用c
指向的对象的属性,该对象与引用f
指向的对象相同。
Java始终按值传递,没有任何例外。
那么为什么会有人对此感到困惑,并认为Java是按引用传递,或者认为他们有一个Java作为按引用传递的示例呢?关键在于Java从未在任何情况下直接提供对对象本身的值的访问。唯一访问对象的方式是通过对该对象的引用。因为Java对象总是通过引用而不是直接访问,所以当严谨地说时,将字段、变量和方法参数称为对象是很常见的,尽管它们实际上只是对象的引用。混淆源于这种(严格来说是不正确的)术语变化。
因此,在调用方法时:
int
、long
等),按值传递是基本类型的实际值(例如3)。因此,如果您有doSomething(foo)
和public void doSomething(Foo foo) {..}
,则两个Foo都复制了指向相同对象的引用。
自然地,按值传递一个对象的引用看起来非常像(在实践中也无法区分)通过引用传递一个对象。
f(x)
(传递一个变量)永远不会将值赋给x
本身。不存在传递变量地址(别名)的情况。这是一种稳健的语言设计决策。 - Joop Eggenint test(int *a) { int b = *(a); return b;)
? - bwass31这将为您提供有关Java如何工作的一些见解,以至于在您下次讨论Java按引用传递还是按值传递时,您只会微笑 :-)
第一步,请从您的脑海中删除以“p”开头的单词“_ _ _ _ _ _ _”,特别是如果您来自其他编程语言。Java和“p”不能写在同一本书、论坛甚至txt中。
第二步,请记住,当您将一个对象传递到方法中时,您传递的是对象的引用,而不是对象本身。
现在想想一个对象的引用/变量是什么:
在以下代码中(请不要尝试编译/执行此代码...):
1. Person person;
2. person = new Person("Tom");
3. changeName(person);
4.
5. //I didn't use Person person below as an argument to be nice
6. static void changeName(Person anotherReferenceToTheSamePersonObject) {
7. anotherReferenceToTheSamePersonObject.setName("Jerry");
8. }
发生了什么?
一张图片胜过千言万语:
请注意,另一个指向同一人对象的引用箭头是指向对象而不是变量person!如果你没明白,那就相信我并记住最好说Java是按值传递。嗯,按引用值传递。哦,甚至更好的是通过复制变量值传递!现在随意讨厌我,但请注意,在谈论方法参数时,原始数据类型和对象之间没有区别。你总是传递引用值的位的副本!如果是原始数据类型,这些位将包含原始数据类型本身的值。如果是对象,则位将包含告诉JVM如何到达对象的地址的值。Java是按值传递的,因为在方法内部,你可以尽情修改所引用的对象,但无论你怎么努力,都无法修改传递的变量,它将继续引用(而不是p_______)相同的对象!File file = new File("C:/");
changeFile(file);
System.out.println(file.getAbsolutePath());
}public static void changeFile(File f) {
f = new File("D:/");
}` - ExcessstoneJava传递的是值引用。
所以,您无法更改传递的引用。
我觉得争论“按引用传递 vs 按值传递”不是特别有帮助。
如果你说,“Java 是按照什么传递的(引用/值)”,在任一情况下,你都没有提供完整的答案。下面提供一些额外信息,希望有助于理解在内存中发生的情况。
在我们讨论 Java 实现之前,先来了解一下堆栈/堆的基础知识: 值按规则地在堆栈上进出,就像自助餐厅的盘子堆一样有序。 堆内存(也称为动态内存)是混乱和无组织的。JVM 只是在能找到空间的地方分配内存,并在不再需要使用它的变量时释放它。
好的。首先,局部原始类型会被放在栈上。所以这段代码:
int x = 3;
float y = 101.1f;
boolean amIAwesome = true;
导致这个结果:
当你声明并实例化一个对象时,实际的对象被存储在堆内存中。那么栈上存储了什么呢?是指向堆内存中对象的地址。C++程序员称之为指针,但一些Java开发人员反对使用“指针”这个词。无论如何,知道对象的地址存储在栈上即可。
就像这样:
int problems = 99;
String name = "Jay-Z";
数组是一个对象,因此它也存储在堆上。那么,数组中的对象呢?它们会获得自己的堆空间,并且每个对象的地址都会放在数组内。
JButton[] marxBros = new JButton[3];
marxBros[0] = new JButton("Groucho");
marxBros[1] = new JButton("Zeppo");
marxBros[2] = new JButton("Harpo");
那么,当你调用一个方法时传入的是什么?如果你传入一个对象,实际上传入的是该对象的地址。一些人可能会说这是地址的“值”,有些人则说它只是对该对象的引用。这就是“引用”与“值”支持者之间圣战的起源。重要的不是你如何称呼它,而是你理解传入的是对象的地址。
private static void shout(String name){
System.out.println("There goes " + name + "!");
}
public static void main(String[] args){
String hisName = "John J. Jingleheimerschmitz";
String myName = hisName;
shout(myName);
}
一串字符串被创建并在堆中分配了空间,该字符串的地址被存储在栈中并赋予标识符“hisName”,由于第二个字符串的地址与第一个相同,因此不会创建新字符串,也不会分配新的堆空间,但是在栈上创建了一个新的标识符。然后我们调用 shout()
:创建一个新的堆栈帧并创建一个新标识符 name
,并将其分配给已经存在的字符串的地址。
所以,值,引用?你说“土豆”。基本上,重新分配对象参数不会影响参数本身,例如:
private static void foo(Object bar) {
bar = null;
}
public static void main(String[] args) {
String baz = "Hah!";
foo(baz);
System.out.println(baz);
}
这将输出"Hah!"
,而不是null
。 这个方法的原理是bar
是baz
值的副本,而baz
只是一个指向"Hah!"
的引用。如果它是引用本身,那么foo
会重新定义baz
为null
。
C++代码如下:注意:糟糕的代码——内存泄露!但它能够证明观点。
void cppMethod(int val, int &ref, Dog obj, Dog &objRef, Dog *objPtr, Dog *&objPtrRef)
{
val = 7; // Modifies the copy
ref = 7; // Modifies the original variable
obj.SetName("obj"); // Modifies the copy of Dog passed
objRef.SetName("objRef"); // Modifies the original Dog passed
objPtr->SetName("objPtr"); // Modifies the original Dog pointed to
// by the copy of the pointer passed.
objPtr = new Dog("newObjPtr"); // Modifies the copy of the pointer,
// leaving the original object alone.
objPtrRef->SetName("objRefPtr"); // Modifies the original Dog pointed to
// by the original pointer passed.
objPtrRef = new Dog("newObjPtrRef"); // Modifies the original pointer passed
}
int main()
{
int a = 0;
int b = 0;
Dog d0 = Dog("d0");
Dog d1 = Dog("d1");
Dog *d2 = new Dog("d2");
Dog *d3 = new Dog("d3");
cppMethod(a, b, d0, d1, d2, d3);
// a is still set to 0
// b is now set to 7
// d0 still have name "d0"
// d1 now has name "objRef"
// d2 now has name "objPtr"
// d3 now has name "newObjPtrRef"
}
在Java中,public static void javaMethod(int val, Dog objPtr)
{
val = 7; // Modifies the copy
objPtr.SetName("objPtr") // Modifies the original Dog pointed to
// by the copy of the pointer passed.
objPtr = new Dog("newObjPtr"); // Modifies the copy of the pointer,
// leaving the original object alone.
}
public static void main()
{
int a = 0;
Dog d0 = new Dog("d0");
javaMethod(a, d0);
// a is still set to 0
// d0 now has name "objPtr"
}
Java 只有两种传递方式:对于内置类型采用值传递,而对于对象类型采用指针的值传递。
Java以传值方式传递对象引用。