术语“按值传递”和“按引用传递”在计算机科学中具有特殊、精确定义的含义。这些含义与许多人第一次听到这些术语时的直觉不同。这次讨论中的大部分混乱似乎都来自这一事实。
术语“按值传递”和“按引用传递”正在讨论变量。按值传递意味着变量的值被传递给函数/方法。按引用传递意味着对该变量的引用将传递给函数。后者为函数提供了一种更改变量内容的方法。
根据这些定义,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
}
在上面的示例中仍将返回 。在函数中,当对象引用按值传递时,不会更改其中的值。如果它是通过引用传递的,则在调用 后,in 将返回。aDog.getName()"Max"aDogmainfooDog"Fifi"aDog.getName()main"Fifi"foo
同样:
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");
}
在上面的示例中,是狗的名字在调用后,因为对象的名称是在 .对 执行的任何操作都是这样,出于所有实际目的,它们都是在 上执行的,但不可能更改变量本身的值。Fififoo(aDog)foo(...)foodaDogaDog
有关按引用传递和按值传递的详细信息,请参阅以下答案:https://stackoverflow.com/a/430958/6005228。这更彻底地解释了两者背后的语义和历史,也解释了为什么Java和许多其他现代语言在某些情况下似乎同时做到了这两点。
我刚刚注意到你引用了我的文章。
Java Spec说Java中的所有内容都是按值传递的。Java中没有“通过引用传递”这样的东西。
理解这一点的关键是,像这样的东西
Dog myDog;
不是狗;它实际上是指向狗的指针。在Java中使用术语“引用”是非常具有误导性的,并且是导致这里大多数混淆的原因。他们所谓的“引用”的行为/感觉更像是我们在大多数其他语言中所谓的“指针”。
这意味着,当你有
Dog myDog = new Dog("Rover");
foo(myDog);
您实际上是将创建对象的地址传递给方法。Dogfoo
(我这么说主要是因为Java指针/引用不是直接地址,但以这种方式思考它们是最简单的。
假设对象驻留在内存地址 42。这意味着我们将 42 传递给该方法。Dog
如果将方法定义为
public void foo(Dog someDog) {
    someDog.setName("Max");     // AAA
    someDog = new Dog("Fifi");  // BBB
    someDog.setName("Rowlf");   // CCC
}
让我们来看看发生了什么。
- 参数设置为值 42someDog
- 在“AAA”行- 
someDog跟随到它指向(地址 42 处的对象)DogDog
- (地址42中的那个)被要求将他的名字更改为Max。Dog
 
- 
- 在行“BBB”- 将创建一个新的。假设他在地址74Dog
- 我们将参数分配给 74someDog
 
- 将创建一个新的。假设他在地址74
- 在“CCC”行- someDog 被跟踪到它指向(地址 74 处的对象)DogDog
- (地址74的那个)被要求将他的名字改为RowlfDog
 
- someDog 被跟踪到它指向(地址 74 处的对象)
- 然后,我们返回
现在让我们考虑一下方法之外会发生什么:
myDog改变了吗?
这是关键。
请记住,这是一个指针,而不是一个实际的,答案是否定的。 仍然具有值 42;它仍然指向原始版本(但请注意,由于行“AAA”,它的名字现在是“Max” - 仍然是同一个狗;的值未更改。myDogDogmyDogDogmyDog
遵循地址并更改其末尾的内容是完全有效的;但是,这不会更改变量。
Java的工作方式与C完全相同。您可以分配指针,将指针传递给方法,跟随方法中的指针并更改指向的数据。但是,调用方将看不到您对该指针指向的位置所做的任何更改。(在具有按引用传递语义的语言中,方法函数可以更改指针,调用方将看到该更改。
在C++、Ada、Pascal 和其他支持按引用传递的语言中,您实际上可以更改传递的变量。
如果Java具有逐引用传递语义,那么我们上面定义的方法在BBB行上分配时会改变指向的位置。foomyDogsomeDog
将引用参数视为传入变量的别名。分配该别名后,传入的变量也是如此。
更新
评论中的讨论值得一些澄清...
在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 都在模拟按引用传递。它们仍然在传递值(指向整数或数组的指针),并在被调用的函数中跟随这些指针来操作数据。
通过引用传递是关于函数声明/定义,以及它如何处理其参数。引用语义适用于对该函数的每次调用,并且调用站点只需要传递变量,不需要额外的数据结构。
这些模拟需要调用站点和函数进行协作。毫无疑问,它是有用的,但它仍然是按值传递的。
- 
					    	
					    				    		  
- 
					    	
					    				    		  
- 
					    	为什么修改了 ArrayList 参数,而不是 String 参数? (93 回答) 9年前关闭。 在 的情况下,添加的元素将被检索。如果方法调用对传递的字符串没有影响。JVM 到底在做什么?任何人都可以详细解  
- 
					    	数组列表和修改其中包含的对象 假设我们有一个ArrayList myArray。我想通过调用对象的函数来修改它。如果我这样做,原始对象是否会被更改? 为了进一步澄清 - 我担心get()是否真的返回对我的原始对象的引用,或者它是否  
- 
					    	Java 对象赋值 我是Java的新手,我有一些关于对象分配的问题。例如 我通过在String上测试案例来意识到我的错误,因为它是不可变的。我认为修改字符串的情况实际上是将“1”的引用返回到s1。尽管如此,  
 
					