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(...)
内部设置的。任何操作是foo
上执行d
是这样的,对于所有的实际目的,它们被执行aDog
,但它是不可能改变的变量的值aDog
本身。
我只是注意到您引用了我的文章 。
Java 规范说 Java 中的所有内容都是按值传递的。 Java 中没有 “通过引用传递” 这样的东西。
理解这一点的关键是
Dog myDog;
不是狗它实际上是指向狗的指针 。
这意味着什么时候
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 someDog
之后到Dog
它指向(在Dog
对象在地址 42) Dog
(地址为 42 的那只Dog
)被要求将他的名字改成 Max Dog
。假设他在地址 74 someDog
分配给 74 Dog
它指向(在Dog
对象在地址 74) Dog
(地址为 74 的那只Dog
)被要求将他的名字改成 Rowlf 现在,让我们考虑一下方法外发生的情况:
我的myDog
变了吗?
有钥匙
请记住, myDog
是一个指针 ,而不是实际的Dog
,答案是否定的。 myDog
的值仍然为 42;它仍指向原始的Dog
(但请注意,由于行 “AAA”,其名称现在为 “Max”- 仍是同myDog
Dog; myDog
的值未更改。)
跟随地址并更改地址末尾是完全有效的;但这不会更改变量。
Java 的工作原理与 C 完全相同。您可以分配一个指针,将指针传递给方法,在该方法中跟随指针并更改所指向的数据。但是,您不能更改该指针指向的位置。
在 C ++,Ada,Pascal 和其他支持按引用传递的语言中,实际上可以更改传递的变量。
要是 Java 通 - by-reference 语义中, foo
我们在上面定义的方法会改变其中myDog
指着时分配someDog
上线 BBB。
将参考参数视为传入变量的别名。分配别名后,传入变量也将被分配。
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");
从方法方面,声明了名称为a
的类型为Foo
的引用,并将其初始分配为null
。
public static void changeReference(Foo a)
调用方法changeReference
,将为引用a
分配对象,该对象作为参数传递。
changeReference(f);
声明一个名为b
的引用,类型为Foo
并为其分配一个具有属性"b"
的类型为Foo
的新对象。
Foo b = new Foo("b");
a = b
对属性为"b"
的对象的引用a
而不是 f
进行新赋值。
当您调用modifyReference(Foo c)
方法时,将创建引用c
并为该对象分配属性"f"
。
c.setAttribute("c");
会更改引用c
指向它的对象的属性,并且它是引用f
指向它的对象。
我希望您现在了解如何将对象作为参数传递给 Java :)
这将使您对 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. }
怎么了?
一张图片胜过千言万语:
请注意,anotherReferenceToTheSamePersonObject 箭头指向对象而不是变量人!
如果您没有得到它,那就请相信我,并记住,最好说Java 是通过价值传递的 。好吧, 通过参考值 。哦,更好的是通过变量值的传递! ;)
现在,请随时恨我,但请注意,鉴于此,在谈论方法参数时传递原始数据类型和对象之间没有区别 。
您总是传递参考值的位的副本!
Java 是按值传递的,因为您可以在方法内部随意修改所引用的对象,但是无论尝试多么努力,您都永远无法修改将保持引用的传递变量(而不是 p _ _ _ _ _ _ _)相同的对象,无论如何!
上面的 changeName 函数将永远无法修改传递的引用的实际内容(位值)。换句话说,changeName 不能使 Person 人引用另一个 Object。
当然,您可以简而言之,只是说Java 是按价值传递的!
Java 的始终是按值传递,没有例外, 永远 。
那么,如何让所有人对此感到困惑,以为 Java 是通过引用传递的呢,还是认为他们有一个 Java 充当引用传递的示例呢?关键是,在任何情况下,Java 都不提供对对象本身的值的直接访问。对对象的唯一访问是通过对该对象的引用 。因为 Java 对象始终通过引用而不是直接访问来访问,所以通常将字段和变量以及方法参数称为对象 ,而在学究上它们仅是对象的引用 。 混淆源于命名上的这种变化(严格来说,是错误的)。
因此,在调用方法时
int
, long
等),按值传递是基本参数的实际值 (例如 3)。 因此,如果您有doSomething(foo)
和public void doSomething(Foo foo) { .. }
则两个 Foos 都复制了指向相同对象的引用 。
自然地,通过值传递对对象的引用看起来非常像(实际上在实践中是无法区分的)通过引用传递对象。
Java 通过值传递引用。
因此,您无法更改传入的引用。
我觉得争论 “按引用传递与按值传递” 不是很有帮助。
如果您说 “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);
}
创建一个 String 并在堆中为其分配空间,并将该字符串的地址存储在堆栈中,并hisName
指定标识符hisName
,因为第二个 String 的地址与第一个 String 相同,因此不会创建新的 String 并且没有分配新的堆空间,但是在堆栈上创建了一个新的标识符。然后我们调用shout()
:创建一个新的堆栈框架,并创建一个新的标识符, name
,并为其分配已经存在的 String 的地址。
那么,价值,参考?您说 “土豆”。
在 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 按值传递对对象的引用。
基本上,重新分配 Object 参数不会影响参数,例如,
private 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!"
的引用"Hah!"
。如果它是实际的引用本身,那么foo
会将baz
重新定义为null
。