我一直以为Java是 通过按引用。
但是,我看过几篇博文(例如, 这个博客)声称它不是。
我不认为我理解他们所做的区别。
解释是什么?
Java总是如此 通过按值。不幸的是,他们决定将对象的位置称为“引用”。当我们传递一个对象的值时,我们正在传递 参考 它。这对初学者来说很困惑。
它是这样的:
public static void main(String[] args) {
Dog aDog = new Dog("Max");
// 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
}
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"
。价值 aDog
中 main
在功能中没有改变 foo
随着 Dog
"Fifi"
因为对象引用是按值传递的。如果通过引用传递,那么 aDog.getName()
在 main
会回来的 "Fifi"
打电话给 foo
。
同样:
public static void main(String[] args) {
Dog aDog = new Dog("Max");
foo(aDog);
// when foo(...) returns, the name of the dog has been changed to "Fifi"
aDog.getName().equals("Fifi"); // 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
本身(除非 d
被改为指向另一个 Dog
例如 d = new Dog("Boxer")
)。
我刚注意到你引用了 我的文章。
Java规范说Java中的所有内容都是按值传递的。在Java中没有“pass-by-reference”这样的东西。
理解这一点的关键是类似的东西
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
设置为值42someDog
紧随其后 Dog
它指向( Dog
地址对象42)Dog
(地址为42的那个)被要求将他的名字改为MaxDog
被建造。让我们说他在地址74someDog
到74 Dog
它指向( Dog
地址对象74)Dog
(地址为74的那个)被要求将他的名字改为Rowlf现在让我们考虑一下方法之外会发生什么:
难道 myDog
更改?
关键是。
记住这一点 myDog
是一个 指针,而不是实际的 Dog
, 答案是不。 myDog
仍然有42的价值;它仍然指向原始的 Dog
(但请注意,由于行“AAA”,它的名字现在是“Max” - 仍然是相同的狗; myDog
的价值没有改变。)
这完全有效 跟随 一个地址,并改变它的结尾;但是,这不会改变变量。
Java的工作方式与C完全相同。您可以分配指针,将指针传递给方法,按照方法中的指针操作并更改指向的数据。但是,您无法更改指针指向的位置。
在C ++,Ada,Pascal和其他支持pass-by-reference的语言中,您实际上可以更改传递的变量。
如果Java具有pass-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
并将其分配给类型的新对象 Foo
有一个属性 "f"
。
Foo f = new Foo("f");
从方法方面来看,类型的引用 Foo
有一个名字 a
声明并且它最初被分配给 null
。
public static void changeReference(Foo a)
当你调用方法时 changeReference
, 参考资料 a
将被分配给作为参数传递的对象。
changeReference(f);
声明一个名为的引用 b
类型 Foo
并将其分配给类型的新对象 Foo
有一个属性 "b"
。
Foo b = new Foo("b");
a = b
正在重新分配参考 a
不 f
到其属性为的对象 "b"
。
你打电话的时候 modifyReference(Foo c)
方法,参考 c
创建并使用属性分配给对象 "f"
。
c.setAttribute("c");
将更改引用的对象的属性 c
指向它,它与引用的对象相同 f
指向它。
我希望你现在明白如何将对象作为参数传递在Java中:)
这将为您提供有关Java如何工作的一些见解,以至于在您下次讨论Java通过引用传递或通过值传递时,您只需微笑:-)
第一步请从脑海中删除以'p'“_ _ _ _ _ _ _”开头的单词,特别是如果您来自其他编程语言。 Java和'p'不能写在同一本书,论坛甚至txt中。
第二步记住,当你将一个Object传递给一个方法时,你传递的是Object引用,而不是Object本身。
现在想想Object的引用/变量是什么/是什么:
在下面(请不要尝试编译/执行此...):
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箭头指向Object而不是指向变量person!
如果你没有得到它,那么请相信我,并记住最好这么说 Java是按值传递的。好, 通过参考值传递。哦,更好的是 传址复制的最值变量的值! ;)
现在随时恨我,但请注意这一点 传递原始数据类型和对象之间没有区别 在谈论方法论证时。
您总是传递参考值的位副本!
Java是按值传递的,因为在方法中你可以根据需要修改引用的对象,但无论你怎么努力,你都永远无法修改将继续引用的传递变量(不是p _ _ _ _ _ _ _)同样的对象无论如何!
上面的changeName函数永远不能修改传递的引用的实际内容(位值)。换句话说,changeName不能使Person人引用另一个Object。
当然你可以缩短它,然后说出来 Java是按值传递的!
Java总是按值传递,没有例外, 曾经。
那么如何让任何人都对此感到困惑,并相信Java是通过引用传递的,或者认为他们有一个Java作为传递参考的例子?关键是Java 决不 提供对价值的直接访问 对象本身,在 任何 情况。对象的唯一访问是通过a 参考 到那个对象。因为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
,由于第二个String的地址与第一个String的地址相同,因此不会创建新的String并且不会分配新的堆空间,但会在堆栈上创建新的标识符。然后我们打电话 shout()
:创建一个新的堆栈框架和一个新的标识符, name
创建并分配已存在的String的地址。
那么,价值,参考?你说“土豆”。
只是为了显示对比,请比较以下内容 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只有两种类型的传递:内置类型的值,以及对象类型的指针值。
Java按值传递对象的引用。