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