Java是“通过引用传递”还是“通过值传递”?

guykilcj  于 2022-09-16  发布在  Java
关注(0)|答案(30)|浏览(230)

我一直认为Java使用通过引用传递
然而,我看到a blog post声称Java使用传递值
我想我不明白他们的区别。
解释是什么?

ar7v8xwq

ar7v8xwq16#

术语“通过值”和“通过引用”在计算机科学中具有特殊的precisely defined含义。这些含义不同于许多人第一次听到这些术语时的直觉。这场讨论中的许多混乱似乎都来自这一事实。
术语“传递值”和“传递引用”指的是变量。传递值意味着变量的传递给函数/方法。通过引用传递意味着该变量的引用被传递给函数。后者为函数提供了一种更改变量内容的方法。
根据这些定义,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"main中的值aDog在函数foo中没有改变,其中Dog``"Fifi"作为通过值传递的对象参考。如果通过引用传递,则main中的aDog.getName()将在调用foo之后返回"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(...)内。food执行的任何操作都是这样的,即,出于所有实际目的,它们都是对aDog执行,但不可能**改变变量aDog本身的值。
有关传递参考和传递值的更多信息,请参考以下答案:https://stackoverflow.com/a/430958/6005228。这更彻底地解释了这两种语言背后的语义和历史,也解释了为什么Java和许多其他现代语言在某些情况下似乎都这样做。

bzzcjhmw

bzzcjhmw17#

已经有很好的答案涵盖了这一点。我想通过分享一个非常简单的示例(将编译),对比c中的引用传递和Java中的值传递之间的行为,做出一点贡献。
几点:
1.术语“参考”是一个具有两种不同含义的重载。在Java中,它只是一个指针,但在“通过引用传递”的上下文中,它意味着传递的原始变量的句柄。
1.Java是传递值*。Java是C(以及其他语言)的后代。在C之前,一些(但不是所有)早期语言,如FORTRAN和COBOL支持PBR,但C不支持。PBR允许这些其他语言对子例程中传递的变量进行更改。为了完成同样的事情(即改变函数中变量的值),C程序员将变量指针传递到函数中。受C启发的语言,如Java,借鉴了这一思想,并继续像C一样向方法传递指针,只是Java调用其指针引用。同样,这是“引用”一词与“引用传递”一词的不同用法。
1.**C
允许通过引用传递**,方法是使用“&”字符声明引用参数(该字符恰好与C和C中用于指示“变量地址”的字符相同)。例如,如果我们通过引用传入指针,则参数和参数不只是指向同一个对象。相反,它们是相同的变量。如果一个被设置为不同的地址或空,那么另一个也会被设置为空。
1.在下面的C
示例中,我通过引用指针传递到以空结尾的字符串。在下面的Java示例中,我通过值传递对字符串的Java引用(同样,与指向字符串的指针相同)。注意注解中的输出。
C++传递参考示例:

using namespace std;

# include <iostream>

void change (char *&str){   // the '&' makes this a reference parameter
    str = NULL;
}

int main()
{
    char *str = "not Null";
    change(str);
    cout<<"str is " << str;      // ==>str is <null>
}

Java通过值传递“Java引用”示例

public class ValueDemo{

    public void change (String str){
        str = null;
    }

     public static void main(String []args){
        ValueDemo vd = new ValueDemo();
        String str = "not null";
        vd.change(str);
        System.out.println("str is " + str);    // ==> str is not null!!
                                                // Note that if "str" was
                                                // passed-by-reference, it
                                                // WOULD BE NULL after the
                                                // call to change().
     }
}

编辑

有几个人写了评论,似乎表明要么他们没有看我的示例,要么他们没有看到c示例。不确定断开连接的位置,但猜测c示例并不清楚。我在pascal中发布了相同的示例,因为我认为pascal中的传递引用看起来更干净,但我可能错了。我可能只是让人们更加困惑;我希望不是。
在pascal中,通过引用传递的参数称为“var参数”。在下面的过程setToNil中,请注意参数“ptr”前面的关键字“var”。当指针传递到此过程时,它将通过引用**传递。注意行为:当这个过程将ptr设置为nil(即pascal表示NULL)时,它将参数设置为nil——在Java中不能这样做。

program passByRefDemo;
type 
   iptr = ^integer;
var
   ptr: iptr;

   procedure setToNil(var ptr : iptr);
   begin
       ptr := nil;
   end;

begin
   new(ptr);
   ptr^ := 10;
   setToNil(ptr);
   if (ptr = nil) then
       writeln('ptr seems to be nil');     { ptr should be nil, so this line will run. }
end.

编辑2

摘自《Java编程语言》,作者:肯·阿诺德、**詹姆斯·高斯林(发明Java的人)**和大卫·霍姆斯,第2章,第2.6.5节

方法的所有参数都是“按值传递”。换句话说,方法中参数变量的值是指定为参数的调用程序的副本。

他继续对物体提出同样的观点。
您应该注意,当参数是对象引用时,是对象引用而不是对象本身通过“值”传递
在同一节的结尾,他对java做了更广泛的陈述,认为java只通过值传递,而从不通过引用传递。
Java编程语言不通过引用传递对象;它****通过值传递对象引用。因为同一引用的两个副本引用同一个实际对象,所以通过一个引用变量所做的更改可以通过另一个变量看到。只有一种参数传递模式-通过值传递,这有助于保持简单。
本书的这一部分对Java中的参数传递以及通过引用传递和通过值传递之间的区别进行了很好的解释,这是由Java的创建者所做的。我鼓励任何人读它,特别是如果你仍然不相信。
我认为这两个模型之间的差异非常微妙,除非您在编程中实际使用了引用传递,否则很容易忽略两个模型的不同之处。
我希望这能解决争论,但可能不会。

编辑3

我可能对这篇文章有点着迷。可能是因为我觉得Java的制造商无意中传播了错误信息。如果他们没有使用“参考”这个词作为指针,而是使用了其他东西,比如丁勒贝里,那么就不会有问题了。你可以说,“Java通过值而不是引用传递dingleberries”,没有人会感到困惑。
这就是只有Java开发人员对此有异议的原因。他们看“参考”这个词,认为他们知道它的确切含义,所以他们甚至不考虑相反的论点。
不管怎样,我注意到一篇旧帖子中的一条评论,这是一个我非常喜欢的气球比喻。如此之多,以至于我决定把一些剪贴画粘在一起,制作一组卡通来说明这一点。

通过值传递引用——对引用的更改不会反映在调用方的作用域中,但对对象的更改会反映。这是因为引用被复制,但原始和副本都引用同一对象。
通过引用——没有引用的副本。调用方和被调用函数共享单个引用。对引用或对象数据的任何更改都会反映在调用方的作用域中。
编辑4

我已经看到了关于这个主题的文章,其中描述了Java中参数传递的底层实现,我认为这非常有帮助,因为它使抽象概念具体化。然而,对我来说,问题更多的是语言规范中描述的行为,而不是行为的技术实现。这是Java语言规范第8.4.1节的一个练习:
当调用方法或构造函数时(§15.12)**

xmakbtuz

xmakbtuz18#

Java总是按值传递,而不是按引用传递

首先,我们需要理解什么是传递值和传递引用。

通过值传递意味着在内存中复制传入的实际参数值。这是实际参数内容的副本。
通过引用(也称为通过地址)意味着存储了实际参数地址的副本

有时Java会给人一种通过引用传递的错觉。让我们通过下面的例子来看看它是如何工作的:

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeValue(t);
        System.out.println(t.name);
    }

    public void changeValue(Test f) {
        f.name = "changevalue";
    }
}

class Test {
    String name;
}

该程序的输出为:

changevalue

让我们一步一步了解:

Test t = new Test();

众所周知,它将在堆中创建一个对象,并将引用值返回给t。例如,假设t的值是0x100234(我们不知道实际的JVM内部值,这只是一个示例)。

new PassByValue().changeValue(t);

当将引用t传递给函数时,它不会直接传递对象测试的实际引用值,但会创建t的副本,然后将其传递给函数。由于它是传递值,因此它传递变量的副本,而不是变量的实际引用。因为我们说t的值是0x100234,所以t和f将具有相同的值,因此它们将指向相同的对象。

如果使用引用f更改函数中的任何内容,它将修改对象的现有内容。这就是为什么我们得到输出changevalue,它在函数中更新。
为了更清楚地理解这一点,请考虑以下示例:

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeRefence(t);
        System.out.println(t.name);
    }

    public void changeRefence(Test f) {
        f = null;
    }
}

class Test {
    String name;
}

这会抛出NullPointerException吗?不,因为它只传递引用的副本。在通过引用传递的情况下,可能会抛出NullPointerException,如下所示:

希望这会有所帮助。

djp7away

djp7away19#

在java中,一切都是引用,所以当您有类似于Point pnt1 = new Point(0,0);的东西时,java会执行以下操作:
1.创建新的点对象
1.创建新的点参考,并将该参考初始化为先前创建的点对象上的点(参考)
1.从这里开始,通过点对象生命,您将通过pnt1引用访问该对象。所以我们可以说,在Java中,通过对象的引用来操纵对象。

**Java不通过引用传递方法参数;它通过值传递它们。**我将使用this site中的示例:

public static void tricky(Point arg1, Point arg2) {
  arg1.x = 100;
  arg1.y = 100;
  Point temp = arg1;
  arg1 = arg2;
  arg2 = temp;
}
public static void main(String [] args) {
  Point pnt1 = new Point(0,0);
  Point pnt2 = new Point(0,0);
  System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
  System.out.println(" ");
  tricky(pnt1,pnt2);
  System.out.println("X1: " + pnt1.x + " Y1:" + pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);  
}

程序流程:

Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);

创建两个不同的点对象,并关联两个不同参照。

System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
System.out.println(" ");

预计产出将为:

X1: 0     Y1: 0
X2: 0     Y2: 0

在这一行,“通过值”进入游戏

tricky(pnt1,pnt2);           public void tricky(Point arg1, Point arg2);

引用pnt1pnt2通过值**传递给棘手的方法,这意味着现在您的引用pnt1和E1 D4D1E的copies命名为arg1arg2。因此pnt1arg1*指向同一对象。(与pnt2arg2相同)
tricky方法中:

arg1.x = 100;
 arg1.y = 100;

接下来,在tricky方法中:

Point temp = arg1;
arg1 = arg2;
arg2 = temp;

在这里,您首先创建新的temp点参考,它将指向与arg1参考相同的位置。然后,将参考arg1移动到到与arg2参考相同的位置。最后,arg2指向与temp相同的位置。

从这里开始,tricky方法的范围消失了,您无法再访问参考:arg1arg2temp但重要的是,当这些引用“在生活中”时,你对它们所做的一切都将永久地影响它们所指向的对象
因此,在执行方法tricky之后,当您返回到main时,会出现以下情况:
现在,程序的完全执行将是:

X1: 0         Y1: 0
X2: 0         Y2: 0
X1: 100       Y1: 100
X2: 0         Y2: 0
hm2xizp9

hm2xizp920#

问题的关键在于,表达式“passbyreference”中的单词reference的含义与Java中的单词reference的通常含义完全不同。
通常在Java中,引用表示对对象的a引用。但是编程语言理论中的技术术语通过引用/值是指对保存变量的存储单元的引用,这是完全不同的。

rhfm7lfc

rhfm7lfc21#

我不敢相信还没有人提到芭芭拉·利斯科夫。在1974年设计CLU时,她遇到了同样的术语问题,她发明了术语“共享调用”(也称为“对象共享调用”和“对象调用”),用于“值是引用的值调用”。

a0zr77ik

a0zr77ik22#

Java通过值传递对对象的引用。

6kkfgxo0

6kkfgxo023#

为了显示对比,比较以下C++Java片段:
在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只有两种类型的传递:内置类型的按值传递,对象类型的按指针值传递。

3okqufwl

3okqufwl24#

基本上,重新分配对象参数不会影响参数,例如。,

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。之所以这样做,是因为barbaz值的副本,它只是对"Hah!"的引用。如果它是实际的引用本身,那么foo会将baz重新定义为null

pjngdqdw

pjngdqdw25#

我觉得争论“参考传递与价值传递”并不是很有帮助。
如果您说“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,并将其分配给已经存在的字符串的地址。

那么,这是价值还是参考?你说“土豆”。

mwngjboj

mwngjboj26#

Java通过值传递引用。
因此,您无法更改传入的引用。

u5i3ibmn

u5i3ibmn27#

这将让您深入了解Java是如何工作的,在您下一次讨论Java通过引用传递或通过值传递时,您只会微笑:-)

第一步请从您的脑海中删除以“p”开头的单词,尤其是如果您来自其他编程语言。Java和“p”不能写在同一本书、论坛甚至txt中。
第二步请记住,当您将对象传递到方法中时,您传递的是对象引用,而不是对象本身。

*学生:大师,这是否意味着Java是通过引用传递的?
*主人:蚱蜢,不。

现在想想对象的引用/变量的作用:
1.变量包含告诉JVM如何在内存(堆)中获取引用对象的位
1.当向方法传递参数时,您传递的不是引用变量,而是引用变量中位的副本。类似于:3bad086a。3bad086a表示一种获取传递对象的方法。
1.所以你只是传递了3bad086a,它是引用的值。
1.您传递的是引用的值,而不是引用本身(而不是对象)。
1.该值实际上被复制并提供给方法
在下面(请不要尝试编译/执行此…):

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是在第#1行中创建的,开头为空。
  • 在第#2行中创建一个新的Person对象,存储在内存中,变量Person被赋予对Person对象的引用。即其地址。假设是3bad086a。
  • 保存对象地址的变量person传递给第3行中的函数。
  • 在第4行,你可以听到寂静的声音
  • 检查第5行的评论
  • 创建了一个方法局部变量-anotherReferenceToTheSamePersonObject,然后第6行中出现了魔术:
  • 变量/referenceperson被逐位复制,并传递给函数中的另一个引用对象。
  • 不会创建Person的新示例。
  • person”和“anotherReferenceToTheSamePersonObject”都具有相同的3bad086a值。
  • 不要尝试此操作,但person==其他引用此MePersonObject将为真。
  • 两个变量都有相同的引用副本,它们都引用相同的Person对象,堆上的相同对象,而不是副本。

一张图片胜过千言万语:

请注意,另一个指向MePersonObject箭头的引用指向对象,而不是变量person

如果你没有得到它,请相信我,记住最好说Java是通过值传递。那么,通过参考值。哦,更好的方法是*传递变量值的副本!;)***
现在,请随意憎恨我,但请注意,鉴于此
在讨论方法参数时,传递原始数据类型和对象**之间没有区别。
您总是传递引用值位的副本!

  • 如果是原始数据类型,这些位将包含原始数据类型本身的值。
  • 如果它是一个对象,这些位将包含地址的值,告诉JVM如何到达该对象。

Java是按值传递的,因为在方法中,您可以随意修改引用的对象,但无论您如何努力,都无法修改传递的变量,该变量将继续引用(而不是p___!
上面的changeName函数将永远无法修改传递的引用的实际内容(位值)。换句话说,changeName不能使Person-Person引用另一个对象。
当然,你可以简略地说,Java是通过值传递的

eyh26e7m

eyh26e7m28#

Java总是按值传递,没有例外,ever
那么,怎么会有人对此感到困惑,并认为Java是按引用传递的,或者认为他们有一个Java作为按引用传递行为的示例?关键的一点是,在任何情况下,Java都不会提供对对象本身的值的直接访问。对对象的唯一访问是通过对该对象的引用。因为Java对象“总是”通过引用而不是直接访问,所以通常将字段和变量以及方法参数称为“对象”*,而实际上它们只是“对象的引用”*混淆源于术语的这种变化(严格来说,是不正确的)
因此,在调用方法时

  • 对于基元参数(int、e1d1e等),传递值是基元的实际值(例如,3)。
  • 对于对象,传递值是对象引用*的值。

因此,如果您有doSomething(foo)public void doSomething(Foo foo) { .. },那么这两个Foo已经复制了指向相同对象的引用
自然,通过值传递对对象的引用看起来很像(实际上与)通过引用传递对象。

v6ylcynt

v6ylcynt29#

Java总是通过值传递参数,而不是通过引用。
让我通过example来解释这一点:

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");
     }

}

我将分步骤对此进行解释:
1.声明类型为Foo的名为f的引用,并将其分配给具有属性"f"的类型为Foo的新对象。

Foo f = new Foo("f");

1.从方法方面,声明名称为aFoo类型的引用,并将其初始分配为null

public static void changeReference(Foo a)

1.当您调用方法changeReference时,将为引用a分配作为参数传递的对象。

changeReference(f);

1.声明Foo类型的名为b的引用,并将其分配给具有属性"b"Foo类型新对象。

Foo b = new Foo("b");

1.a = b对属性为"b"的对象的引用a而不是f进行新的赋值。

1.当您调用modifyReference(Foo c)方法时,将创建引用c,并将其分配给属性为"f"的对象。

1.c.setAttribute("c");将更改引用c指向它的对象的属性,并且它与引用f指向的对象相同。

我希望您现在理解了在Java中作为参数传递对象的工作方式:)

xmq68pz9

xmq68pz930#

我刚刚注意到你引用了my article
Java规范说,Java中的一切都是按值传递的。Java中没有“通过引用传递”这样的东西。
理解这一点的关键是

Dog myDog;

不是狗;它实际上是一个指向狗的指针。在Java中使用术语“reference”是非常误导人的,也是造成这里大部分混淆的原因。他们称之为“引用”的行为/感觉更像大多数其他语言中我们称之为的“指针”。
这意味着,当你

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设置为值42
  • 在“AAA”行
  • someDog之后是它指向的Dog(地址42处的e1d 6d1e对象)
  • Dog(地址42)被要求将他的名字改为Max
  • 在“BBB”行
  • 创建一个新的Dog。假设他在地址74
  • 我们将参数someDog赋给74
  • 在“CCC”行
  • someDog被跟踪到它指向的Dog(地址74处的e1d 11d1e对象)
  • 要求Dog(地址74)将他的名字改为Rowlf
  • 然后,我们回来

现在,让我们考虑一下方法之外发生了什么:

  • myDog有变化吗*

钥匙在那里。
记住myDog是一个指针,而不是实际的Dog,答案是NO。myDog仍然有值42;它仍然指向原来的Dog(但请注意,由于行“AAA”,它的名称现在是“Max”-仍然是同一只狗;myDog的值没有改变。)
跟随一个地址并更改其末尾的内容是完全有效的;然而,这不会改变变量。
Java的工作原理与C完全相同。您可以分配一个指针,将指针传递给一个方法,跟随方法中的指针并更改所指向的数据。但是,调用者不会看到您对指针指向的位置所做的任何更改。(在具有pass-by-reference语义的语言中,方法函数可以更改指针,调用者将看到该更改。)
在C++、Ada、Pascal和其他支持按引用传递的语言中,实际上可以更改传递的变量。
如果Java具有按引用传递语义,那么当在BBB行上分配someDog时,我们在上面定义的foo方法将更改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都是通过引用“模拟”传递的。它们仍然在传递值(指向int或数组的指针),并在被调用函数中跟踪这些指针来操作数据。
通过引用传递的所有内容都是关于函数声明/定义,以及它如何处理其参数。引用语义适用于对该函数的every调用,调用站点只需要传递变量,不需要额外的数据结构。
这些模拟需要呼叫站点和功能协同工作。毫无疑问,它是有用的,但它仍然是按价值传递的。

相关问题