Java是”通过引用传递”还是”传递价值”?
我一直认为Java是传递引用的.
但是,我看过一些博客文章(例如,这个博客)声称它不是.
我不认为我理解他们所做的区别.
解释是什么?
回答
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"
.值aDog
内main
未在功能改变foo
与Dog
"Fifi"
作为对象基准由值来传递.如果它是通过引用传递的,则aDog.getName()
in 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
本身.
- 但是有一个微妙的区别.看看第一个例子.如果纯粹通过引用传递,aDog.name将是"Fifi".它不是 - 你得到的参考是一个值引用,如果在退出函数时将被覆盖,将被恢复.
- @Lorenzo:不,在Java中,一切都是按值传递的.基元按值传递,对象引用按值传递.对象本身永远不会传递给方法,但是对象总是在堆中,只有对象的引用传递给方法.
- 我尝试了一种可视化对象传递的好方法:想象一下气球.调用fxn就像将第二个字符串绑定到气球并将该行传递给fxn.parameter = new Balloon()将剪切该字符串并创建一个新的气球(但这对原始气球没有影响).parameter.pop()仍然会弹出它,因为它跟随字符串到同一个原始气球.Java是按值传递的,但传递的值不是很深,而是处于最高级别,即基元或指针.不要将其与完全克隆并传递对象的深度传递混淆.
- 这个问题与内部细节有点混淆吗?假设你的意思是"指向对象的内部指针的值","传递引用"和"传递引用的值"之间没有概念上的区别.
- 令人困惑的是,对象引用实际上是指针.在开始时,SUN称它们为指针.然后营销告知"指针"是一个坏词.但是你仍然可以在NullPointerException中看到"正确的"命名法.
- To me, saying that an object's reference is passed by value is the same as saying that the object is passed by reference. I'm a Java novice, but I presume that (in contrast) *primitive data* is pass by value.
- @BillR我有一个comp.sci master和C和C++中的学习编程.我已经构建了编译器和CPU模拟器.我认为我对语言理论和低级系统动力学都有一个很好的把握.我可以毫无疑问地说你错了.传递指针不是通过引用传递的.指针本身是一个数字,您可以通过值或引用传递.在C++中,你可以做到这两点.在Java中,您只能通过值传递.已经向您展示了您无法更改持有指针的变量.您只能跟随指针,但这不会更改调用方法.
- @ubershmekel - 不,那不对.Java中的"按值传递引用"与在C++中"按值传递指针"完全相同.在C++中通过引用传递允许被调用的函数/方法实际更改用于传递值的变量.通过值传递总是允许被调用的函数/方法读取并遵循由值传递的指针...
- Java修改了C/C++等语言的"通过引用传递"的含义.在C/C++中,术语"通过引用传递"意味着传递变量地址的值(*这是指针是什么!*),而不是变量本身.您在内存中的'xyz'地址处更改了对象/变量(您更改了原始内容).这是在更改原件时完成的(为什么要通过整个过程?).传递值传递*实际对象/变量*的副本以保留原始对象.Java没有提供选项.OO对象通过引用传递(以C/C++术语表示),原语通过值传递.
- @dbrewster i'm sorry but ... "Fifi" is not among us anymore
- @BillR - C严格按值传递; C++添加了pass-by-reference语义,但绝不是第一种实现这些语义的语言.术语"传递引用"具有非常特定的含义,Java根本不实现它.保持术语简单和实际意义,更容易理解和解释.Java按值传递所有内容,包括指针.(如果你只是简单地避免在Java中使用术语"引用",这也是最简单的...它们是指针......)
- 不是说"对象引用是按价值传递"只是人们说"通过引用传递"时的意思吗?我的意思是你不会叫`void foo(int&a){a = 5; 在C++中"传递值",但它与Java完全相同.
- Java是变量值的副本.传递基元或对象没有区别!唯一的区别是原始变量直接包含其值,而对象变量包含有助于JVM在堆中查找对象的位值.此外,在这两种情况下,这些参考值在被赋予方法之前被复制.最后,请不要在谈论Java时提及它,甚至不要说这样做是不正确的!
- @BillR不是这样.Java通过(新L)值.术语传递/(新L)值/宏是由Scott-Strachey编程语言理论定义的术语(例如),Java肯定是通过(新L)值.您的小测试示例未更新调用者的变量,但可能更新了调用者变量引用的对象.
- 听起来你只是***通过参考***?我将支持Java仍然是一种复制的传递引用语言这一事实.它是复制引用的事实不会改变术语.两个REFERENCES仍指向同一个对象.这是纯粹主义者的论点......
- @Scott Stanchfield当我将值从方法/函数1传递给方法/函数2时,我应该看到由1中的值组成的副本并发送,如果我更改2中的值,我看不到1的变化.不是它在Java中的工作方式.我已经完成了,几分钟前刚刚完成,将一个变量传递给另一个方法,并将其更新并让它更新了我的调用方法中的值.这时我想要这种行为.但有时这可能是一个混乱的问题,当一个人没有找到它时,它可能会导致这种影响.这确实是通过内存引用传递的.
- @BillR令人难以置信的是,尽管答案非常明确,但你仍然在反对它.Java只传递指针.指针是一种opaque类型,我们可以将其视为一个原始数字.此数字按值传递.所以Java传递指针,那些指针是按值传递的.
- @MikeBraun如果你真的使用指针学习编程(比如使用C语言),你会明白当你使用指针作为函数参数时,你是"通过引用传递".您指的是使用指针在内存中的特定位置.即通过指向您正在引用它的内存位置.您需要多少种方式来理解这个概念?你是否忽视了对我有利的观点,或者只是不理解它们?或者更糟糕的是,他们不理解他们,因为你不想理解他们?
- @Shawn Java确实*不*表现得像C++`func(Dog&d)`.如果你在函数`func(Dog&d)`的主体中重新分配`d`,那么用于调用该函数的对象也将被重新赋值.它的行为更像`func(Dog*d)`.如果将指针重新分配给函数体,则不会重新分配传递给函数的原始指针.
- so what happens to "Fifi" in the 1st example? Does it cease to exist, was it never created, or does it exist in the heap but without a reference variable in the stack?
- 是的Timmmm,我认为erlando的反例在C++引用的上下文中是错误的.的确如此,在Java中,它们"按值传递引用",但它意味着与"C++程序员"通过引用传递时所期望的完全相同.
- @Scott Stanchfield和C并没有严格按价值转移.看看传值和参考链接路过这里:http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=%2Fcom.ibm.xlcpp8a.doc%2Flanguage%2Fref% 2Fcfpv.htm查看传递值的链接.这是其他编程语言定义它的方式.Java所做的是通过引用调用,因为你在第二个函数(方法)中所做的事情确实改变了调用/第一个函数中的变量.Java可以撒尿在我的腿上然后告诉我下雨了,但事实并非如此.Java已经选择了"按值传递"一词.
- 因此,通过引用传递指针意味着如果您然后重新分配该引用(在您传递给它的代码块中),您将更改该实际指针值,从而有效地将其与其指向的原始对象切断,渲染该对象有资格进行垃圾收集(如果没有其他指针指向它).这是你不能用Java做的,因为你永远不会传递指针的引用,只是指针值的副本.重新分配副本仅指向其他地方的副本.原始指针保持不变.
- Java是"pass-by-voodoo-doll",当一个对象在调用方法时被传递时,该方法实际上接收一个巫毒娃娃,使用它可以控制实际对象,但不能完全替换该对象.
- 所有这些争论实际上都是因为Sun的家伙犯了一个可怕的命名错误.他们使用名称REFERENCE而不是POINTERS.嗯,这实际上不是一个错误.根据一个与原始Sun团队中的另一个人合作的人,销售部门要求使用参考词而不是指针.销售部门希望将Java作为一种安全的语言推广,而广告的优势之一就是"Java的优势在于它不像C++那样允许指针运算".
- @user36800: You're wrong. Did you work through the example with Fifi and look carefully through the results? Check that indeed `foo(aDog);` did **not** change `aDog` despite `foo` overwriting the value of `d`, showing that indeed all inputs to a function are passed by value.
- @user36800: Well, both statements are wrong. To pass an object by reference would mean that if the function modifies the variable then it modifies the object itself. That is not what happens in Java; objects cannot be passed by reference, but instead one can only pass references as inputs to a function, and when a function performs `d = new Dog("Fifi");` it overwrites the input variable `d`, which **stores** a reference but is not 'the object passed by reference'. Contrast with `&d` in the function signature in C, which would be pass-by-reference. [cont]
- 但请注意,整数类型是按值传递的,而不是通过引用传递的.另外,不可变类型通过引用传递,但"kinda"用作按值传递它们:最常见的示例是String.这允许对不可变对象进行内部空间优化.
- SóJava通过值传递对象的引用?
- 如果传递给构造函数的名称与`foo`方法中使用的名称不同,那么您的观点可能更清楚.
- BlueRaja,不是任何可以在法庭上占据的东西,这可能是因为我的记忆可能存在缺陷,但是可以在这里找到一些证据:http://java.sun.com/docs/books/jls/third_edition/html/ typesValues.html#4.3.1
- @JohnStrickler我已经看到了一个最近的例子,我的同事在调用方法结束时"归零"了对象参数,并期望在调用者中看到它"无效".所以,你可能是'纯粹主义',但很多人仍然忽略它(并且仍然会产生代码行).
- 您可以将Java中的对象声明视为C/C++指针(它们引用对象,它们可以为null等).人们声称Java完全是按值传递的,因为这些引用本身是按值传递的(与通过值传递C/C++指针的方式相同 - 指针被复制,但指向的对象不是).因此,如果您考虑将C/C++中的指针传递给传递引用,那么Java就是传递引用.例外情况是int,它总是在Java中按值传递(复制).
- 有一个C/C++背景,我认为它是**传递_value _**:`func(狗d)`,**传递_reference _**:`func(Dog&d)`,**传递_pointer_**:`func(Dog*d)`.Java对我的行为就像**传递_reference _**,即使在编译器说话中暗示调用者在内存中的对象位置可以被替换,也就是**传递_pointer _**.每当我听到Java的对象被_value _**传递时,它就会让我觉得有一个浅的副本没有.所以它是一个C++参考v.compiler-speak**传递_reference _**似乎有冲突.
- @BillR:"Java修改了像C/C++这样的语言'通过引用传递'的含义......"并且存在误解的根源.Java不会比我更"修改'通过引用传递'的含义".该术语已经在计算机科学中具有意义.试图说服人们"通过引用传递"在不同语言中具有不同的含义是无用的和令人困惑的.
- 对不起,但是你可以重新定位引用的事实并没有改变你收到的是对实际对象的引用这一事实.基本上,这个定义的整个问题是它意味着在任何语言中都没有通过引用传递的东西.根据这种推理,所有语言都按值传递.
- 好的,所以foo(&x)与foo(*x)没什么区别.它们都通过值传递引用,因此根据Java的定义,它们都是按值传递的.通过引用传递的全部内容从来都不是你传递的实际值是如何传递的,而是关于用户希望你传递的对象的方式.这个定义使其毫无意义.除了价值之外,通过任何方式传递任何东西是完全不可能的.
- @izb**指向对象的指针被复制的事实**与**对象通过引用**传递的概念不同.因为它们不是通过引用传递的,所以它们的引用被复制.
- 长话短说,问题是使用相同的单词"引用对象"与"引用变量"
- 我不得不低估这个答案,因为它是自相矛盾的.答案,甚至是一些支持性的评论者,都支持这样的观点:"这一切都取决于你如何定义一个引用,所以当它们说它不是"通过引用传递"时,它只是在论证语义,而不是语言机制.然后继续说"我们正在通过它的引用." 支持"这是*不*通过引用传递"的答案和评论都使用这个措辞.再一次,直接引用答案:"我们正在将*引用*传递给它." (具有讽刺意味的是,重点是回答者,而不是我的)
- 在我心中,我与BillR的拥护者在一起……但是,让我大吃一惊:考虑到原始对象指针并没有丢失,而是在函数退出后保留了下来。这就是为什么要区分它们的原因-他们是说在将引用/指针值传递给函数时进行COPY,然后可以将其覆盖。我仍然不喜欢称其为传递值,但是有一些编程理论的人说是这样,所以我们都必须同意。
- @zeroflagL这就是问题所在.永远不可能传递变量本身.在你的例子中(foo(&x)),你没有传递x.你传递了对x的引用.根据这个定义,没有通过引用这样的东西,因为它永远不会被完成.
- 说Java使用半引用是否安全,因为修改会改变原始对象?
- @Shawn,我认为你同意Java是值得传递的,如果你认为Java`Dog`隐含地对应于C++`Dog*`.如果你考虑所有初始化都包含`new`这一事实是有意义的,如下所示:`Dog d = new Dog();`Java中的变量永远不能保存一个对象.
- 正如该答案所指出的那样,“通过引用传递”与“通过引用传递”是不同的(仍然可以通过Java等值传递语言来完成)。许多评论者未能理解差异。
- 我懂了。Java传递一个指针,一个引用或一个值。
我刚刚注意到你引用了我的文章.
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
设置为值42 - 在线"AAA"
-
someDog
跟随Dog
它指向(Dog
地址42处的对象) - 要求
Dog
(地址为42的那个)将他的名字改为Max
-
- 在线"BBB"
-
Dog
创建了一个新的.让我们说他在地址74 - 我们将参数分配
someDog
给74
-
- 在线"CCC"
- someDog跟随
Dog
它指向(Dog
地址74处的对象) - 要求
Dog
(地址为74的那个)将他的名字改为Rowlf
- someDog跟随
- 然后,我们回来了
现在让我们考虑一下方法之外会发生什么:
没有myDog
改变?
关键是.
记住这myDog
是一个指针,而不是一个实际的Dog
,答案是否定的.myDog
仍然有42的价值; 它仍然指向原始Dog
(但请注意,因为行"AAA",它的名字现在是"Max" - 仍然是相同的狗;它myDog
的值没有改变.)
按照地址并改变最后的内容是完全有效的; 但是,这不会改变变量.
Java的工作方式与C完全相同.您可以分配指针,将指针传递给方法,按照方法中的指针操作并更改指向的数据.但是,您无法更改指针指向的位置.
在C++,Ada,Pascal和其他支持pass-by-reference的语言中,您实际上可以更改传递的变量.
如果Java具有pass-by-reference语义,那么foo
我们在上面定义的方法会在BBB上myDog
分配的位置发生变化someDog
.
将引用参数视为传入的变量的别名.当分配该别名时,传入的变量也是如此.
- 这样想吧.有人在一张名为"annArborLocation"的纸条上写着密歇根州安娜堡(我的家乡,GO BLUE!)的地址.你把它复制在一张名为"myDestination"的纸上.你可以开车去"myDestination"并种一棵树.您可能在该位置更改了有关城市的信息,但它并未更改任何纸张上写入的LAT/LON.您可以在"myDestination"上更改LAT/LON,但不会更改"annArborLocation".这有帮助吗?
- 这就是为什么常见的副词"Java没有指针"是如此误导.
- 你错了,imho."记住myDog是一个指针,而不是一个真正的狗,答案是否定的.我的狗仍然有42值;它仍然指向原来的狗." myDog的值为42,但其名称参数现在包含"Max",而不是// AAA行上的"Rover".
- @Scott Stanchfield:我读了大约一年前的文章,这对我来说真的很有帮助.谢谢!我可以谦卑地建议一点补充:你应该提到的是,实际上有一个特定的术语描述了这种形式的"价值呼唤,其中价值是参考",Barbara Liskov发明了这种形式,用于描述她的CLU语言的评估策略. 1974年,为了避免混淆,例如你的文章解决的问题:*通过共享调用*(有时称为*通过对象共享调用*或简单地*通过对象调用*),这几乎完美地描述了语义.
- @Gevorg - C/C++语言不具有"指针"的概念.还有其他语言使用指针但不允许C/C++允许的相同类型的指针操作.Java有指针; 他们只是为了防止恶作剧.
- 说Java是通过价值传递既无意义又令人困惑.计算机上的所有东西都是按价值传递的.在Java中,当更改传入的数组时,它会更改原始数组,因此数组不会按值传递,而是通过引用传递.指针是按值传递的.这取决于您使用的抽象深度和定义.在C++中,当您更改指针时,您仍然通过一个值(它的内存位置)访问该指针,但是您不会通过值来调用它.
- @Scott,我想这更好地解释了我的观点:如果你看到有人接受Java作为参考传递,你可以展示一段破坏该理论的代码.但是,如果你看到有人接受Java作为值传递,我可以根据他们对"按值传递"的较低层次理解来展示破坏该理论的代码.这里的术语有问题.我认为这个问题的最佳答案是描述每个"传递"方法以及隔离它们的适当命名约定.
- 我认为我们不需要另一个术语."按值传递"适当地涵盖了这一点.Liskov摇滚(我想知道是否有人曾对她说过那些确切的话?),但是"通过(对象)分享呼叫"并没有增加任何新内容.我们可以添加一个相应的"原语调用",它也没有添加任何新东西.我想在这里申请KISS规则......
- 好文章.我不喜欢和Java一起讨论指针.当人们知道Java及其变量范围/生命周期如何工作时,引用就是要使用的术语.使用指针,人们开始想知道指向指针或给出引用是一个指针,关于以C++方式解引用引用.这不好.变量由位-a值组成,该值可以引用Object.也就是说,它告诉JVM如何在堆中找到一个Object.复制/复制此值并将其传递给方法变量,然后该变量可以找到相同的对象.不需要指针.
- @Matthew - "按价值传递"在编程语言设计中有一个非常具体的定义.并非所有参数都按值传递; 某些语言(如C++和Ada)实际上通过引用传递参数(如果在形式参数定义中使用&[C++]或"in out"[Ada]修饰符).
- @Comptrol指针没有改变;它的值仍然是42。如果它是按引用传递的,则它将更改为新狗Fifi(将其重命名为Rowlf,但指针不会第二次更改)的存储位置。最后,如果它是按引用传递的,则在该方法返回void之后,myDog将被称为Rowlf。
- 哇。简单而优雅。我得到了整个困境。
- 我完全同意你的看法; 但是,在第183页的“ Sun认证助理学习指南-2009年版”一书中,第5章“自测答案”的问题编号10表示:*“对象总是通过引用传递给方法。这意味着进行了更改。该方法中的对象将反映在调用该方法的代码中的对象中。” *我同意您的观点,但是对于此Java测试我应该选择什么答案?
- @BillR-根本不像宣传或在政治上是正确的...计算机语言设计对于按值传递和按引用传递具有非常精确的含义。如果您无法更改传递的实际内容(在这些示例中为Dog的指针),则为传递值。按照您的逻辑,C具有按引用传递语义(显然它没有)
- 看起来Java通过对其值的引用值传递对象.
- @dividebyzero使其更简单,使其更安全(例如,不可能走出数组的末端)。即使是优秀的C / C ++程序员,也会发生这种情况。
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");
}
}
我将逐步解释这个:
-
Foo f = new Foo("f");
-
public static void changeReference(Foo a)
-
changeReference(f);
-
Foo b = new Foo("b");
-
我希望你现在明白如何将对象作为参数传递在Java中:)
- +1好东西.好的图表.我还在这里找到了一个非常简洁的页面http://www.adp-gmbh.ch/php/pass_by_reference.html好吧我承认它是用PHP编写的,但它是了解我认为重要的差异的原则(和如何根据您的需要操纵这种差异).
- Java always passes arguments by value, but what you are passing by value is a reference to an object, not a copy of the object. Simple eh?
- @MrD当'a'指向同一个对象'f'也指向时,那么通过'a'对该对象所做的任何改变也可以通过'f'观察到,但是它不会改变'f'.'f'仍然指向同一个对象.你可以完全改变对象,但你永远不能改变'f'指向的对象.这是由于某种原因,有些人无法理解的根本问题.
- @ Eng.Fouad这是一个很好的解释,但如果`了`指向同一个对象`F`(和从来没有得到自己的对象`F`点的副本),对对象的任何更改都使用了``应该做修改`f`以及(因为它们都使用相同的对象),所以在某些时候`a`必须得到对象`f`指向的自己的*副本*.
- 这是我发现的最佳解释.顺便问一下,基本情况怎么样?例如,参数需要一个int类型,它是否仍然将int变量的副本传递给参数?
这将为您提供一些有关Java如何工作的见解,以至于在您下次讨论Java通过引用传递或通过值传递时,您只需微笑:-)
第一步请从脑海中删除以'p'"_ _ _ _ _ _ _"开头的单词,特别是如果您来自其他编程语言.Java和'p'不能写在同一本书,论坛,甚至txt中.
第二步记住,当你将一个Object传递给一个方法时,你传递的是Object引用,而不是Object本身.
- 学生:师父,这是否意味着Java是传递参考?
- 师父:蚱蜢,没有.
现在想想Object的引用/变量是什么/是什么:
- 变量保存位,告诉JVM如何到达内存中引用的Object(Heap).
- 将参数传递给方法时,您不会传递引用变量,而是传递引用变量中的位副本.像这样:3bad086a.3bad086a代表了一种获取传递对象的方法.
- 所以你只是传递3bad086a,它是参考的价值.
- 您传递的是引用的值而不是引用本身(而不是对象).
- 该值实际上是COPIED并赋予方法.
在下面(请不要尝试编译/执行此...):
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行中创建,并且在开头时为null.
- 在第2行创建一个新的Person对象,存储在内存中,并为变量person提供对Person对象的引用.那就是它的地址.比方说3bad086a.
- 保存Object的地址的变量person被传递给第3行中的函数.
- 在第4行,你可以听到沉默的声音
- 检查第5行的评论
- 创建一个方法局部变量 - anotherReferenceToTheSamePersonObject - 然后在#6行中产生魔力:
- 变量/引用人被逐位复制并传递给函数内的anotherReferenceToTheSamePersonObject.
- 没有创建Person的新实例.
- " person "和" anotherReferenceToTheSamePersonObject "都保持相同的3bad086a值.
- 不要试试这个,但是人== anotherReferenceToTheSamePersonObject会是真的.
- 两个变量都具有引用的IDENTICAL COPIES,它们都引用相同的Person对象,堆上的SAME对象而不是COPY.
一张图片胜过千言万语:
请注意,anotherReferenceToTheSamePersonObject箭头指向Object而不是指向变量person!
如果你没有得到它,那么只要相信我,并记住最好说Java是通过值传递的.那么,通过参考值传递.哦,更好的是传递变量值的副本!;)
现在可以随意讨厌我,但请注意,在讨论方法参数时,传递原始数据类型和对象之间没有区别.
你总是传递一份参考值的副本!
- 如果它是原始数据类型,则这些位将包含原始数据类型本身的值.
- 如果它是一个Object,那么这些位将包含告诉JVM如何到达Object的地址值.
当然,你可以缩短它,只是说 Java是值得传递的!
- @domanokz你杀了我,请不要再说这个词了!;)请注意,我可以回答这个问题而不说"参考".这是一个术语问题,而且会使情况变得更糟.不幸的是,我和苏格兰人对此有不同的看法.我认为你已经掌握了它在Java中是如何工作的,现在你可以称之为按值传递,按对象共享,按副本复制变量值,或者随意提出其他内容!只要你知道它的工作方式以及Object类型的变量中的内容,我就不在乎了!只是一个邮政信箱地址!;)
- 听起来你只是***通过参考***?我将支持Java仍然是一种复制的传递引用语言这一事实.它是复制引用的事实不会改变术语.两个REFERENCES仍指向同一个对象.这是纯粹主义者的论点......
- 你的意思是指针吗?..如果我弄错了,在`public void foo(Car car){...}`中,`car`是`foo`的本地,它包含Object的堆位置?因此,如果我通过`car = new Car()`更改`car`的值,它将指向堆上的不同Object?如果我通过`car.Color ="Red"`更改`car`的属性值,则会修改`car`指向的堆中的Object.另外,它在C#中是一样的吗?请回复!谢谢!
- 我想Java的设计考虑了指针.否则,为什么存在`NullPointerException`?然而,对于这个精彩的解释,获得指针只会让它变得复杂
- 所以请进入理论线#9`System.out.println(person.getName());`会出现什么?"汤姆"还是"杰瑞"?这是帮助我克服这种混乱的最后一件事.
Java的始终是按值传递,没有例外,永远.
那么如何让任何人都对此感到困惑,并相信Java是通过引用传递的,或者认为他们有一个Java作为参考传递的例子?关键是Java 在任何情况下都不会直接访问对象本身的值.对对象的唯一访问是通过对该对象的引用.因为Java对象总是通过引用访问,而不是直接访问,所以通常将字段和变量以及方法参数作为对象进行讨论,而当它们只是对对象的引用时.这种混淆源于这种(严格来说,不正确的)命名法的变化.
所以,在调用方法时
- 对于原始参数(
int
,long
等),pass by value是基元的实际值(例如,3). - 对于对象,pass by value是对象引用的值.
所以,如果你有doSomething(foo)
和public void doSomething(Foo foo) { .. }
两个FOOS已复制引用指向同一个对象.
当然,通过值传递对对象的引用看起来非常像(并且在实践中无法区分)通过引用传递对象.
- 由于基元的值是不可变的(如String),因此两种情况之间的差异并不重要.
- 原语是不可改变的?是Java 7中的新功能吗?
- 究竟.对于所有可以通过可观察的JVM行为来判断,原语可以通过引用传递,并且可以存在于堆上.他们没有,但实际上并没有以任何方式观察到.
- 指针是不可变的,原始一般是可变的.String也不是原始的,它是一个对象.而且,String的底层结构是一个可变数组.关于它的唯一不可改变的事情是长度,这是数组的固有性质.
- @CarlosHeuberger,"原始人是不变的?".关键是你可以*假装'原语'实际上(可变)*引用*到不可变对象.
- 这是另一个指出该论点在很大程度上具有语义性质的答案。此答案中提供的引用定义将使Java“通过引用传递”。作者在最后一段中基本上同意了这一点,指出它与“通过引用”“在实践中没有区别”。我怀疑OP之所以问是因为希望了解Java实现,而不是了解如何正确使用Java。如果在实践中没有区别,那么就没有任何关怀,甚至思考也将浪费时间。
Java按值传递引用.
因此,您无法更改传入的引用.
- 我从来没有说过"你不能改变在参数中传递的对象的价值".我会说"你不能改变作为方法参数传入的对象引用的值",这是关于Java语言的真实陈述.显然你可以改变对象的状态(只要它不是不可变的).
- 但是不断重复的事情"你不能改变在参数中传递的对象的值"显然是错误的.您可能无法使它们引用不同的对象,但您可以通过调用其方法来更改其内容.IMO这意味着您将失去参考的所有好处,并且不会获得额外的保证.
- 请记住,您实际上无法在java中传递对象; 对象留在堆上._Pointers_到对象可以传递(它被复制到被调用方法的堆栈帧).因此,您永远不会更改传入的值(指针),但您可以自由地关注它并更改它所指向的堆上的内容.这是传值.
- 听起来你只是***通过参考***?我将支持Java仍然是一种复制的传递引用语言这一事实.它是复制引用的事实不会改变术语.两个REFERENCES仍指向同一个对象.这是纯粹主义者的论点......
- 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的地址.
那么,价值,参考?你说"土豆".
- 人们不是"围绕堆栈与堆的真正问题跳舞",因为那不是真正的问题.这是一个最好的实现细节,最糟糕的是彻头彻尾的错误.(对象很可能存在于堆栈中;谷歌"逃避分析".并且大量对象包含可能*不会*存在于堆栈中的基元.)真正的问题是*正好*引用之间的差异类型和值类型 - 特别是,reference-type变量的值是引用,而不是它引用的对象.
- 无论哪种方式,"基元在堆栈上"都是不正确的.原始*局部变量*进入堆栈.(当然,如果它们还没有被优化掉.)但是,那么*本地参考变量*也是如此.在对象中定义的原始成员生活在对象所在的任何地方.
- 同意这里的评论.堆栈/堆是一个侧面问题,并不相关.一些变量可能在堆栈上,一些变量在静态内存中(静态变量),并且堆上有很多变量(所有对象成员变量).这些变量中的任何一个都不能通过引用传递:从被调用的方法中,永远不可能更改作为参数传递的变量的值.因此,Java中没有pass-by-reference.
- Such an awesome answer that even a fool like myself was able to understand. I would add also amend that "pass by value" literally means that the literal value in the stack is passed.
- 这是一个"实现细节",因为Java永远不需要实际向您显示对象在内存中的位置,事实上似乎决定*避免*泄漏该信息.它可以把对象放在堆栈上,你永远不会知道.如果你关心,你就会关注错误的事情 - 在这种情况下,这意味着忽略了真正的问题.
- 但是,您应该跟进一个更复杂的示例,其中函数似乎将变量更改为其具有引用的地址.
- 这不正确@cHao.Java不会在堆栈上放置任何活动对象.它可能是一个"实现细节",因为它没有在Java规范中具体概述,但事实是所有Java实现都将所有对象放在堆中.
- 实际上,我撒了谎:它在规范中是_is_.该堆栈用于本地和部分结果[链接](http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.5.2),堆用于对象[链接](http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.5.3)
- 它与堆栈与堆分配无关.它与间接有关.堆栈位置有地址.无论如何,Java按值传递所有参数.
为了显示对比,请比较以下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只有两种类型的传递:内置类型的值,以及对象类型的指针值.
- +1我也会在C++示例中添加`Dog**objPtrPtr`,这样我们就可以修改指针"指向"的内容.
Java按值传递对象的引用.
- 答案简洁、正确,而且是我自己的工作。我在 2008 年回答了这个问题,比许多更长、更详尽的答案早了十年,这些答案可能会让你误以为它是抄袭的。
基本上,重新分配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
,它只是一个引用"Hah!"
.如果它本身就是实际参考,那么foo
将重新定义baz
为null
.
- 我宁愿说bar是引用baz(或baz别名)的副本,它最初指向同一个对象.
我无法相信没人提到Barbara Liskov.当她在1974年设计的CLU,她就遇到了这个相同的术语问题,她发明了长期通过共享呼叫(也称为按对象共享呼叫和呼叫的对象的值)调用的"这种特殊情况下,该值为参考".
- [分享呼叫](http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_sharing)
- 我喜欢命名法中的这种区别.遗憾的是Java支持通过共享对象来调用,而不是通过值调用(就像C++一样).Java支持仅对原始数据类型而不是复合数据类型进行值调用.
- 我真的不认为我们需要一个额外的术语 - 它只是特定类型值的传值.添加"原始呼叫"会添加任何澄清吗?
的问题的关键是,字参考在表述"通过引用传递"是指某物从字的通常含义完全不同的参考在Java中.
通常在Java 引用中表示对对象的引用.但是,编程语言理论中通过引用/值传递的技术术语正在讨论对存储变量的存储器单元的引用,这是完全不同的.
- @Gevorg - 那么什么是"NullPointerException"?
- @Hot:一个不幸的是,在Java解决了一个明确的术语之前就已经命名了一个例外.c#中语义上等效的异常称为NullReferenceException.
- 在我看来,在Java术语中使用"引用"是一种妨碍理解的做法.
在java中,所有内容都是引用,所以当你有类似的东西时:
Point pnt1 = new Point(0,0);
Java确实如下:
- 创建新的Point对象
- 创建新的Point引用并初始化对先前创建的Point对象的指向(引用)的引用.
- 从这里开始,通过Point对象生命,您将通过pnt1引用访问该对象.所以我们可以说在Java中你通过它的引用操作对象.
Java不通过引用传递方法参数; 它按值传递它们.我将使用此站点中的示例:
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);
使用两个不同的引用关联创建两个不同的Point对象.
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);
参考文献pnt1
和pnt2
被按值传递给棘手的方法,这意味着现在你参考pnt1
,并pnt2
有自己的copies
命名arg1
和arg2
.所以pnt1
,并arg1
点到同一个对象.(与pnt2
和相同arg2
)
在tricky
方法中:
arg1.x = 100;
arg1.y = 100;
接下来的tricky
方法
Point temp = arg1;
arg1 = arg2;
arg2 = temp;
在这里,您首先创建新的temp
Point引用,它将指向相同的位置,如arg1
引用.然后您将参考arg1
以点像同一个地方arg2
的参考.最后arg2
会指向同一个地方temp
.
从这里的范围tricky
方法走了,你没有获得任何更多的引用:arg1
,arg2
,temp
.但重要的是,当你在生活中使用这些引用时所做的一切都将永久地影响它们所指向的对象.
所以在执行方法之后tricky
,当你返回时main
,你会遇到这种情况:
所以现在,完全执行程序将是:
X1: 0 Y1: 0
X2: 0 Y2: 0
X1: 100 Y1: 100
X2: 0 Y2: 0
- 在java中,当你说"在java中一切都是引用"时你的意思是所有[对象](http://docs.oracle.com/javase/8/docs/api/java/lang/Object.html)都是通过引用传递的.[原始数据类型](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)不通过引用传递.
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
,如下所示:
希望这会有所帮助.
无论您使用何种语言,引用始终是表示的值.
获取框外视图,让我们看看Assembly或一些低级内存管理.在CPU级别,如果将任何内容写入内存或其中一个CPU寄存器,则对任何内容的引用会立即变为值.(这就是为什么指针是一个很好的定义.它是一个值,它同时具有目的).
内存中的数据有一个位置,在该位置有一个值(字节,字,等等).在Assembly中,我们有一个方便的解决方案,可以为某个位置(也就是变量)提供一个名称,但是在编译代码时,汇编器只需将Name替换为指定的位置,就像浏览器用IP地址替换域名一样.
在核心的情况下,技术上不可能在不表示任何语言的情况下将引用传递给任何语言(当它立即变为值时).
比方说,我们有一个变量富,它的位置是在内存中的第47字节,它的值是5,我们有另一个变量Ref2Foo这是在第223次内存字节,并且它的值是47.这Ref2Foo可能是一个技术性的变量,不是由程序明确创建的.如果您只查看5和47而没有任何其他信息,您将只看到两个值.如果您将它们用作参考,那么5
我们必须前往:
(Name)[Location] -> [Value at the Location]
---------------------
(Ref2Foo)[223] -> 47
(Foo)[47] -> 5
这就是跳转表的工作原理.
如果我们想用Foo的值调用方法/函数/过程,有几种可能的方法将变量传递给方法,具体取决于语言及其几种方法调用模式:
- 5被复制到其中一个CPU寄存器(即EAX).
- 5获得PUSHd到堆栈.
- 47被复制到其中一个CPU寄存器
- 47推到堆栈.
- 223被复制到其中一个CPU寄存器.
- 223获取PUSHd到堆栈.
在高于某个值的每种情况下 - 现有值的副本 - 都已创建,现在由接收方法来处理它.当你在方法中写入"Foo"时,它要么从EAX读出,要么自动 解除引用,或者双重解引用,这个过程取决于语言的工作方式和/或Foo的类型.这是开发人员隐藏的,直到她绕过解除引用过程.因此,引用是表示的值,因为引用是必须处理的值(在语言级别).
现在我们已经将Foo传递给了方法:
- 在情况1.和2.如果您更改Foo(
Foo = 9
)它只影响本地范围,因为您有一个值的副本.从方法内部我们甚至无法确定原始Foo所在的内存位置. - 如果您使用默认语言结构并更改Foo(
Foo = 11
),它可以全局更改Foo(取决于语言,即Java或类似Pascal的procedure findMin(x, y, z: integer;
var m: integer);
).但是,如果该语言允许您绕过取消引用过程,您可以更改47
,比方说49
.在那一点上,如果你读它,Foo似乎已被改变,因为你已经改变了它的本地指针.如果你要在方法(Foo = 12
)中修改这个Foo,你可能会搞砸程序的执行(又名.segfault),因为你会写一个不同于预期的内存,你甚至可以修改一个注定要保存可执行文件的区域.程序和写入它将修改运行代码(Foo现在不在47
).但是Foo的值47
并没有全局变化,只有方法内部的变化,因为47
它也是方法的副本. - 在第5和第6例中.如果你
223
在方法内修改它会产生与3.或4中相同的混乱.(一个指针,指向一个现在错误的值,它再次用作指针)但这仍然是一个本地问题,因为223被复制了.但是,如果您能够取消引用Ref2Foo
(即223
),达到并修改指向的值47
,比如说,49
它将全局影响Foo ,因为在这种情况下,方法得到了一个副本,223
但引用的47
只存在一次,并且更改了对49
每一个会导致Ref2Foo
双解引用到一个错误的值.
对无关紧要的细节进行挑剔,即使是通过引用传递的语言也会将值传递给函数,但这些函数知道它们必须将它用于解除引用目的.这个传递参考值只是程序员隐藏的,因为它实际上是无用的,术语只是通过引用传递.
严格的pass-by-value也没用,这意味着每次调用一个以数组为参数的方法时都必须复制一个100 MB的数组,因此Java不能严格按值传递.每种语言都会传递对这个庞大数组的引用(作为一个值),并且如果该数组可以在方法内部进行本地更改,或者允许该方法(如Java那样)全局修改数组,则使用写时复制机制(来自调用者的视图)和一些语言允许修改引用本身的值.
因此,简而言之,在Java自己的术语中,Java是值传递,其中值可以是:实数值或作为引用表示的值.
Java是一种价值呼叫
这个怎么运作
据我所知,Java只知道按值调用.这意味着对于原始数据类型,您将使用副本,对于对象,您将使用对象的引用副本.不过我觉得有一些陷阱; 例如,这不起作用:
public static void swap(StringBuffer s1, StringBuffer s2) {
StringBuffer temp = s1;
s1 = s2;
s2 = temp;
}
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer("Hello");
StringBuffer s2 = new StringBuffer("World");
swap(s1, s2);
System.out.println(s1);
System.out.println(s2);
}
这将填充Hello World而不是World Hello,因为在交换函数中,您使用copys,它对main中的引用没有影响.但是,如果您的对象不是不可变的,您可以更改它,例如:
public static void appendWorld(StringBuffer s1) {
s1.append(" World");
}
public static void main(String[] args) {
StringBuffer s = new StringBuffer("Hello");
appendWorld(s);
System.out.println(s);
}
这将在命令行上填充Hello World.如果将StringBuffer更改为String,它将生成Hello,因为String是不可变的.例如:
public static void appendWorld(String s){
s = s+" World";
}
public static void main(String[] args) {
String s = new String("Hello");
appendWorld(s);
System.out.println(s);
}
但是你可以为这样的String创建一个包装器,它可以使它与字符串一起使用:
class StringWrapper {
public String value;
public StringWrapper(String value) {
this.value = value;
}
}
public static void appendWorld(StringWrapper s){
s.value = s.value +" World";
}
public static void main(String[] args) {
StringWrapper s = new StringWrapper("Hello");
appendWorld(s);
System.out.println(s.value);
}
编辑:我相信这也是使用StringBuffer的原因,当涉及到"添加"两个字符串,因为你可以修改原始对象,你不能用像String这样的不可变对象.
不,它不是通过引用传递.
根据Java语言规范,Java是按值传递的:
让我试着在四个例子的帮助下解释我的理解.Java是按值传递的,而不是按引用传递
/**
通过价值
在Java中,所有参数都按值传递,即调用者无法分配方法参数.
*/
例1:
public class PassByValueString {
public static void main(String[] args) {
new PassByValueString().caller();
}
public void caller() {
String value = "Nikhil";
boolean valueflag = false;
String output = method(value, valueflag);
/*
* 'output' is insignificant in this example. we are more interested in
* 'value' and 'valueflag'
*/
System.out.println("output : " + output);
System.out.println("value : " + value);
System.out.println("valueflag : " + valueflag);
}
public String method(String value, boolean valueflag) {
value = "Anand";
valueflag = true;
return "output";
}
}
结果
output : output
value : Nikhil
valueflag : false
例2:
/****通过价值**/
public class PassByValueNewString {
public static void main(String[] args) {
new PassByValueNewString().caller();
}
public void caller() {
String value = new String("Nikhil");
boolean valueflag = false;
String output = method(value, valueflag);
/*
* 'output' is insignificant in this example. we are more interested in
* 'value' and 'valueflag'
*/
System.out.println("output : " + output);
System.out.println("value : " + value);
System.out.println("valueflag : " + valueflag);
}
public String method(String value, boolean valueflag) {
value = "Anand";
valueflag = true;
return "output";
}
}
结果
output : output
value : Nikhil
valueflag : false
例3:
/**这个'通过价值传递'有一种'通过参考传递'的感觉
有些人说原始类型和'String'是'按值传递'而对象是'按引用传递'.
但是从这个例子中,我们可以理解它只是通过值传递,记住这里我们将引用作为值传递.ie:引用按值传递.这就是为什么能够改变并且在本地范围之后仍然适用的原因.但是我们不能改变原始范围之外的实际参考.这意味着下一个PassByValueObjectCase2示例.
*/
public class PassByValueObjectCase1 {
private class Student {
int id;
String name;
public Student() {
}
public Student(int id, String name) {
super();
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + "]";
}
}
public static void main(String[] args) {
new PassByValueObjectCase1().caller();
}
public void caller() {
Student student = new Student(10, "Nikhil");
String output = method(student);
/*
* 'output' is insignificant in this example. we are more interested in
* 'student'
*/
System.out.println("output : " + output);
System.out.println("student : " + student);
}
public String method(Student student) {
student.setName("Anand");
return "output";
}
}
结果
output : output
student : Student [id=10, name=Anand]
例4:
/**
除了Example3(PassByValueObjectCase1.java)中提到的内容之外,我们无法更改原始范围之外的实际引用."
注意:我没有粘贴代码private class Student
.类定义Student
与Example3相同.
*/
public class PassByValueObjectCase2 {
public static void main(String[] args) {
new PassByValueObjectCase2().caller();
}
public void caller() {
// student has the actual reference to a Student object created
// can we change this actual reference outside the local scope? Let's see
Student student = new Student(10, "Nikhil");
String output = method(student);
/*
* 'output' is insignificant in this example. we are more interested in
* 'student'
*/
System.out.println("output : " + output);
System.out.println("student : " + student); // Will it print Nikhil or Anand?
}
public String method(Student student) {
student = new Student(20, "Anand");
return "output";
}
}
结果
output : output
student : Student [id=10, name=Nikhil]
已经有很好的答案可以解决这个问题。我想通过分享一个非常简单的示例(将进行编译)做出一点贡献,以比较 c ++中的“按引用传递”和Java中的“按值传递”之间的行为。
几点:
- 术语“引用”是具有两个单独含义的重载。在Java中,它只是表示一个指针,但在“按引用传递”的上下文中,它表示传入的原始变量的句柄。
- Java是按值传递,但允许我们通过按值传递Java引用(即指针)来模拟传递被引用。表示它传递了Java参考的副本。编辑:由于有人对此发表评论,让我解释一下。在C之前,FORTRAN和COBOL等几种(但不是全部)较早的语言支持PBR,但是C不支持。为了更改函数内部变量的值,C程序员通过将指向变量的指针传递到函数中来模拟PBR。受C启发的语言(例如Java)借鉴了这一思想,并像C一样继续模仿PBR。
- C ++通过使用“&”字符(在C和C ++中恰好与用来表示“变量的地址”的字符相同)声明引用参数,从而允许通过引用。例如,如果我们按引用传递指针,则参数和参数不仅指向同一对象。相反,它们是相同的变量。如果将一个设置为其他地址或设置为null,则另一个设置也将设置为null。
- 在下面的C ++示例中,我通过引用将指针传递给以 null结尾的字符串。在下面的Java示例中,我按值传递了对String的Java引用(再次与指向String的指针相同)。注意注释中的输出。
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”。当指针传递给该过程时,它将通过reference传递。请注意行为:当此过程将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
Ken Arnold,James Gosling(发明Java的人)和David Holmes的“ THE Java Programming Language”摘录,第2章,第2.6.5节
他继续就物体提出了相同的观点。。。
在同一部分的末尾,他对java仅通过值传递而从不传递引用进行了更广泛的说明。
本书的这一部分对Java中的参数传递以及按引用传递和按值传递之间的区别(由Java的创建者)进行了很好的解释。我会鼓励任何人阅读它,特别是如果您仍然不相信的话。
我认为这两个模型之间的差异非常微妙,除非您在实际使用传递引用的地方进行了编程,否则很容易错过两个模型之间的差异。
我希望这能解决辩论,但可能不会。
编辑3
我可能对这个职位有些痴迷。可能是因为我觉得Java的制造商无意间散布了错误信息。如果他们没有使用“引用”一词作为指针,而是使用了其他东西(例如dingleberry),那就没有问题了。您可能会说:“ Java通过值而不是通过引用传递dingleberries”,并且不会有人感到困惑。(因此,在引用按引用与值进行传递时,我将引用称为dinglebarries。)
这就是只有Java开发人员对此有疑问的原因。他们看着“指称”一词,以为自己确切地知道那是什么意思,因此他们甚至不必理会相反的论点。
无论如何,我注意到dhackner在一个较早的帖子中发表了评论,他做出了我非常喜欢的气球类比。如此之多,以至于我决定将一些剪贴画粘合在一起,制作出一系列动画片来说明这一点。
按值传递引用 -引用的更改不会反映在调用者的作用域中,但对象的更改会反映在调用者的作用域中。这是因为引用已复制,但是原始副本和副本都引用同一对象。
通过引用传递-没有引用的副本。调用者和被调用函数都共享单个引用。对引用或对象数据的任何更改都会反映在调用者的作用域中。
编辑4
我看过有关此主题的文章,这些文章描述了Java中参数传递的低级实现,我认为这很好并且非常有帮助,因为它使抽象概念具体化。但是,对我来说,问题更多的是语言规范中描述的行为,而不是行为的技术实现。这是Java语言规范第8.4.1节的摘录:
这意味着,java在执行方法之前创建了传递的参数的副本。像大多数上过大学的编译器的人一样,我使用的是《 The Dragon Book》,这是THE编译器的书。在第1章中对“按值调用”和“按引用调用”有很好的描述。“按值调用”描述与Java规范完全匹配。
当我使用编译器时,我使用了1986年的第一版书,它比Java早了9到10年。但是,我遇到了2007年第二版的副本,其中实际上提到了Java!标有“参数传递机制”的第1.6.6节很好地描述了参数传递。以下是“按值调用”标题下的摘录,其中提到了Java:
- @AutomatedMike我认为将Java描述为“按引用传递”也是一种误导,它们的引用不过是指针。正如Sanjeev所写的价值一样,无论Java创建者使用什么,引用和指针都是教科书术语,具有各自的含义。
- @Sanjeev感谢您的分享!气球示例非常实用!
您永远不能通过Java中的引用传递,并且明显的一种方法是当您想要从方法调用返回多个值时.考虑C++中的以下代码:
void getValues(int& arg1, int& arg2) {
arg1 = 1;
arg2 = 2;
}
void caller() {
int x;
int y;
getValues(x, y);
cout << "Result: " << x << " " << y << endl;
}
有时你想在Java中使用相同的模式,但你不能; 至少不是直接的.相反,你可以做这样的事情:
void getValues(int[] arg1, int[] arg2) {
arg1[0] = 1;
arg2[0] = 2;
}
void caller() {
int[] x = new int[1];
int[] y = new int[1];
getValues(x, y);
System.out.println("Result: " + x[0] + " " + y[0]);
}
正如之前的答案中所解释的那样,在Java中,您将指向数组的指针作为值传入getValues
.这就足够了,因为该方法然后修改了数组元素,按照惯例,您期望元素0包含返回值.显然,您可以通过其他方式执行此操作,例如构造代码以使其不必要,或者构造可以包含返回值或允许设置它的类.但是,在C++中可用的简单模式在Java中不可用.
我想我会提供这个答案,以便从规格中添加更多细节.
首先,通过引用传递与传递值之间的区别是什么?
按值传递意味着被调用函数的参数将是调用者传递的参数的副本.
或者来自维基百科,关于传递参考的主题
而对主题传递的价值
其次,我们需要知道Java在其方法调用中使用了什么.在Java语言规范状态
因此它将参数的值赋予(或绑定)到相应的参数变量.
争论的价值是什么?
让我们考虑引用类型,在Java虚拟机规范状态
在Java语言规范还规定
参数(某种引用类型)的值是指向对象的指针.请注意,变量,具有引用类型返回类型的方法的调用以及实例创建表达式(new ...
)都将解析为引用类型值.
所以
public void method (String param) {}
...
String var = new String("ref");
method(var);
method(var.toString());
method(new String("ref"));
all将String
实例引用的值绑定到方法新创建的参数中param
.这正是pass-by-value的定义所描述的内容.因此,Java是按值传递的.
您可以按照引用来调用方法或访问引用对象的字段这一事实与对话完全无关.传递参考的定义是
在Java中,修改变量意味着重新分配它.在Java中,如果在方法中重新分配变量,它将不会被调用者忽视.修改变量引用的对象完全是一个不同的概念.
此处还在Java虚拟机规范中定义了原始值.该类型的值是相应的积分或浮点值,适当编码(8,16,32,64等位).
区别,或者也许只是我记忆中的方式,因为我曾经与原始海报的印象相同:Java总是按价值传递.Java中的所有对象(在Java中,除了基元之外的任何东西)都是引用.这些引用按值传递.
- 我发现你的倒数第二句非常误导."Java中的所有对象都是引用"并不是真的.它只是引用那些对象的_references_.
在Java中,只传递引用并按值传递:
Java参数都是按值传递的(当方法使用时会复制引用):
对于基本类型,Java行为很简单:将值复制到基本类型的另一个实例中.
在Objects的情况下,这是相同的:对象变量是仅包含使用"new"关键字创建的Object的地址的指针(桶),并且像原始类型一样被复制.
行为可能与原始类型不同:因为复制的对象变量包含相同的地址(对于同一个对象)对象的内容/成员可能仍然在方法中被修改并且稍后在外部访问,从而产生(包含)对象的错觉本身是通过引用传递的.
"字符串"对象似乎是城市传说中"对象通过引用传递" 的完美反例:
实际上,在一个永远无法实现的方法中,更新作为参数传递的String的值:
String对象,由声明为final的数组保存,不能修改.只有Object的地址可能被另一个使用"new"替换.使用"new"更新变量,不会让Object从外部访问,因为变量最初是按值传递并复制的.
正如许多人之前提到的那样,Java始终是按值传递的
这是另一个帮助您理解差异的示例(经典交换示例):
public class Test {
public static void main(String[] args) {
Integer a = new Integer(2);
Integer b = new Integer(3);
System.out.println("Before: a = " + a + ", b = " + b);
swap(a,b);
System.out.println("After: a = " + a + ", b = " + b);
}
public static swap(Integer iA, Integer iB) {
Integer tmp = iA;
iA = iB;
iB = tmp;
}
}
打印:
这是因为iA和iB是新的本地引用变量,它们具有相同的传递引用值(它们分别指向a和b).因此,尝试更改iA或iB的引用只会在本地范围内更改,而不会在此方法之外.
Java只通过值传递.一个非常简单的例子来验证这一点.
public void test() {
MyClass obj = null;
init(obj);
//After calling init method, obj still points to null
//this is because obj is passed as value and not as reference.
}
private void init(MyClass objVar) {
objVar = new MyClass();
}
- 这是看到Java按值传递的最清晰,最简单的方法.`obj`(`null`)的值传递给`init`,而不是对`obj`的引用.
我一直认为它是"通过副本".它是值的原始或引用的副本.如果它是原始的,则它是作为值的位的副本,如果它是Object,则它是引用的副本.
public class PassByCopy{
public static void changeName(Dog d){
d.name = "Fido";
}
public static void main(String[] args){
Dog d = new Dog("Maxx");
System.out.println("name= "+ d.name);
changeName(d);
System.out.println("name= "+ d.name);
}
}
class Dog{
public String name;
public Dog(String s){
this.name = s;
}
}
java PassByCopy的输出:
原始包装类和字符串是不可变的,因此使用这些类型的任何示例都不会与其他类型/对象相同.
- "通过副本传递"是值传递**的意思**.
我已经为这里的任何编程语言创建了一个致力于这类问题的线程.
还提到了Java.以下是简短摘要:
- Java按值传递参数
- "by value"是java将参数传递给方法的唯一方法
- 使用作为参数给出的对象的方法将改变对象,因为引用指向原始对象.(如果该方法本身改变某些值)
一些帖子的一些更正.
C不支持通过引用传递.它总是通过价值.C++确实支持按引用传递,但不是默认值,而且非常危险.
Java中的值是什么并不重要:对象的原始或地址(大致),它总是按值传递.
如果Java对象"行为"就像它通过引用传递一样,那就是可变性的属性,并且与传递机制完全无关.
我不确定为什么会这么混乱,也许是因为很多Java"程序员"没有经过正式培训,因此不了解内存中真正发生了什么?
简而言之,Java对象具有一些非常特殊的属性.
一般来说,Java有原始类型(int
,bool
,char
,double
,等等)直接由值来传递.然后Java有对象(从中衍生出来的所有东西java.lang.Object
).实际上,对象总是通过引用来处理(引用是一个你无法触摸的指针).这意味着实际上,对象是通过引用传递的,因为引用通常不是很有趣.但它确实意味着您无法更改指向的对象,因为引用本身是按值传递的.
这听起来有点奇怪和令人困惑吗?让我们考虑C如何通过引用传递并按值传递.在C中,默认约定是按值传递.void foo(int x)
按值传递int.void foo(int *x)
是一个不需要的函数int a
,而是一个指向int的指针:foo(&a)
.可以使用它与&
运算符一起传递变量地址.
把它带到C++,我们有参考.引用基本上(在此上下文中)隐藏等式的指针部分的语法糖:void foo(int &x)
被调用foo(a)
,其中编译器本身知道它是引用,并且a
应该传递非引用的地址.在Java中,所有引用对象的变量实际上都是引用类型,实际上强制通过引用调用大多数意图和目的,而没有由例如C++提供的细粒度控制(和复杂性).
Java的按值传递参数,并通过价值ONLY.
长话短说:
来自PASCAL的人:没有"var"参数.
这意味着您无法更改对象本身的引用,但您始终可以更改对象的属性.
解决方法是使用StringBuilder
参数String
.你总是可以使用数组!
与其他一些语言不同,Java不允许您选择按值传递或按引用传递 - 所有参数都按值传递.方法调用可以将两种类型的值传递给方法 - 原始值的副本(例如,int和double的值)以及对对象的引用的副本.
当方法修改基本类型参数时,对参数的更改不会影响调用方法中的原始参数值.
对于对象,对象本身不能传递给方法.所以我们传递在引用变量中保存的对象的地址.
Java如何创建和存储对象.当我们创建一个对象时,我们将对象的地址存储在一个引用变量中."扫描仪输入"是类型和参考变量,"="是赋值运算符,"新"是从系统请求所需的空间量.创建对象的关键字new右侧的构造函数由关键字new隐式调用.使用assign运算符将创建的对象的地址(右变量的结果,即表达式)分配给左变量(具有指定名称和类型的引用变量)."new Account()"被称为"类实例创建表达式".
虽然对象的引用是按值传递的,但是方法仍然可以通过使用对象引用的副本调用其公共方法来与引用的对象进行交互.由于存储在参数中的引用是作为参数传递的引用的副本,因此被调用方法中的参数和调用方法中的参数引用内存中的同一对象.
由于性能原因,传递对数组的引用而不是数组对象本身是有意义的.因为Java中的所有内容都是按值传递的,所以如果传递了数组对象,则会传递每个元素的副本.对于大型阵列,这会浪费时间并为元素的副本消耗大量存储空间.
在下面的图像中,您可以看到我们有两个引用变量(这些在C/C++中称为指针.我认为该术语使得更容易理解此功能.)在main方法中.原始和引用变量保存在堆栈存储器中(左下图中).这些引用变量"指向"(作为C/C++程序员称之为)或引用作为对象的a和b数组(这些引用变量保存的值是对象的地址)在堆内存中(下图中的右侧).
如果我们将array1引用变量的值作为参数传递给reverseArray方法,则在方法中创建一个引用变量,该引用变量开始指向同一个数组(a).
public class Test
{
public static void main(String args)
{
int[] array1 = { 1, 10, -7 };
reverseArray(array1);
}
public void reverseArray(Int[] array1)
{
// ...
}
}
...
array1[0] = 5;
所以,如果我们说
array1 = array2;
在reverseArray方法中,它将在数组a中进行更改.
我们在reverseArray方法(array2)中有另一个指向数组c的引用变量.如果我们要说
return array2;
在reverseArray方法中,方法reverseArray中的引用变量array1将停止指向数组a并开始指向数组c(第二个图像中的虚线).
如果我们返回引用变量array2的值作为方法reverseArray的返回值并将此值赋给main方法中的引用变量array1,则main中的array1将开始指向数组c.
您也可以将main2中的array2值赋给array1.array1将开始指向b.
这是回答问题的最佳方式...
首先,我们必须了解,在Java中,参数传递行为 ......
public void foo(Object param)
{
// some code in foo...
}
public void bar()
{
Object obj = new Object();
foo(obj);
}
与......完全一样
public void bar()
{
Object obj = new Object();
Object param = obj;
// some code in foo...
}
不考虑堆栈位置,这与本讨论无关.
所以,事实上,我们在Java中寻找的是变量赋值的工作原理.我在文档中找到了它:
int cadence = 0;
int speed = 0;
int gear = 1;此运算符也可用于对象以分配对象引用 [...]
很清楚这个算子如何以两种不同的方式起作用:赋值和赋值.最后一个,当它是一个对象时...第一个,当它不是一个对象时,也就是当它是一个原语时.但这样,我们可以了解到Java的功能PARAMS可以通过按值和传递按引用?
事实是在代码中.我们来试试吧:
public class AssignmentEvaluation
{
static public class MyInteger
{
public int value = 0;
}
static public void main(String[] args)
{
System.out.println("Assignment operator evaluation using two MyInteger objects named height and width\n");
MyInteger height = new MyInteger();
MyInteger width = new MyInteger();
System.out.println("[1] Assign distinct integers to height and width values");
height.value = 9;
width.value = 1;
System.out.println("-> height is " + height.value + " and width is " + width.value + ", we are different things! \n");
System.out.println("[2] Assign to height's value the width's value");
height.value = width.value;
System.out.println("-> height is " + height.value + " and width is " + width.value + ", are we the same thing now? \n");
System.out.println("[3] Assign to height's value an integer other than width's value");
height.value = 9;
System.out.println("-> height is " + height.value + " and width is " + width.value + ", we are different things yet! \n");
System.out.println("[4] Assign to height the width object");
height = width;
System.out.println("-> height is " + height.value + " and width is " + width.value + ", are we the same thing now? \n");
System.out.println("[5] Assign to height's value an integer other than width's value");
height.value = 9;
System.out.println("-> height is " + height.value + " and width is " + width.value + ", we are the same thing now! \n");
System.out.println("[6] Assign to height a new MyInteger and an integer other than width's value");
height = new MyInteger();
height.value = 1;
System.out.println("-> height is " + height.value + " and width is " + width.value + ", we are different things again! \n");
}
}
这是我的运行输出:
Assignment operator evaluation using two MyInteger objects named height and width [1] Assign distinct integers to height and width values -> height is 9 and width is 1, we are different things! [2] Assign to height's value the width's value -> height is 1 and width is 1, are we the same thing now? [3] Assign to height's value an integer other than width's value -> height is 9 and width is 1, we are different things yet! [4] Assign to height the width object -> height is 1 and width is 1, are we the same thing now? [5] Assign to height's value an integer other than width's value -> height is 9 and width is 9, we are the same thing now! [6] Assign to height a new MyInteger and an integer other than width's value -> height is 1 and width is 9, we are different things again!
在[2]中,我们有不同的对象,并将一个变量的值分配给另一个.但是在[3]中分配新值后,对象具有不同的值,这意味着在[2]中,赋值是原始变量的副本,通常称为按值传递,否则,在[3]中打印的值]应该是一样的.
在[4]中,我们仍然有不同的对象,并将一个对象分配给另一个.在[5]中分配新值后,对象具有相同的值,这意味着在[4]中,分配的对象不是另一个的副本,应该称为pass-by-reference.但是,如果我们仔细研究[6],我们就不能确定没有复制了...... ?????
我们不能这么肯定,因为在[6]中对象是相同的,然后我们为其中一个分配了一个新对象,之后,对象具有不同的值!如果他们是相同的,他们现在如何区别?他们在这里也应该是一样的!?????
我们需要记住文档以了解发生了什么:
所以我们的两个变量是存储引用...我们的变量在[4]之后具有相同的引用和[6]之后的不同引用...如果这样的事情是可能的,这意味着对象的赋值是通过对象的副本完成的参考,否则,如果它不是参考文献的副本,[6]中变量的印刷值应该相同.因此,对象(引用)就像基元一样,通过赋值复制到变量,人们通常称之为传值.这是Java中唯一的传递方式.
这非常非常简单:
对于基本类型的变量(如int
,boolean
,char
,等...),当你使用它的名字的方法参数,要传递包含在它的价值(5
,true
,或'c'
).此值被"复制",即使在方法调用之后,变量仍保留其值.
对于引用类型的变量(例如String
,Object
等...),当你使用它的名字的方法参数,要传递包含的值(该参考值是"点"的对象).此参考值被"复制",即使在方法调用之后,变量仍保留其值.引用变量保持"指向"同一对象.
无论哪种方式,你总是按价值传递东西.
比较一下,你可以用c ++来获取一个方法int&
,或者在C#中你可以拿一个ref int
(虽然,在这种情况下,你还必须ref
在将变量的名称传递给方法时使用修饰符.)
纵观所有问题的答案,我们看到的Java传递的价值或者说作为@Gevorg写道:"传址复制的最值变量值",这是我们应该在心中所有的时间的想法.
我专注于帮助我理解这个想法的例子,它是以前答案的补充.
从[1]在Java中,你总是通过副本传递参数; 那就是你总是在函数内部创建一个新的值实例.但是有些行为可以让你认为你是通过参考传递的.
通过复制/按值传递的示例
[ref 1]的例子
void incrementValue(int inFunction){
inFunction ++;
System.out.println("In function: " + inFunction);
}
int original = 10;
System.out.print("Original before: " + original);
incrementValue(original);
System.out.println("Original after: " + original);
We see in the console:
> Original before: 10
> In Function: 11
> Original after: 10 (NO CHANGE)
[ref 2]的例子
(通过引用传递)传递变量值的副本
[ref 1]中的示例
(记住数组是一个对象)
void incrementValu(int[] inFuncion){
inFunction[0]++;
System.out.println("In Function: " + inFunction[0]);
}
int[] arOriginal = {10, 20, 30};
System.out.println("Original before: " + arOriginal[0]);
incrementValue(arOriginal[]);
System.out.println("Original before: " + arOriginal[0]);
We see in the console:
>Original before: 10
>In Function: 11
>Original before: 11 (CHANGE)
正在复制复杂对象本身,但保留了内部引用.
[ref 3]的例子
package com.pritesh.programs;
class Rectangle {
int length;
int width;
Rectangle(int l, int b) {
length = l;
width = b;
}
void area(Rectangle r1) {
int areaOfRectangle = r1.length * r1.width;
System.out.println("Area of Rectangle : "
+ areaOfRectangle);
}
}
class RectangleDemo {
public static void main(String args[]) {
Rectangle r1 = new Rectangle(10, 20);
r1.area(r1);
}
}
矩形区域为200,长度= 10,宽度= 20
我想分享的最后一件事是讲座的这一刻:
内存分配
,我发现非常有助于理解Java传递的价值,或者更确切地说是"传递变量值的副本",正如@Gevorg写的那样.
- REF 1 Lynda.com
-
REF 2 Mehran Sahami教授
- 观看最长5分钟
- 内存分配
-
REF 3 c4learn
- 通过-对象作为参数对方法
- @ShailendraSingh Java决不会通过引用传递。有关此问题的明确证据,请参见Gaurav的答案。
Java按值复制引用.因此,如果您将其更改为其他内容(例如,使用new
),则引用不会在方法之外更改.对于本机类型,它始终按值传递.
Java编程语言中最大的困惑之一是Java是Pass by Value还是Pass by Reference.
首先,我们应该理解通过值或通过引用传递的含义.
传递值:将方法参数值复制到另一个变量,然后传递复制的对象,这就是它被称为按值传递的原因.
通过引用传递:将实际参数的别名或引用传递给方法,这就是它被称为按引用传递的原因.
假设我们有一个类Balloon,如下所示.
public class Balloon {
private String color;
public Balloon(){}
public Balloon(String c){
this.color=c;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
我们有一个简单的程序,使用通用方法交换两个对象,该类如下所示.
public class Test {
public static void main(String[] args) {
Balloon red = new Balloon("Red"); //memory reference 50
Balloon blue = new Balloon("Blue"); //memory reference 100
swap(red, blue);
System.out.println("redblueblueRed"); //baloon=100
balloon = new Balloon("Green"); //baloon=200
balloon.setColor("Blue"); //baloon = 200
}
//Generic swap method
public static void swap(Object o1, Object o2){
Object temp = o1;
o1=o2;
o2=temp;
}
}
当我们执行上面的程序时,我们得到以下输出.
red color=Red
blue color=Blue
blue color=Red
如果你查看输出的前两行,很明显交换方法不起作用.这是因为Java是通过值传递的,这个swap()方法测试可以与任何编程语言一起使用,以检查它是通过值传递还是通过引用传递.
让我们一步一步地分析程序的执行情况.
Balloon red = new Balloon("Red");
Balloon blue = new Balloon("Blue");
当我们使用new运算符创建类的实例时,将创建实例,并且变量包含保存对象的内存的引用位置.对于我们的示例,我们假设"red"指向50并且"blue"指向100,这些是两个Balloon对象的内存位置.
现在,当我们调用swap()方法时,会创建两个新变量o1和o2,分别指向50和100.
所以下面的代码片段解释了swap()方法执行中发生的事情.
public static void swap(Object o1, Object o2){ //o1=50, o2=100
Object temp = o1; //temp=50, o1=50, o2=100
o1=o2; //temp=50, o1=100, o2=100
o2=temp; //temp=50, o1=100, o2=50
} //method terminated
请注意,我们正在更改o1和o2的值,但它们是"红色"和"蓝色"参考位置的副本,因此实际上,"红色"和"蓝色"的值没有变化,因此输出也没有变化.
如果你已经理解了这一点,你可以很容易地理解混乱的原因.由于变量只是对象的引用,我们对传递引用感到困惑,因此Java通过引用传递.但是,我们传递的是引用的副本,因此它通过了值.我希望它现在能解决所有的疑虑.
现在让我们分析一下foo()方法的执行情况.
private static void foo(Balloon balloon) { //baloon=100
balloon.setColor("Red"); //baloon=100
balloon = new Balloon("Green"); //baloon=200
balloon.setColor("Blue"); //baloon = 200
}
当我们调用方法时,第一行是重要的一行,在参考位置的Object上调用该方法.此时,气球指向100,因此它的颜色变为红色.
在下一行中,气球参考被更改为200,并且执行的任何其他方法都发生在存储器位置200处的对象上,并且对存储器位置100处的对象没有任何影响.这解释了我们的程序输出的第三行打印蓝色=红.
我希望上面的解释清楚所有的疑问,只要记住变量是引用或指针,并将其副本传递给方法,因此Java总是按值传递.当您了解堆内存和堆栈内存以及存储不同对象和引用的位置时,会更清楚.
Java通过常量引用传递,其中传递了引用的副本,这意味着它基本上是按值传递.如果类是可变的,您可能会更改引用的内容,但您无法更改引用本身.换句话说,地址不能改变,因为它是通过值传递的,但地址指向的内容可以改变.在不可变类的情况下,引用的内容也不能改变.
Java总是使用按值调用.这意味着该方法获取所有参数值的副本.
考虑下面3种情况:
1)尝试改变原始变量
public static void increment(int x) { x++; }
int a = 3;
increment(a);
x将复制a的值并将增加x,a保持不变
2)尝试改变对象的原始字段
public static void increment(Person p) { p.age++; }
Person pers = new Person(20); // age = 20
increment(pers);
p将复制pers的参考值并将增加age字段,变量引用同一个对象,因此年龄会发生变化
3)尝试更改参考变量的参考值
public static void swap(Person p1, Person p2) {
Person temp = p1;
p1 = p2;
p2 = temp;
}
Person pers1 = new Person(10);
Person pers2 = new Person(20);
swap(pers1, pers2);
在调用swap p1之后,来自pers1和pers2的p2复制参考值与值交换,因此pers1和pers2保持不变
所以.您可以在方法中仅更改对象的字段,将参考值的副本传递给此对象.
这么多长的答案.让我举一个简单的说法:
- Java总是按值传递所有内容
- 这意味着引用也按值传递
简而言之,您无法修改传递的任何参数的值,但您可以调用方法或更改传递的对象引用的属性.
毫无疑问,Java肯定是"按价值传递".此外,由于Java(大多数)是面向对象的,并且对象使用引用,因此很容易混淆并认为它是"通过引用传递"
按值传递意味着您将值传递给方法,如果方法更改传递的值,则实体不会更改.另一方面,通过引用传递意味着将引用传递给方法,并且如果方法更改它,则传递的对象也会更改.
在Java中,通常当我们将一个对象传递给一个方法时,我们基本上将对象的引用作为一个值传递,因为这就是Java的工作原理; 就堆中的Object而言,它适用于引用和地址.
但是要测试它是否真的按值传递或通过引用传递,您可以使用基本类型和引用:
@Test
public void sampleTest(){
int i = 5;
incrementBy100(i);
System.out.println("passed ==> "+ i);
Integer j = new Integer(5);
incrementBy100(j);
System.out.println("passed ==> "+ j);
}
/**
* @param i
*/
private void incrementBy100(int i) {
i += 100;
System.out.println("incremented = "+ i);
}
输出是:
incremented = 105
passed ==> 5
incremented = 105
passed ==> 5
因此,在这两种情况下,方法内部发生的任何事情都不会更改真实的Object,因为该对象的值已传递,而不是对象本身的引用.
但是当您将自定义对象传递给方法并且方法更改它时,它也会更改实际对象,因为即使您传递了对象,也会将其作为值传递给方法.我们试试另一个例子:
@Test
public void sampleTest2(){
Person person = new Person(24, "John");
System.out.println(person);
alterPerson(person);
System.out.println(person);
}
/**
* @param person
*/
private void alterPerson(Person person) {
person.setAge(45);
Person altered = person;
altered.setName("Tom");
}
private static class Person{
private int age;
private String name;
public Person(int age, String name) {
this.age=age;
this.name =name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Person [age=");
builder.append(age);
builder.append(", name=");
builder.append(name);
builder.append("]");
return builder.toString();
}
}
在这种情况下,输出是:
Person [age=24, name=John]
Person [age=45, name=Tom]
Java严格按值传递
当我说通过值时,它意味着每当调用者调用被调用者时,参数(即:要传递给另一个函数的数据)被复制并放入形式参数(被调用者的本地变量用于接收输入).Java仅通过值环境从一个函数到其他函数进行数据通信.
重要的一点是要知道即使是C语言也只是通过值严格传递:
即:数据从调用者复制到被调用者,并且被调用者执行的操作更多地在同一个内存位置上,我们传递给他们的是我们从(&)运算符获取的那个位置的地址和形式参数中使用的标识符被声明为一个指针变量(*),我们可以使用它来获取内存位置以访问其中的数据.
因此,形式参数只不过是该位置的别名.并且在该位置上进行的任何修改都是可见的,其中变量的范围(标识该位置)是活着的.
在Java中,没有指针的概念(即:没有任何东西称为指针变量),尽管我们可以在java中将引用变量视为技术指针,我们将其称为句柄.我们之所以将指向地址的指针调用为java中的句柄,是因为指针变量不仅可以执行单个解除引用,而且还可以执行多次解除引用:例如:int *p;
P表示p指向整数,int **p;
而C表示p指向指向我们在Java中没有这个功能的整数的指针,所以它绝对正确且在技术上合法地说它作为句柄,C中也有指针算法的规则.它允许对具有约束的指针执行算术运算.
在C中,我们调用这样的机制来传递地址并使用指针变量接收它们作为传递,因为我们传递它们的地址并在形式参数中接收它们作为指针变量但在编译器级别将地址复制到指针变量中(因为这里的数据)是地址,即使它的数据)因此我们可以100%确定C严格按值传递(因为我们只传递数据)
(如果我们直接在C中传递数据,我们将其称为pass by value.)
在java中,当我们这样做时,我们用句柄来做; 因为它们不像in中那样被称为指针变量(如上所述),即使我们传递了引用,也不能说它是通过引用传递的,因为我们没有用Java中的指针变量来收集它.
因此Java 严格使用pass by value机制
我看到所有答案都包含相同的内容:按值传递。然而,最近 Brian Goetz 对 Valhalla 项目的更新实际上给出了不同的答案:
您可以在此处阅读更多信息:瓦尔哈拉状态。第 2 部分:语言模型
编辑: Brian Goetz 是 Java 语言架构师,领导诸如 Project Valhalla 和 Project Amber 之类的项目。
编辑 2020-12-08:更新瓦尔哈拉状态
- Object references (i.e. pointers) are passed by value and primitives are also passed by value. Meaning everything is always passed by value. I think the operative term here is Pass-by-value.
- I think Java Language Architect, who is leading Project Amber and Project Valhalla is a credible source to claim that it is not pass by value.
- And the most relevant reference of all is the Java Language Specification which states *"The effect of this is to assign the argument values to corresponding freshly created parameter variables of the method"*. (15.12.4.5). Note that it is avoiding the terminological confusion by not saying "pass by ..." at all. (FWIW, I disagree with the Goetz's characterization of "pass by value" and "pass references by value" as being semantically different. And I agree that he is contradicting himself.)
看看这段代码.这段代码不会抛出NullPointerException
...它将打印"Vinay"
public class Main {
public static void main(String[] args) {
String temp = "Vinay";
print(temp);
System.err.println(temp);
}
private static void print(String temp) {
temp = null;
}
}
如果Java通过引用传递,那么它应该抛出,NullPointerException
因为引用设置为Null.
- 不回答这个问题.OP要求解释,而不仅仅是证据.
主要的基础知识必须是引用的,
Java:初学者指南,第六版,Herbert Schildt
检查语言是否支持传递引用的简单测试只是编写传统的交换.你能用Java编写传统的swap(a,b)方法/函数吗?
传统的交换方法或函数接受两个参数并交换它们,以便传递给函数的变量在函数外部进行更改.它的基本结构看起来像
(非Java)基本交换功能结构
swap(Type arg1, Type arg2) {
Type temp = arg1;
arg1 = arg2;
arg2 = temp;
}
如果你能用你的语言编写这样的方法/函数,那么就可以调用
Type var1 = ...;
Type var2 = ...;
swap(var1,var2);
实际上切换变量var1和var2的值,语言支持pass-by-reference.但Java不允许这样的事情,因为它支持仅传递值而不是指针或引用.
不重复,但给那些在阅读了许多答案后可能仍然感到困惑的人的一点:
-
pass by value
在Java中是不等于要pass by value
在C ++中,虽然听起来好像是,这可能是为什么有困惑
分解它:
-
pass by value
在 C++ 中意味着传递对象的值(如果是对象),字面意思是对象的副本 -
pass by value
在 Java 中意味着传递对象的地址值(如果是对象),而不是像 C++ 那样真正的对象的“值”(副本) - 通过
pass by value
在 Java 中,对myObj.setName("new")
函数内部的对象(例如)进行操作会对函数外部的对象产生影响;通过pass by value
在C ++中,它具有NO在一个外面的效果。 - 但是,
pass by reference
在 C++ 中,对函数中的对象进行操作确实会对外部对象产生影响!与Java 中的相似(只是相似,不相同)pass by value
,不是吗?.. 人们总是重复“ Java 中没有通过引用传递”,=> BOOM,混乱开始......
所以,朋友们,一切都只是术语定义(跨语言)的差异,您只需要知道它是如何工作的,就是这样(尽管有时我承认它的名称有点令人困惑)!
围绕这个问题的许多困惑来自于 Java 试图重新定义“按值传递”和“按引用传递”的含义这一事实。重要的是要了解这些是行业术语,在该上下文之外无法正确理解。它们旨在在您编写代码时为您提供帮助并且理解起来很有价值,因此让我们首先了解一下它们的含义。
可以在此处找到两者的详细说明。
Pass By Value函数接收到的值是调用者正在使用的对象的副本。它对函数来说是完全独一无二的,你对该对象所做的任何事情都只会在函数中看到。
Pass By Reference函数接收到的值是对调用者正在使用的对象的引用。函数对 value 引用的对象所做的任何事情都会被调用者看到,并且从那时起它将处理这些更改。
从这些定义中可以清楚地看出,引用按值传递的事实是无关紧要的。如果我们接受这个定义,那么这些术语就变得毫无意义,所有语言都只是传递值。
无论您如何传递引用,它都只能按值传递。这不是重点。关键是您将自己对象的引用传递给函数,而不是它的副本。您可以丢弃收到的参考资料这一事实无关紧要。同样,如果我们接受这个定义,这些术语就变得毫无意义,每个人都在传递价值。
不,C++ 特殊的“引用传递”语法并不是引用传递的唯一定义。这纯粹是一种方便的语法,旨在使您在传入指针后不需要使用指针语法。它仍在传递指针,编译器只是向您隐藏了这个事实。它仍然按值传递该指针,编译器只是对你隐藏了它。
所以,有了这样的理解,我们可以看看Java,发现它实际上兼具了两者。所有 Java 基本类型总是按值传递,因为您收到调用者对象的副本并且无法修改它们的副本。所有 Java 引用类型总是按引用传递,因为您收到对调用者对象的引用并且可以直接修改它们的对象。
您无法修改调用者的引用这一事实与传递引用无关,并且在支持传递引用的每种语言中都是如此。
- 这些术语早在 Java 或 C 之前就存在了。指针只是实现其中一个的方法。如果您接受 Java 对它们的定义,那么它们就变得毫无意义,因为根据该定义,曾经创建的每种语言都只是按值传递。
- Java 没有重新定义这些术语。没有人有。它只是避免了 C 术语“指针”。
为了增加这一点,我想我会在这个主题中加入SCJP学习指南部分.这是通过对Java行为进行Sun/Oracle测试的指南,因此它是用于此讨论的良好来源.
7.3确定对象引用和原始值在传递到对参数执行赋值或其他修改操作的方法时的影响.
可以声明方法以获取基元和/或对象引用.您需要知道被调用方法如何(或者如果)调用者的变量会受到影响.传递给方法时,对象引用和原始变量之间的区别是巨大而重要的.要理解本节,您需要熟悉本章第一部分中介绍的作业部分.
传递对象引用变量
将对象变量传递给方法时,必须记住您传递的是对象引用,而不是实际的对象本身.请记住,引用变量包含一些位(表示底层VM)到达内存中特定对象(在堆上)的方式.更重要的是,您必须记住,您甚至没有传递实际的引用变量,而是传递引用变量的副本.变量的副本意味着您获得该变量中位的副本,因此当您传递引用变量时,您将传递表示如何到达特定对象的位的副本.换句话说,调用者和被调用的方法现在都具有相同的引用副本,因此它们都将引用堆上相同的(非副本)对象.
对于此示例,我们将使用java.awt包中的Dimension类:
1. import java.awt.Dimension;
2. class ReferenceTest {
3. public static void main (String [] args) {
4. Dimension d = new Dimension(5,10);
5. ReferenceTest rt = new ReferenceTest();
6. System.out.println("Before modify() d.height = " + d.height);
7. rt.modify(d);
8. System.out.println("After modify() d.height = "
9. }
10.
11.
12.
13. }
14. }
C:\Java Projects\Reference>java ReferenceTest Before modify() d.height = 10 dim = 11 After modify() d.height = 11
Java是否使用Pass-By-Value语义?
如果Java通过传递引用变量来传递对象,那是否意味着Java使用对象的pass-by-reference?不完全是,虽然你经常会听到并读到它.Java实际上是在单个VM中运行的所有变量的值传递.按值传递意味着传递变量值.这意味着,传递副本的变量!(再次有那个单词复制!)
如果您传递原始变量或引用变量没有区别,那么您总是传递变量中的位副本.因此,对于原始变量,您传递的是表示该值的位的副本.例如,如果传递值为3的int变量,则传递代表3的位的副本.然后,被调用的方法获取其自己的值副本,并根据需要进行操作.
如果您传递的是对象引用变量,那么您将传递表示对象引用的位的副本.然后,被调用的方法获得自己的引用变量副本,以便按照自己喜欢的方式进行操作.但是因为两个相同的引用变量引用完全相同的对象,如果被调用的方法修改了对象(例如,通过调用setter方法),调用者将看到调用者的原始变量引用的对象也已被更改.在下一节中,我们将讨论当我们谈论基元时图像如何变化.
按值传递的底线:被调用的方法不能更改调用者的变量,尽管对于对象引用变量,被调用的方法可以更改引用的变量对象.更改变量和更改对象之间有什么区别?对于对象引用,它意味着被调用的方法不能重新分配调用者的原始引用变量并使其引用不同的对象,或者为null.例如,在以下代码片段中,
void bar() {
Foo f = new Foo();
doStuff(f);
}
void doStuff(Foo g) {
g.setName("Boo");
g = new Foo();
}
传递原始变量
让我们看一下将原始变量传递给方法时会发生什么:
class ReferenceTest {
public static void main (String [] args) {
int a = 1;
ReferenceTest rt = new ReferenceTest();
System.out.println("Before modify() a = " + a);
rt.modify(a);
System.out.println("After modify() a = " + a);
}
void modify(int number) {
number = number + 1;
System.out.println("number = " + number);
}
}
Before modify() a = 1
number = 2
After modify() a = 1
与其他一些语言不同,Java不允许您选择按值传递或按引用传递
所有参数都按值传递.
方法调用可以将两个types of values
方法传递给方法
- 原始值的副本(例如,int和double类型的值)
- 对象引用的副本.
Objects themselves cannot be passed to methods
.当方法修改基本类型参数时,对参数的更改不会影响调用方法中的原始参数值.
参考类型参数也是如此.如果修改引用类型参数以使其引用另一个对象,则只有参数引用新对象 - 存储在调用方变量中的引用仍引用原始对象.
参考文献:Java™如何编程(早期对象),第十版
Java 仅按值传递。没有按引用传递,例如可以看下面的例子。
package com.asok.cop.example.task;
public class Example {
int data = 50;
void change(int data) {
data = data + 100;// changes will be in the local variable
System.out.println("after add " + data);
}
public static void main(String args[]) {
Example op = new Example();
System.out.println("before change " + op.data);
op.change(500);
System.out.println("after change " + op.data);
}
}
输出:
before change 50
after add 600
after change 50
正如迈克尔在评论中所说:
长话短说:
- 非基本体:Java传递Reference的值。
- 基元:公正的价值。
结束。
(2)太容易了。现在,如果您想考虑(1)的含义,请想象您有一个Apple类:
class Apple {
private double weight;
public Apple(double weight) {
this.weight = weight;
}
// getters and setters ...
}
然后当您将此类的实例传递给main方法时:
class Main {
public static void main(String[] args) {
Apple apple = new Apple(3.14);
transmogrify(apple);
System.out.println(apple.getWeight()+ " the goose drank wine...";
}
private static void transmogrify(Apple apple) {
// does something with apple ...
apple.setWeight(apple.getWeight()+0.55);
}
}
哦..但是您可能知道,您对执行以下操作会发生什么感兴趣:
class Main {
public static void main(String[] args) {
Apple apple = new Apple(3.14);
transmogrify(apple);
System.out.println("Who ate my: "+apple.getWeight()); // will it still be 3.14?
}
private static void transmogrify(Apple apple) {
// assign a new apple to the reference passed...
apple = new Apple(2.71);
}
}
A:Java 确实通过引用来操作对象,所有对象变量都是引用。但是,Java 不会通过引用传递方法参数;它按值传递它们。
以 badSwap() 方法为例:
public void badSwap(int var1, int var2)
{
int temp = var1;
var1 = var2;
var2 = temp;
}
当 badSwap() 返回时,作为参数传递的变量仍将保持其原始值。如果我们将参数类型从 int 更改为 Object,该方法也会失败,因为 Java 也按值传递对象引用。现在,这就是棘手的地方:
public 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("X: " + pnt1.x + " Y: " +pnt1.y);
System.out.println("X: " + pnt2.x + " Y: " +pnt2.y);
System.out.println(" ");
tricky(pnt1,pnt2);
System.out.println("X: " + pnt1.x + " Y:" + pnt1.y);
System.out.println("X: " + pnt2.x + " Y: " +pnt2.y);
}
如果我们执行这个 main() 方法,我们会看到以下输出:
X: 0 Y: 0
X: 0 Y: 0
X: 100 Y: 100
X: 0 Y: 0
该方法成功地改变了 pnt1 的值,即使它是按值传递的;然而,pnt1 和 pnt2 的交换失败了!这是混淆的主要来源。在 main() 方法中,pnt1 和 pnt2 只不过是对象引用。当您将 pnt1 和 pnt2 传递给 tricky() 方法时,Java 就像任何其他参数一样按值传递引用。这意味着传递给该方法的引用实际上是原始引用的副本。下面的图 1 显示了在 Java 将对象传递给方法后指向同一对象的两个引用。
图 1. 传递给方法后,一个对象至少会有两个引用
Java 按值复制和传递引用,而不是按对象。因此,方法操作将改变对象,因为引用指向原始对象。但是由于引用是副本,交换将失败。该方法引用交换,而不是原始引用。不幸的是,在方法调用之后,您只剩下未交换的原始引用。为了在方法调用之外成功进行交换,我们需要交换原始引用,而不是副本。
Java 总是按值传递参数。
Java 中的所有对象引用都是按值传递的。这意味着值的副本将传递给方法。但诀窍是传递值的副本也会更改对象的实际值。
请参考下面的例子,
public class ObjectReferenceExample {
public static void main(String... doYourBest) {
Student student = new Student();
transformIntoHomer(student);
System.out.println(student.name);
}
static void transformIntoDuleepa(Student student) {
student.name = "Duleepa";
}
}
class Student {
String name;
}
在这种情况下,它将是 Duleepa!
原因是 Java 对象变量只是指向内存堆中真实对象的引用。因此,即使Java通过值将参数传递给方法,如果变量指向一个对象引用,那么真实的对象也会发生变化。
这有点难以理解,但Java总是复制值 - 重点是,通常值是参考.因此,你最终得到同一个对象而不考虑它...
@Scott Stanchfield 先生写了一个很好的答案。这是您要确切验证他的意思的课程:
public class Dog {
String dog ;
static int x_static;
int y_not_static;
public String getName()
{
return this.dog;
}
public Dog(String dog)
{
this.dog = dog;
}
public void setName(String name)
{
this.dog = name;
}
public static void foo(Dog someDog)
{
x_static = 1;
// y_not_static = 2; // not possible !!
someDog.setName("Max"); // AAA
someDog = new Dog("Fifi"); // BBB
someDog.setName("Rowlf"); // CCC
}
public static void main(String args[])
{
Dog myDog = new Dog("Rover");
foo(myDog);
System.out.println(myDog.getName());
}
}
所以,我们从 main() 传递了一个叫做 的狗Rover
,然后我们为我们传递的指针分配了一个新地址,但最后,狗的名字不是Rover
,不是Fifi
,当然不是Rowlf
,但是Max
。
分两步理解:
您不能更改对对象本身的引用,但可以将此传递的参数用作对对象的引用。
如果您想更改引用后面的值,您只需在堆栈上声明一个具有相同名称“d”的新变量。查看带有标志的引用,@
您会发现引用已更改。
public static void foo(Dog d) {
d.Name = "belly";
System.out.println(d); //Reference: Dog@1540e19d
d = new Dog("wuffwuff");
System.out.println(d); //Dog@677327b6
}
public static void main(String[] args) throws Exception{
Dog lisa = new Dog("Lisa");
foo(lisa);
System.out.println(lisa.Name); //belly
}
我制作了这个小图,显示了如何创建和传递数据
注意:原始值作为值传递,对该值的第一个引用是方法的参数
这意味着:
- 您可以更改函数
myObject
内部的值 - 但是您不能更改
myObject
函数内部的引用,因为point
不是myObject
- 请记住,
point
和myObject
都是引用,是不同的引用,但是,这些引用指向相同的new Point(0,0)
有一种非常简单的方法可以理解这一点.让我们以C++传递参考.
#include <iostream>
using namespace std;
class Foo {
private:
int x;
public:
Foo(int val) {x = val;}
void foo()
{
cout<<x<<endl;
}
};
void bar(Foo& ref)
{
ref.foo();
ref = *(new Foo(99));
ref.foo();
}
int main()
{
Foo f = Foo(1);
f.foo();
bar(f);
f.foo();
return 0;
}
结果是什么?
1 1 99 99
因此,在bar()为传入的"引用"分配了一个新值之后,它实际上改变了从main本身传入的那个,解释了来自主打印99的最后一次f.foo()调用.
现在,让我们看看java所说的内容.
public class Ref {
private static class Foo {
private int x;
private Foo(int x) {
this.x = x;
}
private void foo() {
System.out.println(x);
}
}
private static void bar(Foo f) {
f.foo();
f = new Foo(99);
f.foo();
}
public static void main(String[] args) {
Foo f = new Foo(1);
System.out.println(f.x);
bar(f);
System.out.println(f.x);
}
}
它说:
1 1 99 1
Voilà,传递给酒吧的Foo的主要参考仍未改变!
这个例子清楚地表明,当我们说"按引用传递"时,java与C++不同.从本质上讲,java将"引用"作为"值"传递给函数,这意味着java是按值传递的.
Java是按值传递的.
这个帖子已经有了很好的答案.不知何故,关于原始数据类型和对象,我从未明确传递值/引用.因此,我通过以下代码测试了我的满意度和清晰度; 可能会帮助寻求类似清晰度的人:
class Test {
public static void main (String[] args) throws java.lang.Exception
{
// Primitive type
System.out.println("Primitve:");
int a = 5;
primitiveFunc(a);
System.out.println("Three: " + a); //5
//Object
System.out.println("Object:");
DummyObject dummyObject = new DummyObject();
System.out.println("One: " + dummyObject.getObj()); //555
objectFunc(dummyObject);
System.out.println("Four: " + dummyObject.getObj()); //666 (555 if line in method uncommented.)
}
private static void primitiveFunc(int b) {
System.out.println("One: " + b); //5
b = 10;
System.out.println("Two:" + b); //10
}
private static void objectFunc(DummyObject b) {
System.out.println("Two: " + b.getObj()); //555
//b = new DummyObject();
b.setObj(666);
System.out.println("Three:" + b.getObj()); //666
}
}
class DummyObject {
private int obj = 555;
public int getObj() { return obj; }
public void setObj(int num) { obj = num; }
}
如果该行b = new DummyObject()
未被注释,则此后进行的修改将在新对象上进行,即新实例化.因此,它不会反映在调用方法的地方.然而,否则,改变反映为仅对对象的"引用"进行修改,即-b指向相同的伪对象.
此主题(/sf/answers/870096741/)中的一个答案中的插图有助于深入理解.
- Java使用pass by value
- 传递原始数据时,它复制原始数据类型的值.
- 传递对象时,它复制对象的地址并传递给callee方法变量.
使用原始数据类型的示例:
public class PassByValuePrimitive {
public static void main(String[] args) {
int i=5;
System.out.println(i); //prints 5
change(i);
System.out.println(i); //prints 5
}
private static void change(int i) {
System.out.println(i); //prints 5
i=10;
System.out.println(i); //prints 10
}
}
使用对象的示例:
public class PassByValueObject {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("prem");
list.add("raj");
new PassByValueObject().change(list);
System.out.println(list); // prints [prem, raj, ram]
}
private void change(List list) {
System.out.println(list.get(0)); // prem
list.add("ram");
list=null;
System.out.println(list.add("bheem")); //gets NullPointerException
}
}
PT 1:房地产清单
有一个蓝色的,120平方英尺的"Tiny House",目前停放在1234 Main St,前面有一个修剪整齐的草坪和花坛.
聘请了一家本地公司的房地产经纪人并告知他们要保留该房屋的清单.
让我们称之为房地产经纪人"鲍勃".嗨鲍勃.
鲍勃保持他的名单,他称之为tinyHouseAt1234Main
最新的网络摄像头,允许他实时记录实际房屋的任何变化.他还记录了有多少人询问上市情况.鲍勃的viewTally
房子整数今天是42.
每当有人想要了解1234 Main St的蓝色小屋时,他们会问鲍勃.
鲍勃查看他的清单tinyHouseAt1234Main
并告诉他们所有关于它 - 颜色,漂亮的草坪,阁楼床和堆肥厕所等.然后他将他们的询问添加到他的viewTally
.他并没有告诉他们真实的实际地址,因为鲍勃的公司专注于可以随时移动的Tiny Houses.这个数字现在是43.
在另一家公司,房地产经纪人可能会明确表示他们的上市"点"指向1234 Main St的房子,表示*
旁边有一点,因为他们主要处理很少搬家的房子(尽管可能有理由这样做) .鲍勃的公司不打扰这样做.
现在,鲍勃当然不会把实际的房子放在卡车上直接向客户展示 - 这是不切实际的,也是荒谬的资源浪费.通过他的理货单的完整副本是一回事,但一直绕过整个房子是昂贵和荒谬的.
(旁白:鲍勃的公司也不会每次有人问起3D打印所列出的房屋的新的和独特的副本.这就是新贵,同样命名为基于网络的公司及其衍生产品 - 这是昂贵和缓慢的,以及人们经常让2家公司感到困惑,但无论如何他们都很受欢迎.
在其他一些靠近海洋的老公司,像鲍勃这样的房地产经纪人可能甚至不存在管理房源.客户可以咨询Rolodex"Annie"(&
简称)以获取房屋的直接地址.客户不是像鲍勃那样从列表中读取引用的房屋细节,而是从Annie(&
)获得房屋地址,然后直接前往1234 Main St,有时不知道他们在那里可能找到什么.
有一天,Bob的公司开始提供一种新的自动化服务,该服务需要客户感兴趣的房屋列表.
好吧,拥有该信息的人是Bob,所以客户端让Bob调用该服务并向其发送该列表的副本.
jobKillingAutomatedListingService(Listing tinyHouseAt1234Main, int viewTally)
鲍勃发送...
该服务最终会调用此列表houseToLookAt
,但它收到的确实是Bob的列表的完整副本,其中包含完全相同的VALUE,指的是1234 Main St.的房子.
这项新服务还有自己的内部统计数据,显示有多少人查看过该商家信息.该服务接受Bob的专业礼貌,但它并不真正关心并完全用自己的本地副本覆盖它.今天的计数是1,而鲍勃仍然是43.
自从鲍勃传递他viewTally
和他的上市的当前价值以来,房地产公司称之为"按价值传递" tinyHouseAt1234Main
.他实际上并没有经过整个实体房子,因为那是不切实际的.他也没有像Annie(&
)那样传递真实的实际地址.
但他正在传递他所拥有的参考价值的副本给房子.在某些方面看起来似乎是一种愚蠢的迂腐差异,但这就是他的公司如何运作...... ..............
PT II:事情变得混乱和危险......
新的自动化服务,不像其他一些时髦的金融和科学公司那样全功能和数学导向,可能会产生无法预料的副作用......
一旦给出了一个列表对象,它允许客户使用远程无人机机器人车队在1234 Main St 实际重新绘制REAL房屋!它允许客户控制机器人推土机,实际上挖掘花坛!这太疯狂了!!!
该服务还允许客户完全重定向houseToLookAt
到另一个地址的其他房子,而不涉及Bob或他的列表.突然之间,他们可能会看到4321 Elm St.,这与Bob的列表没有任何联系(幸好他们不能再造成任何损害).
鲍勃在他的实时网络摄像头上观看了这一切.他辞去了他唯一的工作职责,他告诉客户新的丑陋油漆工作和突然缺乏路边吸引力.他的上市是仍是1234主街,毕竟.新服务houseToLookAt
无法改变这一点.鲍勃tinyHouseAt1234Main
一如既往地准确,尽职地报告他的细节,直到他被解雇或者房子被完全摧毁了.
真正唯一的服务是不能用它houseToLookAt
的Bob原始列表的副本将地址从1234 Main St.更改为其他地址,或者是虚无的,或者像Platypus这样的随机类型的对象.鲍勃的上市仍然总是指向1234 Main St,无论它仍然值得.他像往常一样传递当前的价值.
将列表传递给新的自动化服务的这种奇怪的副作用让那些询问它如何工作的人感到困惑.真的,远程控制机器人的能力有什么区别,改变1234 Main的房子状态,实际上去那里并且因为安妮给你的地址造成严重破坏?
如果您通常关心的是列表中房屋的状态被复制和传递,看起来就像是一种挑剔的语义论证,对吧?
我的意思是,如果你的业务是实际购买房屋并将它们移动到其他地址(不像移动或Tiny Homes那样,这是平台的预期功能),或者你正在访问,重命名和改组整个邻居喜欢某种低级别的上帝玩疯子,那么也许你更关心的是传递那些特定的地址参考而不仅仅是房子细节的最新价值的副本......
Java通过引用按值和类类型传递基本类型
现在,人们喜欢无休止地争论"通过引用传递"是否是描述Java等人的正确方法.实际上.关键在于:
- 传递对象不会复制对象.
- 传递给函数的对象可以通过函数修改其成员.
- 传递给函数的原始值不能被函数修改.制作副本.
在我的书中,这被称为传递参考.
- Brian Bi - 哪些编程语言通过引用传递?
- 这个答案是完全错误的,只会造成混乱.Java是一种纯粹的传值语言.令你困惑的是,值可以是指向对象的指针.通过引用传递意味着可以在呼叫者侧改变对象的身份.例如,将新对象分配给方法参数也会影响在调用该方法的代码中传递的指针.
- @Dennis 字符串不是基元,它们是对象。
一切都是通过价值传递的.基元和对象引用.但是,如果对象允许,则可以更改对象.
将对象传递给方法时,将传递引用,并且方法实现可以修改该对象.
void bithday(Person p) {
p.age++;
}
对象本身的引用是通过值传递的:您可以重新分配参数,但更改不会反映出来:
void renameToJon(Person p) {
p = new Person("Jon"); // this will not work
}
jack = new Person("Jack");
renameToJon(jack);
sysout(jack); // jack is unchanged
作为效果,"p"是参考(指向对象的指针)并且不能改变.
原始类型按值传递.对象的引用也可以被认为是原始类型.
总结一下,一切都是按价值传递的.
最短的答案:)
- Java具有pass-by-value(并且按值传递引用).
- C#也有传递参考
在C#中,这是通过"out"和"ref"关键字完成的.
按引用传递:传递变量的方式是即使在方法外部也反映了方法内部的重新分配.
下面是一个传递引用(C#)的例子.java中不存在此功能.
class Example
{
static void InitArray(out int[] arr)
{
arr = new int[5] { 1, 2, 3, 4, 5 };
}
static void Main()
{
int[] someArray;
InitArray(out someArray);
// This is true !
boolean isTrue = (someArray[0] == 1);
}
}
另请参见:MSDN库(C#):通过ref和out传递数组
另请参见:MSDN库(C#):按值和引用传递
Java 中有一个解决方法供参考。让我通过这个例子来解释:
public class Yo {
public static void foo(int x){
System.out.println(x); //out 2
x = x+2;
System.out.println(x); // out 4
}
public static void foo(int[] x){
System.out.println(x[0]); //1
x[0] = x[0]+2;
System.out.println(x[0]); //3
}
public static void main(String[] args) {
int t = 2;
foo(t);
System.out.println(t); // out 2 (t did not change in foo)
int[] tab = new int[]{1};
foo(tab);
System.out.println(tab[0]); // out 3 (tab[0] did change in foo)
}}
我希望这有帮助!
简单的程序
import java.io.*;
class Aclass
{
public int a;
}
public class test
{
public static void foo_obj(Aclass obj)
{
obj.a=5;
}
public static void foo_int(int a)
{
a=3;
}
public static void main(String args[])
{
//test passing an object
Aclass ob = new Aclass();
ob.a=0;
foo_obj(ob);
System.out.println(ob.a);//prints 5
//test passing an integer
int i=0;
foo_int(i);
System.out.println(i);//prints 0
}
}
从C/C++程序员的角度来看,java使用pass by value,因此对于原始数据类型(int,char等),函数中的更改不会反映在调用函数中.但是当您传递一个对象并在函数中更改其数据成员或调用可以更改对象状态的成员函数时,调用函数将获得更改.
- @ mrres1不完全正确.您只能为每个文件定义一个*public*顶级类/接口.每个文件支持几个类是第一个Java版本的遗留物,它没有嵌套类,但它仍然受支持,尽管经常不赞成.
Java 确实通过引用来操作对象,并且所有对象变量都是引用。但是,Java 不会通过引用传递方法参数;它按值传递它们。
以 badSwap() 方法为例:
public void badSwap(int var1, int
var2{ int temp = var1; var1 = var2; var2 =
temp; }
当 badSwap() 返回时,作为参数传递的变量仍将保持其原始值。如果我们将参数类型从 int 更改为 Object,该方法也会失败,因为 Java 也按值传递对象引用。现在,这就是棘手的地方:
public 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("X:
" + pnt1.x + " Y: " +pnt1.y);
System.out.println("X: " + pnt2.x + " Y:
" +pnt2.y); System.out.println(" ");
tricky(pnt1,pnt2);
System.out.println("X: " + pnt1.x + " Y:" + pnt1.y);
System.out.println("X: " + pnt2.x + " Y: " +pnt2.y); }
如果我们执行这个 main() 方法,我们会看到以下输出:
X:0 Y:0 X:0 Y:0 X:100 Y:100 X:0 Y:0
该方法成功地改变了 pnt1 的值,即使它是按值传递的;然而,pnt1 和 pnt2 的交换失败了!这是混淆的主要来源。在 main() 方法中,pnt1 和 pnt2 只不过是对象引用。当您将pnt1 和pnt2 传递给tricky() 方法时,Java 就像任何其他参数一样按值传递引用。这意味着传递给该方法的引用实际上是原始引用的副本。下面的图 1 显示了在 Java 将对象传递给方法后指向同一对象的两个引用。
Java 按值复制和传递引用,而不是按对象。因此,方法操作将改变对象,因为引用指向原始对象。但由于引用是副本,交换将失败。如图 2 所示,方法引用交换,而不是原始引用。不幸的是,在方法调用之后,您只剩下未交换的原始引用。为了在方法调用之外成功进行交换,我们需要交换原始引用,而不是副本。
在我看来,"按价值传递"是一种可怕的方式来单独描述两个相似但不同的事件.我猜他们应该先问我.
对于原语,我们将原语的实际值传递给方法(或构造函数),无论是整数"5",字符"c",还是你有什么.然后,该实际值成为其自己的本地原语.但是对于对象,我们所做的只是给同一个对象一个额外的引用(一个本地引用),这样我们现在有两个引用指向同一个对象.
我希望这个简单的解释有所帮助
- "按值传递"是计算机科学中的标准术语,自20世纪50年代以来一直存在.现在抱怨它没有意义.
Java始终是按值传递的,参数是传递的变量的副本,所有对象都是使用引用定义的,而reference是一个变量,用于存储对象在内存中的存储地址。
检查注释以了解执行中会发生什么;按照数字显示执行流程。
class Example
{
public static void test (Cat ref)
{
// 3 - <ref> is a copy of the reference <a>
// both currently reference Grumpy
System.out.println(ref.getName());
// 4 - now <ref> references a new <Cat> object named "Nyan"
ref = new Cat("Nyan");
// 5 - this should print "Nyan"
System.out.println( ref.getName() );
}
public static void main (String [] args)
{
// 1 - a is a <Cat> reference that references a Cat object in memory with name "Grumpy"
Cat a = new Cat("Grumpy");
// 2 - call to function test which takes a <Cat> reference
test (a);
// 6 - function call ends, and <ref> life-time ends
// "Nyan" object has no references and the Garbage
// Collector will remove it from memory when invoked
// 7 - this should print "Grumpy"
System.out.println(a.getName());
}
}
传值的底线:被调用的方法不能改变调用者的变量,尽管对于对象引用变量,被调用的方法可以改变变量引用的对象。改变变量和改变对象有什么区别?对于对象引用,这意味着被调用的方法不能重新分配调用者的原始引用变量并使其引用不同的对象,或者为 null。
我从一本关于 Java 认证的书中获取了这段代码和解释,并做了一些小改动。
我认为这是对对象按值传递的一个很好的说明。在下面的代码中,重新分配 g 不会重新分配 f!在 bar() 方法的最后,创建了两个 Foo 对象,一个由局部变量 f 引用,另一个由局部(参数)变量 g 引用。
因为 doStuff() 方法有一个引用变量的副本,所以它有一种获取原始 Foo 对象的方法,例如调用 setName() 方法。但是, doStuff() 方法无法访问 f 引用变量。所以 doStuff() 可以改变 f 所指对象内的值,但是 doStuff() 不能改变 f 的实际内容(位模式)。换句话说,doStuff() 可以改变 f 所引用的对象的状态,但它不能让 f 引用不同的对象!
package test.abc;
public class TestObject {
/**
* @param args
*/
public static void main(String[] args) {
bar();
}
static void bar() {
Foo f = new Foo();
System.out.println("Object reference for f: " + f);
f.setName("James");
doStuff(f);
System.out.println(f.getName());
//Can change the state of an object variable in f, but can't change the object reference for f.
//You still have 2 foo objects.
System.out.println("Object reference for f: " + f);
}
static void doStuff(Foo g) {
g.setName("Boo");
g = new Foo();
System.out.println("Object reference for g: " + g);
}
}
package test.abc;
public class Foo {
public String name = "";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
请注意,以下控制台输出中的对象引用未更改:
控制台输出:
f 的对象引用:test.abc.Foo@62f72617
g 的对象引用:test.abc.Foo@4fe5e2c3
f 的 Boo 对象参考:test.abc.Foo@62f72617
Java按值传递参数,但对于对象变量,值实际上是对象的引用.由于数组是对象,因此以下示例代码显示了差异.
public static void dummyIncrease(int[] x, int y)
{
x[0]++;
y++;
}
public static void main(String[] args)
{
int[] arr = {3, 4, 5};
int b = 1;
dummyIncrease(arr, b);
// arr[0] is 4, but b is still 1
}
main()
arr +---+ +---+---+---+
| # | ----> | 3 | 4 | 5 |
+---+ +---+---+---+
b +---+ ^
| 1 | |
+---+ |
|
dummyIncrease() |
x +---+ |
| # | ------------+
+---+
y +---+
| 1 |
+---+
Java 按值传递一切!!
//通过传入名称和年龄创建一个对象:
PersonClass variable1 = new PersonClass("Mary", 32);
PersonClass variable2;
//变量2和变量1现在引用同一个对象
variable2 = variable1;
PersonClass variable3 = new PersonClass("Andre", 45);
// 变量 1 现在指向变量 3
variable1 = variable3;
//这是什么输出?
System.out.println(variable2);
System.out.println(variable1);
Mary 32
Andre 45
如果你能理解这个例子,我们就完成了。否则,请访问此网页以获取详细说明:
网页
It seems everything is call by value in java as i have tried to understand by the following program
Class-S
class S{
String name="alam";
public void setName(String n){
this.name=n;
}}
Class-Sample
public class Sample{
public static void main(String args[]){
S s=new S();
S t=new S();
System.out.println(s.name);
System.out.println(t.name);
t.setName("taleev");
System.out.println(t.name);
System.out.println(s.name);
s.setName("Harry");
System.out.println(t.name);
System.out.println(s.name);
}}
Output
alam
taleev
alam
taleev
harry
As we have define class S with instance variable name with value taleev so for
all the objects that we initialize from it will have the name variable with value of taleev but if we change the name's value of any objects then it is changing the name of only that copy of the class(Object) not for every class so after that also when we do System.out.println(s.name) it is printing taleev only we can not change the name's value that we have defined originally, and the value that we are changing is the object's value not the instance variable value so once we have define instance variable we are unable to change it
So i think that is how it shows that java deals with values only not with the references
为原始变量的内存分配可以通过理解 这个
我试图简化上面的示例,仅保留问题的实质。让我将其作为一个易于记忆和正确应用的故事来介绍。故事是这样的:您有一只宠物狗吉米(Jimmy),尾巴长12英寸。您出国旅行时,会把它交给兽医待几个星期。
兽医不喜欢吉米的长尾巴,所以他想把它切成两半。但是,作为一名好兽医,他知道自己无权残害他人的狗。因此,他首先制作了一只狗的克隆体(使用了新的关键字),然后剪下了克隆体的尾巴。当狗终于回到你身边时,它的原始尾巴整齐了。美好结局 !
下次旅行时,您会无意中将狗带到邪恶的兽医那里。他还讨厌长尾巴,因此将其削减到了2英寸。但是他是对您亲爱的吉米(Jimmy)这么做的,而不是对它的模仿。当您返回时,您会震惊地看到Jimmy可怜地摇着2英寸的存根。
故事的寓意:当您传授宠物时,您便将宠物的全部和不受约束的监护权交给了兽医。他可以自由发挥任何破坏作用。按值传递,按引用传递,按指针传递都只是技术上的争执。除非兽医首先克隆它,否则他最终将残破原始狗。
public class Doggie {
public static void main(String...args) {
System.out.println("At the owner's home:");
Dog d = new Dog(12);
d.wag();
goodVet(d);
System.out.println("With the owner again:)");
d.wag();
badVet(d);
System.out.println("With the owner again(:");
d.wag();
}
public static void goodVet (Dog dog) {
System.out.println("At the good vet:");
dog.wag();
dog = new Dog(12); // create a clone
dog.cutTail(6); // cut the clone's tail
dog.wag();
}
public static void badVet (Dog dog) {
System.out.println("At the bad vet:");
dog.wag();
dog.cutTail(2); // cut the original dog's tail
dog.wag();
}
}
class Dog {
int tailLength;
public Dog(int originalLength) {
this.tailLength = originalLength;
}
public void cutTail (int newLength) {
this.tailLength = newLength;
}
public void wag() {
System.out.println("Wagging my " +tailLength +" inch tail");
}
}
Output:
At the owner's home:
Wagging my 12 inch tail
At the good vet:
Wagging my 12 inch tail
Wagging my 6 inch tail
With the owner again:)
Wagging my 12 inch tail
At the bad vet:
Wagging my 12 inch tail
Wagging my 2 inch tail
With the owner again(:
Wagging my 2 inch tail
首先让我们了解 Java 中的内存分配:堆栈和堆是 JVM 为不同目的分配的内存的一部分。堆栈内存在创建时预先分配给线程,因此一个线程无法访问其他线程的堆栈。但是 Heap 可用于程序中的所有线程。
对于一个线程,Stack 存储所有本地数据、程序元数据、原始类型数据和对象引用。并且,堆负责实际对象的存储。
Book book = new Book("Effective Java");
在上面的例子中,引用变量是“book”,它存储在堆栈中。new operator -> new Book("Effective Java") 创建的实例存储在 Heap 中。引用变量“book”具有在堆中分配的对象的地址。假设地址是 1001。
考虑传递原始数据类型,即 int、float、double 等。
public class PrimitiveTypeExample {
public static void main(string[] args) {
int num = 10;
System.out.println("Value before calling method: " + num);
printNum(num);
System.out.println("Value after calling method: " + num);
}
public static void printNum(int num){
num = num + 10;
System.out.println("Value inside printNum method: " + num);
}
}
输出为:调用方法前的值:10 方法内部的值:20 调用方法后的值:10
整数编号 = 10; -> 这会在正在运行的线程的堆栈中为“int”分配内存,因为它是一种原始类型。现在,当调用 printNum(..) 时,会在同一个线程中创建一个私有堆栈。将“num”传递给此方法时,会在方法堆栈帧中创建“num”的副本。数量=数量+10;-> 这会增加 10 并修改方法堆栈帧中的 int 变量。因此,方法栈帧外的原始 num 保持不变。
考虑将自定义类的对象作为参数传递的示例。
在上面的例子中,ref变量“book”驻留在执行程序的线程栈中,当程序执行new Book()时,在Heap空间中创建了类Book的对象。Heap 中的这个内存位置由“book”引用。当“book”作为方法参数传递时,在同一线程堆栈内的方法的私有堆栈帧中创建“book”的副本。因此,复制的引用变量指向 Heap 中类“Book”的同一个对象。
方法堆栈帧中的引用变量为同一对象设置了一个新值。因此,当原始引用变量“book”获取其值时会反映出来。请注意,在传递引用变量的情况下,如果在被调用的方法中再次初始化它,它将指向新的内存位置,任何操作都不会影响 Heap 中的先前对象。
因此,当任何东西作为方法参数传递时,它始终是 Stack 实体——原始变量或引用变量。我们从不传递存储在堆中的东西。因此,在Java中,我们总是在堆栈中传递值,并且是按值传递。
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
Let's understand step by step:
测试 t = 新测试();众所周知,它将在堆中创建一个对象并将引用值返回给 t。例如,假设 t 的值为 0x100234(我们不知道实际的 JVM 内部值,这只是一个示例)。
第一张图
new PassByValue().changeValue(t);
将引用 t 传递给函数时,它不会直接传递对象 test 的实际引用值,而是会创建 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,如下所示:
第三张图
希望这会有所帮助。
为简单起见,
其pass reference by value
:
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
}