Is Java “pass-by-reference” or “pass-by-value”?
我一直认为Java是通过引用传递的。
但是,我看到过一些博客文章(例如,这个博客)声称它不是。
我想我不明白他们的区别。
解释是什么?
- 我认为,在这个问题上的许多困惑与这样一个事实有关:不同的人对"参考"一词有不同的定义。来自C++背景的人假设"引用"必须指C++中的含义,C背景中的人假设"引用"必须与他们的语言中的"指针"相同,等等。说Java通过引用是否正确实际上取决于"引用"的含义。
- 我一直在尝试使用评估策略文章中找到的术语。值得注意的是,尽管文章指出术语因社区而异,但它强调了call-by-value和call-by-reference的语义差异非常关键。(就我个人而言,这些天我更喜欢使用call-by-object-sharing而不是call-by-value[-of-the-reference],因为这在高层描述了语义,并且不会与基础实现call-by-value产生冲突。)
- pgras在这个线程中有一个非常好的答案:stackoverflow.com/questions/589919/…
- 当然Java通过引用传递所有的对象!对于问题中提到的博客,foo(dog d)只是创建一个新的对象,并使d指向新的地址(d最初指的是对象,它也被a dog引用——这就是所谓的"pass-by-reference")。它正在更改指针,原始对象仍在其位置上,没有任何更改。关键是根本没有对象值的副本,它只复制引用值。
- 你不能说它是"pass-by-value",仅仅因为方法不改变传入值,你必须检查是否有任何值复制。
- @重力:你能把你的评论放在一个巨大的广告牌上吗?简而言之,这就是整个问题。它表明了这一切都是语义。如果我们不同意引用的基本定义,那么我们就不会同意这个问题的答案:)
- 我认为混淆是"引用传递"和"引用语义"。Java是通过引用语义传递值的。
- 我通常使用传递指针这个词。当然,指针是按值传递的,但毕竟,指针不是任何人关心的。这就清楚地表明,指针(而不是对象)正在被复制,对调用者来说,改变被指向的对象是可见的,而更改副本所指向的内容则是不可见的。
- 这里的许多答案都是错误的或误导性的。我们应该忘记语义,用示例演示特定语言的属性。
- 如果Java是"通过值",那么下面的程序的结果是:// 0 / / 1 / / 2(可能是您正在使用的字符串),这是一个不可变的类,公共类NexField{Puint int x=0;}公共类HeloWorld {公共静态空隙OxEx(NexFieltR){Tr.x++;}公共静态空隙main(String []ARGS){NexFrand Trf=new newfile();system.out.println(trf.x);changex(trf);system.out.println(trf.x);changex(trf);system.out.println(trf.x);
- "重力",虽然你绝对正确,来自C++的人会本能地对"参考"一词有不同的直觉,我个人认为身体更被埋葬在"By"中。"过去"是令人困惑的,因为它在爪哇绝对不同于"通过A"。在C++中,它不是口语的。在C++中,你可以说"传递一个引用",并且可以理解它将通过EDCOX1×5测试。
- @在您的示例中,changeX()方法将引用作为参数。该引用作为值传递给它。考虑如果你的方法是这样定义的:EDCOX1,1,如果Java是PBR,那么调用EDCOX1×0的方法将完全关闭它的对象。因为它是PBV,所以在changeX()返回之后什么都不会发生。
- 从来没有理解的困惑,通过Java传入通过与通过值混淆(C/C++背景,现在是C程序员)。使用指针调用方法会传递指针,并允许您更改基础对象。将指针传递给指针允许您更改对象的原始指针。Java没有"指针指针"的概念,就是这样。(C++引用只是模糊的指针)
- 有些概念只是有点困难,语言试图使它们"安全"或"隐藏"它们并不能帮助我们。这个问题有超过3/4的观点和55个答案…真正的答案应该是:"为你自己画一个图表,一直看着它直到你明白了。"还有比这更困难的问题,如果你不能很容易地得到它,作为一个程序员,它对你来说并不是好兆头。
- Java语言规范定义了一个"引用"作为指针。此外,计算机科学中"价值调用"和"参考调用"的定义并不含糊。"按值调用"表示方法中参数的更改对调用方不可见;"按引用调用"表示不可见。关于"这取决于你如何定义‘参考’"的内容是不相关和不适用的。Java方法调用是通过值来明确调用的。
- 1。Java是按值传递的。2。对于基元,传递实际值的副本。三。对于对象的引用,传递引用的副本(遥控器)。javarach.com/campfire/storypassby.jsp
- 看看下一个答案,它真的回答了问题:Java做的是副值。时期。
- 它是传递值。所以Java将复制一个你手上的对象作为一个参数。但是,对该对象内的对象(内存地址)的引用是值,因此被复制并仍然指向同一个内存。因此,如果更改包含在作为参数传递的对象内的引用对象,它将更改内存,从而更改原始对象。但是,如果只更改作为参数传递的对象,则原始对象不变。原语(int、float、array等)被复制为值,并且没有这种类型的引用。
- 记住这一点:玫瑰是红色的,紫罗兰是蓝色的,如果它是爪哇的话,它就有价值了。
- Java像C @凯文那样在C中传递指针。值得一看-geeksforgeks.org/…
- Java通过值,就像C和它的变体一样。
Java总是通过值传递。不幸的是,当我们传递一个对象的值时,我们正在传递对它的引用。这对初学者来说很困惑。
就像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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中没有改变,因为对象引用是通过值传递的。如果是通过引用传递的,那么main中的aDog.getName()会在调用foo之后返回"Fifi"。
同样地:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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"。它不是-您正在获取的引用是一个值引用,如果覆盖,则在退出函数时将恢复该值引用。
- 但是请注意,整型是通过值传递的,而不是通过引用传递的。同样,不可变类型是通过引用传递的,但"kinda"的作用是通过值传递它们:最常见的例子是字符串。这允许不可变对象的内部空间优化。
- 洛伦佐:不,Java中的所有东西都是通过值传递的。原语按值传递,对象引用按值传递。对象本身永远不会传递给方法,但对象总是在堆中,只有对对象的引用才会传递给方法。
- 我试图用一种很好的方式来想象物体经过:想象一个气球。调用一个fxn就像把第二个字符串绑在气球上,然后把线交给fxn。参数=new balloon()将剪切该字符串并创建一个新的气球(但这对原始气球没有影响)。尽管如此,parameter.pop()仍将弹出它,因为它跟随字符串到达相同的原始气球。Java是通过值传递的,但传递的值并不深,它处于最高级别,即基元或指针。不要将其与对象完全克隆并传递的深度传递值混淆。
- 如果传递给构造函数的名称与foo方法中使用的名称不同,您的观点可能会更清楚。
- 令人困惑的是,对象引用实际上是指针。在开始的时候,太阳称它们为指针。然后市场部通知说"指针"是个坏词。但是您仍然可以在nullpointerException中看到"正确"的术语。
- BeleReja,不是任何能在法庭上坚持的东西,那是我的回忆,可能是有瑕疵的,但是一些证据可以在这里找到:Java.Sun.com/DOCS/Boo/JL/TyddieEdith/HTML/Helip;
- 我喜欢把pass-by值(与函数的参数有关)看作是每个函数都对传递的对象进行本地复制。一般来说,依赖于卫星方法,在一些更大的状态上做它的工作似乎比明确定义通过和返回的状态更模糊。
- 不是说"对象引用通过值传递"就是人们说"通过引用传递"时的意思吗?我的意思是,在C++中,你不会调用EDCOX1×0"逐值",但它与Java完全一样。
- Java是通过变量值的拷贝传递的。传递原语或对象没有区别!唯一的区别是原语变量直接包含它的值,而对象变量包含一个位值,这有助于JVM在堆中查找对象。此外,在这两种情况下,这些引用值在提供给方法之前都会被复制。最后,请不要提P……。在谈论Java的时候,甚至不说这样做是不对的!
- 是的,Timmmm,我认为在C++引用的上下文中,厄兰多的反例是错误的。确实,在Java中,它们"通过值传递引用",但它意味着与C++程序员在"通过引用"时所期望的完全一样。
- @Ubershmekel-不,这不正确。"在爪哇中传递值引用与C++中"按值传递指针"完全相同。通过C++中的引用允许调用函数/方法实际改变用来传递值的变量。传递值始终允许被调用的函数/方法读取并跟踪通过值传递的指针…
- Java从C/C++等语言中修改"通过引用"的含义。在C/C++中,"通过引用"一词意味着传递变量地址的值(这是指针的值)。,而不是变量本身。对内存中"xyz"地址处的对象/变量进行更改(更改原始地址)。当改变原稿时就这样做了(为什么要通过整件事?)。传递值传递实际对象/变量的副本以保留原始对象/变量。Java没有给出选择。OO对象通过引用传递(C/C++术语),原语通过值传递。
- 将方法参数视为用值初始化的局部变量。
- @Erlando,我们能有一个类似的数组对象区别的例子吗?
- @ BillR -C是严格按值传递的;C++增加了通过引用语义,但决不是第一个实现这些语义的语言。"传递引用"一词具有非常具体的含义,而Java根本没有实现它。保持术语的简单性和它们的实际含义,并且更容易理解和解释。Java通过值传递所有的东西,包括指针。(如果在Java中简单地避免使用"引用"一词,那也是最简单的。它们是指针……)
- @Scott StanchField当我将值从方法/函数1传递到方法/函数2时,我应该看到一个1中的值的副本,并发送到如果我更改2中的值,则不会看到1中的更改。这不是它在Java中的工作方式。我已经完成了,几分钟前刚刚完成了,将一个变量传递给另一个方法,并让它更新,并让它更新调用方法中的值。这时我想要这种行为。但有时这可能是一个模糊的问题,当人们不寻找它时,它可能会导致这种效果。这确实是通过内存引用传递的。
- @Scott Stanchfield和C不是严格按值传递的。查看pass-by值和pass-by引用链接:publib.boulder.ibm.com/infocenter/comphelp/v8v101/…查看pass-by值上的链接。这就是其他编程语言定义它的方式。Java是通过引用来调用的,因为在第二个函数(方法)中所做的操作确实改变了调用/第一函数中的变量。JAVA可以在我的腿上撒尿,告诉我正在下雨,但它并没有这样做。Java已经选择了逐值的术语。
- @比尔不是这样的。Java是通过(new L)值。通过引用/ /(new L)值/宏的术语是由编程语言的Scott Strachey理论定义的(例如)术语,Java绝对通过(新L)值。您的小测试示例没有更新调用者的变量,而是可能更新了调用者变量引用的对象。
- 基本上,作为函数参数传递的Java引用类似于作为函数参数传递的C++指针(不通过引用;引用引用的C++约定),当您在函数中修改它时,您正在修改局部变量,既不引用对象,也不修改传递的引用。
- 实际上,这在术语上是一个两难的问题。考虑C++中的同一个例子,每个人都希望它的"引用通过"(或者它的引用是通过值传递的)。void foo(Dog & d) { d.name.equals("Max"); // true // d = *(new Dog("Fifi")); //<- C++ does not allows this //So what is point in in arguing passed by reference or not }
- 在我心中,我和比尔的人群在一起…但是,对我的窥视:考虑到原始对象指针没有丢失,而是在函数退出后保持不变。这就是为什么他们要区分的原因——他们说当传递给函数时会生成引用/指针值的副本,然后可以重写。我仍然不喜欢称之为"传递值",但是一些编程理论的人说是的,所以我们都必须同意。
- @比尔难以置信的是,尽管答案非常清楚,你仍然反对它。Java只传递指针。指针是一种不透明的类型,我们可以将其视为原始数字。这个数字是按值传递的。所以Java传递指针,这些指针是按值传递的。
- @Mikebraun如果你真的学习了使用指针的编程(比如使用C语言),你会理解当你使用指针作为函数参数时,你是"通过引用传递"。您正在使用指针引用内存中的特定位置。也就是说,通过指向内存位置,您正在引用它。为了理解这个概念,你需要陈述多少种方式?你有没有忽视我赞成的论点,或者只是不理解它们?或者更糟的是,因为你不想理解他们而不理解他们?
- @比尔,我有一个补偿。SCI精通C语言和C++编程。我已经构建了编译器和CPU模拟器。我认为我对语言理论和低级系统动力学都有很好的理解。我可以毫不怀疑地说你错了。传递指针不是通过引用传递的。指针本身是一个数字,可以通过值或引用传递。在C++中,你可以两者兼而有之。在爪哇,你只能通过价值观。已经向您展示了不能更改保存指针的变量。只能跟随指针,但这不会更改调用方法。
- 因此,通过引用传递指针意味着,如果您随后重新分配该引用(在传递给它的代码块内),您将更改该实际指针值,从而有效地将其与它指向的原始对象分离,从而使该对象符合垃圾收集的条件(如果没有其他指针指向它)。而这个y在Java中不能使用OU,因为您从不传递指针的引用,而只是指针的值的副本。对复制的重新分配仅指在其他地方复制的点。原始指针保持不变。
- 第一个片段不应该跟在第二个片段后面吗?或者我是不是看错了?
- 听起来你刚刚通过了推荐信?我将支持这样一个事实,Java仍然是一个复制的通过引用语言。事实上,它是一个复制的参考并不改变术语。两个引用仍然指向同一个对象。这是一个纯粹主义者的论点…
- @约翰斯特里克勒,我最近看到一个例子,我的同事在被调用方法的末尾"使对象参数无效",并期望在调用方看到它"为空"。所以,你说得很对,它可能是"纯粹主义",但许多人仍然忽视它(他们仍然产生代码行)。
- 可以将爪哇中的对象声明视为C/C++指针(它们引用对象,它们可以是空的,等等)。人们声称Java完全通过了值,因为这些引用本身是通过值传递的(以C/C++指针通过值传递的方式相同——指针被复制,但指向对象不是)。因此,如果您考虑通过C/C++中的指针来传递引用,那么Java是通过引用传递的。异常是int类的原语,这些值在爪哇中总是通过值(复制)传递。
- @埃兰多,我试图更新答案来反映你的评论。请回顾一下,我不想在这个问题上误导别人。:)
- 有C/C++背景,我认为它是通过值:EDCOX1,0,通过引用:EDCOX1,1,和通过指针:EDCOX1×2。Java对我来说就像传递引用,即使在编译器语言中,也意味着调用者的对象位置可以被替换,也可以被指针传递。每当我听到Java的对象是通过值传递时,它会让我觉得当没有的时候有一个浅拷贝。所以这是一个C++引用。
- S&A 243;Java通过值传递对象的引用?
- @ BillR:"Java修改了‘通过引用’的含义,从C/C++的语言……"这就是你误解的根源。Java并没有比我更"修改通过引用"的含义。这个词在计算机科学中已经有了意义。试图说服人们"参照传递"在不同的语言中有不同的含义是无用的和混乱的。
- @billr pass-by-reference:void f(std::string&s)s=std::string("另一个字符串");pass pointer-by-value:void f(std::string*s)s=new std::string("另一个字符串");如果获取字符串的地址并将其传递给第二个函数,则不会更改原始字符串,但如果通过引用将其传递给第一个函数,则yo你是。
- @肖恩Java的行为不象C++ EDCOX1 1。如果在函数func(Dog &d)的主体中重新分配d,则用于调用函数的对象也将被重新分配。它的行为更像是func(Dog *d)。如果在函数体中重新分配指针,则不会重新分配传递给函数的原始指针。
- 没有人提到过c使用ref关键字实现pass-by引用。
- public class trial static trial t_=new trial();public class trial1 private string s="hmm";public static void main(string[]args)trial1 t=t_u.new trial1();foo(t);system.out.println("main:"+t.s);private static void foo(trial1 t)t=t_u.new trial1();//专线t.s="noo";system.out.println("foo:"+t.s);如果删除标记为特殊行的行,两个系统操作都将返回更新的值。这是否意味着我们正在向foo发送Trial1对象的指针?请使用格式化程序查看代码。
- @reventhkumar-很难从格式中分辨出来;我假设您的特殊行是"t=t_.new trial1()"行?如果是,那么是的。您有一个指针传递给foo。如果运行特殊行,参数t将更改为指向新的试用对象,并且该新trial1中的字符串将更改。如果删除了该行,foo将跟踪指向传入的trial1的指针并更改其字符串。
- 很抱歉,但是您可以重定引用的目标并不能改变您收到的是对实际对象的引用的事实。基本上,这个定义的全部问题是,它意味着在任何语言中都不存在引用传递之类的东西。根据这种推理,所有语言都是传递值的。
- @cdaragorn pass by value表示传递变量的值。传递引用基本上意味着传递变量本身。重点是这种情况是隐含的,即你可以调用foo(x),而不必调用foo(&x)和取消引用foo中的x。正是这种二分法产生了差异,而不是传递的值的类型。不幸的是,许多人只关注这一点,这必然会造成混乱。
- @这就是问题所在。不可能通过变量本身。在您的示例(foo(&x))中,您不会传递x。您传递的是对x的引用。根据该定义,不存在传递引用之类的事情,因为它永远无法完成。
- @cdaragorn"在您的示例中(foo(&x))…你提到x"嗯,我就是这么说的。
- 好的,那么foo(&x)和foo(*x)没有什么不同。它们都通过值传递引用,所以通过Java的定义,它们都是通过值传递的。传递引用的整个点从来不是传递实际值的方式,而是用户希望传递给您的对象的方式。这个定义使其毫无意义。完全不可能通过任何方式传递任何东西,除了价值。
- @对象指针被复制的事实与对象被引用传递的概念不同。因为它们不是通过引用传递的,所以复制它们的引用。
- 这会导致严重的问题。因为当你在函数中传递一个对象作为参数时,你不知道它是否会被使用函数的用户修改。
- 我建议避免在Java上下文中完全使用这些术语。只知道它在Java中是如何工作的,这个例子做得很好。
- 或者我们可以说它不是通过引用传递的,而是通过值传递的对象引用。
- 我有点困惑。有人能解释数组是pass by reference的概念吗?gist.github.com/shah-smit/0506d70f7a48b70eeddbe00b59ce6a84
- 在编译时,Java的行为是通过引用传递的。在运行时,它是按值传递的。
- 说Java使用半引用是安全的,因为修改会改变原始对象吗?
- @彼得不,那不正确。它将指针的一个副本传递到一个选择性可变的内存位置。改变存储的值有一个对调用方和被调用方都可见的副作用操作。
- Java是"通过巫毒娃娃",当一个对象在调用一个方法时被传递时,该方法实际上接收了一个巫毒玩偶,你可以使用它来控制实际对象,但是不能完全替换对象。
- 长话短说,问题是使用同一个词"引用一个对象"与"引用一个变量"
- 肖恩,我认为如果你认为Java EDCOX1 0隐含地对应于C++ EDCOX1×1 },你会同意Java是通过值的。如果你考虑到所有的初始化都有EDOCX1×2的值,这是有意义的,就像:EDCOX1,3,一个Java中的变量永远不能保存一个对象。
- 我不得不否决这个答案,因为它是自相矛盾的。答案,甚至是一些支持评论的人,都支持这样的观点:"这完全取决于你如何定义一个引用,所以这只是在争论语义,而不是语言机制",当他们说它不是"通过引用",然后继续说"我们正在传递对它的引用。"回答和评论支持"这不是通过"引用"都用这个词。同样,从答案中直接引用:"我们正在传递对它的引用。"(讽刺的是,重点是回答者的,而不是我的)
- 很好的解释!!!!我要补充的是,在Java RMI调用的情况下,该方法接收参数的完整序列化副本,而不是引用的值。
- "当我们传递一个对象的值时,我们将引用传递给它。"然后,Java是一个通过引用的语言,我想…
- 那为什么还要有这两个该死的条件呢?是的,pass-by-reference将在某种程度上处理值,但您不必将概念抽象到n阶!
- 我会说,因为Java声称是面向对象的,它是人们关心的对象,人们对传递的对象感兴趣。Java中大多数新开发人员必须了解的有趣的事情是,传递对象的唯一方法是传递引用的副本,给出对象的传递引用语义,这对于99%问这个问题的人来说至关重要。如果引用本身是通过引用传递的,那么将非常混乱,几乎不可能使用该语言。
- 所有这些争论实际上是因为太阳报的人犯了一个严重的命名错误。他们使用名称引用而不是指针。其实这不是一个错误。根据一个和原来Sun团队的另一个人一起工作的人,销售部门要求使用参考词而不是指针。销售部门想把Java作为一种安全的语言,而广告的一个优点就是"Java的优点是它不允许像C++那样使用指针算法"。
- 我的家伙克恩文的家伙,像太阳队的其他人一样,不得不制造这个大谎言。他们知道什么是指针。他们更新了什么是参考文献。但这笔钱要求条件改变。正因为如此,今天许多Java程序员不知道什么是参考,因为他们学会了错误的术语。最初,"引用"严格地表示作为参数传递的变量的别名。引用词描述了向函数传递参数的方法。Sun团队重新定义了"引用"一词,因为Sun希望Java语言有更多的成功,口袋里还有更多的钱。
- 我确实认为,为了提高你的答案并使它完整,你应该提到这个"引用"一词,意思是Java使用的改变。不幸的是,很难教Java程序员他们讲错了术语。但这是事实。最后,获奖者写下了历史。Java重写了引用定义,人们相信它们仅仅是因为他们赢得了很多用户。
- 我们可以把引用看作是C++中指针的等价性。
- 基本上Java似乎是通过原语传递值并通过对象传递引用。
- 正如这个答案所指出的,"通过引用"不同于"传递引用"(它仍然可以像Java那样通过以值传递语言)。许多评论者未能领会这一区别。
- "令人困惑的是,对象引用实际上是指针",是的。引用是不能进行指针算术的指针。
- 有人能给我解释一下这一区别吗:如果我有一个方法void changeint(int myint)/…,其中myint被设置为某个新值,它是传递值,因此传递某个整数后,该值与以前的值相同。然后,如果我定义了一个方法(假设我有一个应该表示链接列表的类listnode)void changehead(listnode myhead)myhead.value=155;myhead=myhead.next;,那么在将myhead传递给changehead之后,它的值已经更改为155,但head仍然是初始head而不是head.next。不知怎的,我无法用这两个例子来找到一个一般规律。
- 尽管答案本身是正确的,但我认为Java总是"按价值调用"是不好的,因为"按需调用"和"按值调用"的区别仍然是必要的。也就是说,这个例子将是EJB的远程方法调用与本地EJB的方法调用。为了更好地解释方法调用是如何在Javaee世界中发生的,我认为这两个方法的区别非常有用。甚至甲骨文也使用这种区别。参见:【docs.oracle.com/cd/e24329_/web.1211/e24368/&hellip;(应用程序类加载和传递值或引用)
- Erand的行"如果它被引用通过,那么主体中的ADOG.GETNAME.()在调用FO之后会返回‘FIFI’",将清除Java为原语和对象传递值的所有混乱。很少有人会认为这是幻觉。
- @Satyandrakumar这是一个令人恼火的低估评论。我笑得很开心。
- @Aaron好吧,那么如何定义一个"true"pass-by引用,在这个引用中,可以在函数调用中更改原始变量(而不是值)?Java在传递引用值时是通过值的,所以我无法用简单的传递X语言来描述这一点。
- @答案是Giacomo,函数引用该值,所以您可以更新原始值。但保存此引用的变量与引用相同值的函数之外的变量没有任何连接。因此,当您为函数内部的局部变量分配一个新值时,您只需覆盖传递给的任何引用。
- @首先,我不会创建一个定义。当人们认为他们需要重新定义英语,而英语被用来谈论一个特定的领域时,我很恼火。这些是英语单词,无论你说的是电脑还是其他东西,都有意义。所以我不会定义它。第二,"值为引用的传递值"非常接近于"传递引用",因此可以毫无混淆地说出来。事实上,它是如此接近C++引用,最大的不同是重新分配,我认为人们应该这样看待它。
- @亚伦的问题是有一个混乱。如示例所示,这两个过程显示了不同的行为,它们需要不同的定义。显然,这些"英语单词"不够精确,无法定义这些方案。
- @如果你这么说,也许你遇到了需要,在这种情况下,你可以说具体点。但在与我经验丰富的其他软件工程师交谈时,这从来都不是问题,即使是在讨论JVM语言的编译器编程时,这也是问题所在,如果它是一个问题,那么它最有可能是一个问题。由于在JVM语言的编译器构造的英语语言讨论中(对我来说)这并不是一个问题,所以我前面提到的感觉将会持续下去。
- 亚伦,我的意思是,当然,如果你在Java环境中,那就不重要了。这种混淆随之而来,因为有些其他语言的行为不同。我认为这对像我这样的学生来说是一个重要的/必要的区别。
- 这个问题本身就在Java的上下文中。虽然我的最后一点评论是为了我的观点而扩展了一点:JVM语言不仅仅涉及Java,事实上,我所提到的讨论不是关于Java的,而是关于Java语言的研究,目标是JVM。我最后一句话的意思是,我从来没有发现它是实际使用中的问题。不过,如果你是一个学生,那就一定要从最低的层次去挖掘和理解它。然而,即便如此,我个人也不会严格定义"引用传递"。
我刚注意到你引用了我的文章。
Java规范说Java中的所有东西都是通过值传递的。在爪哇,没有所谓的"通行证"。
理解这一点的关键是
不是狗,而是指向狗的指针。
这意味着,当你
1 2
| Dog myDog = new Dog("Rover");
foo(myDog); |
您实际上是将创建的Dog对象的地址传递给foo方法。
(我说的基本上是因为Java指针不是直接地址,但是用这种方法最容易想到它们)
假设Dog对象位于内存地址42。这意味着我们将42传递给方法。
如果方法定义为
1 2 3 4 5
| public void foo(Dog someDog) {
someDog.setName("Max"); // AAA
someDog = new Dog("Fifi"); // BBB
someDog.setName("Rowlf"); // CCC
} |
让我们看看发生了什么。
- 参数someDog设置为值42。
- 在"AAA"线上
- someDog接Dog指向(地址42的Dog对象)
- 要求Dog(地址42的那个)把他的名字改成Max
- 在线"BBB"
- 创建新的Dog。假设他在74号地址
- 我们将参数someDog指定为74。
- 在线"CCC"
- 一些狗被跟踪到Dog,它指向(地址74处的Dog对象)
- 要求Dog(地址74)改为Rowlf
- 然后,我们回来
现在让我们考虑一下在方法之外会发生什么:
myDog有变化吗?
有钥匙。
记住,myDog是一个指针,而不是实际的Dog,答案是:myDog仍然有值42;它仍然指向原始的Dog(但请注意,由于行"a a a",它的名称现在是"max"—仍然是同一只狗;myDog的值没有改变。)
跟踪地址并更改地址末尾的内容是完全有效的;但是,这不会更改变量。
Java可以像C一样工作,可以分配一个指针,将指针传递给一个方法,跟随方法中的指针,并改变指向的数据。但是,您不能更改指针指向的位置。
在C++、艾达、Pascal和其他支持传递引用的语言中,实际上可以更改传递的变量。
如果Java具有通过引用语义,则上面定义的EDCOX1 OR 1方法将在EDOCX1×13"Ed"被指派给在线BBB的情况下发生改变。
将引用参数视为传入变量的别名。当分配别名时,传入的变量也是如此。
- 这就是为什么"Java没有指针"的常见说法是如此的误导。
- 你错了,imho。"记住,mydog是一个指针,而不是一个实际的狗,答案是"否"。mydog仍然有值42;它仍然指向原始的狗。"mydog有值42,但它的名称参数现在包含"max",而不是//a a a行上的"rover"。
- 这样想。有人知道密歇根州安阿伯的地址(我的家乡,蓝色的!)在一张名为"Annarborlocation"的纸条上。你把它抄在一张叫做"我的目的地"的纸上。你可以开车去"我的目的地"种一棵树。你可能已经在那个地方改变了一些关于城市的东西,但是它并没有改变写在两张纸上的纬度/经度。您可以在"我的目的地"上更改lat/lon,但它不会更改"Annarborlocation"。这有帮助吗?
- @斯科特·斯坦奇菲尔德:我读了你一年前的文章,它真的帮我理清了事情。谢谢!我可以谦虚地建议您补充一点:您应该提到,实际上有一个专门的术语描述了这种形式的"按值调用,其中值是一个引用",这是Barbara Liskov在1974年发明的,用于描述其CLU语言的评估策略,以避免混淆,例如您的文章中提到的:call通过共享(有时称为对象共享调用或简单的对象调用),这几乎完美地描述了语义。
- 我认为我们不需要另一个术语。""传递值"适当地涵盖了这一点。利斯科夫·洛克(我想知道是否有人对她说过这些话?),但"按(对象)共享调用"没有添加任何新内容。我们可以添加一个相应的"按原语调用",它也不添加任何新内容。我喜欢在这里运用亲吻规则…
- 好文章。我不喜欢谈论与Java一起指针…当知道Java及其变量范围/生命周期如何工作时,引用是使用的术语。有了指针,人们开始怀疑指针或给定引用是指针,关于C++引用的引用。那不好。变量由位(一个值)组成,这个值可以引用一个对象。也就是说,它告诉JVM如何在堆中查找对象。这个值被复制/复制并传递给一个方法变量,然后该变量可以找到相同的对象。不需要指针。
- 所以,如果methodfoo()返回somedog,并且我使用dog rdog=foo(mydog),那么rdog变量将被设置为地址74,对吗?
- @ GEVORG——C/C++语言不具有"指针"的概念。还有其他语言使用指针,但不允许对C/C++允许的指针进行相同类型的操作。Java有指针,它们被保护免遭恶作剧。
- 我完全同意你的观点,但是在第183页的《Sun认证助理学习指南-2009版》一书中,第5章"自我测试答案"的第10个问题说:"对象总是通过引用传递给方法。这意味着对方法中对象的更改将反映在调用该方法的代码中的对象中。"我同意,但是我应该为Java测试选择什么答案?
- 说Java是价值的传递既没有意义又令人困惑。计算机上的所有东西都是传递值。在Java中,当改变传入数组时,它会改变原始数组,因此数组不会按值传递,它是通过引用传递的。指针是按值传递的。它取决于抽象的深度和您使用的定义。在C++中,当你改变一个指针时,你仍然通过一个值,它的内存位置来访问那个指针,但是你不会把它称为传递值。
- 这对我们中的一些人来说是非常困惑的,他们真的很想知道,当它清楚地包含似乎是通过引用的结果时,如"Java通过值"这样的语句会发生什么。在新来者身上使用如此低的抽象级别是令人困惑的,当你走得太远时,最终是毫无意义的:正如我之前所说,在最低级别的计算机上,一切都是通过价值传递的。现在,请指出我哪里错了,因为在我困惑的时候,我很可能是错的。
- @Matthew-"传递值"在编程语言设计中有一个非常具体的定义。并非所有的参数都是按值传递的;一些语言(如C++和艾达)实际上通过引用传递参数(如果在正式参数定义中使用[AMP[[c+] ]或"in -in"[艾达]修饰符]。
- Matthew - Java实际上并没有包含看起来是通过引用的结果。不能从方法内部更改用作参数的变量的值。(当然,您可以跟踪一个指针并更改它指向的对象的内容,但这不是引用语义;您只是跟踪一个值)。
- 马修——对新来的人说任何其他的事情都是不正确的,因为Java不太熟悉面向对象编程,因为它是一种程序语言。如果有人没有正确理解值与引用语义,那么给予他们正确的理解是很重要的,特别是如果他们后来转向一种具有引用语义的语言。(想象一下,如果您不理解它并分配了一个参数,比如dog=new dog()。在Java中,它不改变外部变量,但其他语言可能。不知道差异会导致混淆)
- @Scott,跟随指针并更改内容意味着与在更高级别上通过引用传递相同的事情。数组可以被视为其内容。这是语言和定义的问题。我从根本上理解它是如何工作的。Java的问题在于,它隐藏了基本原理:JoelOnFielas.com/ToeSt/ TeaSeriFasJavaCooL.HTML。我百分之百同意你希望每个人都了解实际发生的事情,也许你的"摇船"会引起人们的注意。我只是注意到,它在抽象层次上很重要。
- 史葛,我想这可以更好地解释我的观点:如果你看到有人接受Java作为引用,你可以显示一段代码来打破这个理论。但是,如果你看到有人接受Java作为值传递,我可以显示代码打破了这一理论,基于他们对"通过值"的更高层次的理解。这里的术语是错误的。我认为这个问题的最佳答案是描述每一个"传递"方法,以及隔离它们的适当命名约定。
- ScottStanchfield,我提到C++,因为Java是它的直接进化。关于指针,除了NullPointerException之外,没有其他东西可以从下面的白皮书Java.Sun.com/DOCS/White /LangEnv/Simult.Do2.html 4107中看到!而且,即使我不是专家,我相信大多数JVM使用EDCOX1对对象和0对象可以在堆中移动,而参考值没有改变(这不应该在C/C++中发生)!不过,我不想谈论JVMs的实现,我不能相信我刚才提到的Java和"PS"在同一评论;
- 好的观点,但你的结论基本上还是错误的。Java确实是通过引用传递的。仅仅因为它有一个简化的指令集,不允许您更改指针指向的位置,就不会使它通过值。您仍在维护对原始对象的引用,您没有该对象的副本,因此不会更改原始对象。唯一一次这不是真的是与不变的对象和原语。这就像是政治正确或利用宣传…就因为你说大便是牛排并不意味着它的味道还是…
- @比尔-一点也不像宣传或政治上的正确…计算机语言设计对于传递值和传递参考具有非常精确的意义。如果您不能更改传递的实际内容(在这些示例中是指向狗的指针),则它是传递值。根据您的逻辑,C具有旁路引用语义(显然没有)。
- @斯科特·斯坦奇菲尔德解释得很好。我只想补充一点,当使用RMI时,引用是单独管理的,所以即使我们在一侧A(somedog.setname("newname")中更新一个实例,然后立即返回,它也不会在另一侧B中更新。如果我有时间,我稍后将详细解释它。当做。
- @rodmarconde-yep-查看我的文章javaude.com/articles/passbyvalue.htm中的"远程方法调用(rmi)的注释"部分-启动这个问题的部分。RMI调用有两种模式:值和引用。
- @指针没有改变,它的值仍然是42。如果它是通过引用传递的,那么它将更改为新的dog fifi的内存位置(它将被重命名为rowlf,但指针不会再次更改)。最后,如果它是通过引用传递的,在方法返回void之后,将调用rowlf。
- 当你说,"…它仍然指向原始的狗。"是因为foo()中发生的任何事情都是对指向mydog的指针的值执行的,一旦该方法完成,那么它就超出了范围吗?根据我的理解,如果我们传递一个对"Rover"的引用,我不理解为什么foo()内的操作不会影响"Rover"。
- @jpl-在foo中,行//aaa跟踪指向"rover"狗的指针并更改其名称。line//bbb将变量指向新的狗,因此对该变量所做的任何其他操作都不会影响以前称为rover的狗。在这种方法之后,我的狗仍然指向以前称为漫游者的狗。
- @斯科特桑菲尔德,我一直很喜欢这个答案。不过,我认为有一件事确实有帮助,那就是如果你有一个附录,它处理了第二个方法,这个方法接受并重新分配了一个标量。这是我在处理JSSL(JS作为第二语言)开发人员时教过很多的主题之一。修改一个对象/列表的内容(当然,它的副作用超出了范围),而在外部范围重新分配EDOCX1的值(0),这是我发现人们去"啊哈"的地方。"。
- 当然,关于为什么这是传递值的证明(当复杂对象的值显然是一个引用,尽管标量的值是标量值的副本)的更深层次的答案会变得不那么简单,但希望以后更容易谈。
- 六羟甲基三聚氰胺六甲醚。。。我想我认为原语发生的事情是显而易见的…我没想过。实际上,我可以从一个传入int的示例开始本文,然后转到dog示例,以表明它在做相同的事情。我真的需要用一些显示堆和堆栈的图片来更新它…谢谢你的建议!
- 看起来Java通过引用它们的值来传递对象。
- 本文中的一点是,从程序员的角度来看,gosling和joy称之为"引用"的是指针。(如果它看起来像一个指针,就像一个指针,对于程序员的所有意图和目的来说,它就是一个指针。)他们使用"引用"一词已经引起了对Java通过值传递的所有混淆。
- 所以你是说在foo()的// BBB行,myDog现在(仍然在地址42)指向地址74的狗?原来的狗(现在命名为max)仍然位于地址42,但当读到42时,它指向地址74。所以如果你不能找到马克斯(假设他是一个混淆事物的寻回者),他是如何仍然存在的。你还能取回寻回犬吗?(严重问题)
- 记住,myDog是一个指针;它不位于42(它位于内存中的其他位置),它的值为42。调用foo()时,该42作为参数someDog传入。在// AAA上,我们查看someDog的值(42),然后在42号转到Dog并更改其名称。在// BBB上,我们将someDog的值改为74。myDog的值(在方法之外定义,仍在范围内)仍然为42。myDog在调用范围内仍处于活动状态;您可以访问它指向的狗(42,名称为fifi)在foo()外。你再也不能进入那只狗了。
- 所以这和C中的情况完全一样,但是因为指针很硬,所以它们删除了指针操作符,以便"更容易"!
- @分隔为零并不是为了使其更容易,而是使其更安全(例如,不可能从数组的末尾离开)。即使是优秀的C/C++程序员也会出现这种情况。
- @Ö;ZG&252;R键是BBB线,而不是AAA。如果这是通过引用,那么BBB应该将我的狗改为"FIFI",然后改为"ROWLF"。这并不是因为这是"传递参考值"。
Java总是不passes××arguments参考值。 >
让我解释这一年的实例: >
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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");
}
} |
我会解释这步骤: >
declaring命名的Foo参考f型和新的IT assign面向属性"f"Foo带型。 >
>
从法学院Foo端,参考型与adeclared name is initially null和它的分配。 >
1
| public static void changeReference(Foo a) |
>
你changeReference呼叫的方法,将指定的对象a参考,是我过去一年的争论。 >
>
declaring命名的Foo参考b型和新的IT assign面向属性"b"Foo带型。 >
>
a = b让新来的a参考配置,不f,其属性是其《面向"b"。 >
>
你叫我modifyReference(Foo c)法,是人生c参考和面向属性的"f"与分配。 >
>
c.setAttribute("c");会改变的,c面向属性的参考点的吧,和它一样,f面向参考点的吧。 >
>
我希望你明白我现在穿arguments厂如何在Java对象:) >
- + 1好东西。好的图表。我也在这里找到了一个很好的简洁的页面adp-gmbH.ch/php/pass_by ou reference.html好吧,我承认它是用php写的,但这是理解我认为重要的区别的原则(以及如何根据您的需要来处理这种区别)。
- @eng.fouad这是一个很好的解释,但是如果a指向与f相同的对象(并且从未得到它自己的对象f指向的副本),那么使用a对对象所做的任何更改都应该修改f(因为它们都使用相同的对象),因此在某些情况下a必须得到它的对象的副本f指向。
- @mrd当"a"指向同一对象"f"时,通过"a"对该对象所做的任何更改也可以通过"f"观察到,但它没有更改"f"。f'仍然指向同一对象。你完全可以改变物体,但你永远不能改变"f"的指向。这是一些人出于某种原因无法理解的根本问题。
- @Mikebraun…什么?现在你把我搞糊涂了:S.不是你刚才写的和6.7。显示?
- @清洗机不,为什么?你从未见过modifyreference让"f"指向完全不同的实例,是吗?在6和7中只有一个物体。可以创建对tgat对象的本地新引用(示例中为"c"),并且可以更改一个对象的内容(如7中所示),但方法中没有任何内容能够将"f"重新分配给第二个对象。
- @哦,我明白了,你说"但它没有改变‘f’"时我误解了。我还以为你的意思是"f"指向的物体不会改变。
- 如果我说Java中的对象通过引用的副本传递给一个方法,那么这可能会解决整个讨论。上面漂亮的图表可以适合并支持这一说法。
- 需要通过Java反射访问SttEngy方法。教程,Jekov.com/Java反射/ Helip;
- 根据这个定义,所有语言都是传递值,传递引用不存在。传递引用意味着您可以编辑调用者接收到的内容的实例。这是否是通过指针或使用C++的方便语法来完成的,这是无关紧要的。
- 这是我找到的最好的解释。顺便问一下,基本情况如何?例如,参数需要int类型,它是否仍将int变量的副本传递给参数?
- 我在第5行有点困惑。我认为它是从右到左的,这意味着物体A被指定为物体B,而不是相反的方向。或者简单地说,不管A的值是什么,现在不管B的值是什么,我是对的吗?
- @你说得对,变量a被赋予当前值b,换句话说,b的值被赋予变量a。我现在已经改变了语言,以正确地反映所发生的事情。
- 解释得很好。现在我可以说是的。参数名用于指向不同的位置。那么它的PaaS值多少??
这将给你一些关于Java如何真正工作的见解,在你的下一个关于Java通过引用传递或通过值传递的讨论中,你只会微笑:
第一步请从你的脑海中抹去以"p"开头的单词,特别是如果你来自其他编程语言。Java和"P"不能写在同一本书、论坛甚至TXT中。
第二步记住,当你将一个对象传递到一个方法中时,你传递的是对象引用而不是对象本身。
- 学生:大师,这是否意味着Java通过了引用?
- 主人:蚱蜢,不。
现在想想对象的引用/变量是什么:
变量保存告诉JVM如何到达内存中被引用对象(堆)的位。
当向方法传递参数时,不传递引用变量,而是传递引用变量中位的副本。类似这样:3BAD086A。3BAD086A表示一种到达传递对象的方法。
所以你只要通过3BAD086A,它就是参考值。
您传递的是引用的值,而不是引用本身(而不是对象)。
这个值实际上被复制并提供给方法。
在下面(请不要尝试编译/执行此…):
1 2 3 4 5 6 7 8
| 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行中创建的,其开头为空。
- 在第2行中创建一个新的Person对象,存储在内存中,变量Person被赋予对Person对象的引用。也就是它的地址。比如说3BAD086A。
- 持有对象地址的变量人物被传递给第3行中的函数。
- 在第4行,你可以听到寂静的声音。
- 查看第5行的评论
- 创建了一个方法局部变量(另一个与esamepersonObject相关的引用),然后在第6行中出现了魔力:
- 变量/引用人员被逐位复制,并传递给函数内的另一个指向esamepersonObject的引用。
- 没有创建新的人员实例。
- "person"和"anotherreferencetothesamepersonobject"都具有相同的值3bad086a。
- 不要这样做,但是person==anotherreferencetothesamepersonobject将是真的。
- 这两个变量具有相同的引用副本,它们都引用同一个人对象、堆中的同一对象,而不是副本。
一幅画胜过千言万语:
请注意,esamepersonObject箭头的另一个引用指向的是对象,而不是变量person!
如果你没有得到它,那么请相信我,记住最好是说Java是按值传递的。好吧,通过参考值。哦,更好的是传递变量值的副本!;)
现在,可以痛恨我了,但请注意,考虑到这一点,在讨论方法参数时传递原始数据类型和对象没有区别。
您总是传递引用值位的副本!
- 如果是基元数据类型,这些位将包含基元数据类型本身的值。
- 如果它是一个对象,那么位将包含地址值,该地址值告诉JVM如何到达该对象。
Java is pass-by-value because inside a method you can modify the referenced Object as much as you want but no matter how hard you try you'll never be able to modify the passed variable that will keep referencing (not p _ _ _ _ _ _ _) the same Object no matter what!
The changeName function above will never be able to modify the actual content (the bit values) of the passed reference. In other word changeName cannot make Person person refer to another Object.
当然,你可以缩短它,只说Java是通过值传递的!
- 你是指指针吗?…如果我得到正确的话,在public void foo(Car car){ ... }中,car是foo本地的,它包含对象的堆位置?所以,如果我用car = new Car()更改car的值,它将指向堆中的不同对象?如果我用car.Color ="Red"改变car的属性valu,则会修改car指向的堆中的对象。同样,在C中也是一样的。请回复!谢谢!
- @多曼诺克斯,你要杀了我,请不要再说这个词!注意,我可以回答这个问题而不说"参考"。这是一个术语问题,"P"使情况更糟。不幸的是,我和斯科特对此有不同的看法。我想你已经知道它是如何在Java中工作的,现在你可以把它称为通过值,通过对象共享,通过变量值的拷贝,或者自由地想出其他的东西!只要你知道它是如何工作的,以及在一个对象类型的变量中有什么:只有邮政信箱地址,我就不在乎了!;)
- 好的,我知道了,通过值传递,传递时共享值!>:爪哇不再有P和R了!我现在的问题是,如何更改方法的外部变量的值?再次感谢。
- 当两个对象引用同一个对象时,这是否意味着它将占用更多的实际值内存?或者它只会占用内存来创建指向内存中相同位置的指针?它是如何工作的?
- @你用了"P"这个词。但我还是会回答你的问题…当两个变量引用同一个对象时,对象本身不会再次添加到内存中,只需为变量添加一小部分。如果你把形状当作记忆的使用,格沃格的图像会最好地显示出来。大矩形是实际存储在堆中的对象,小圆圈是对它的多个引用,它们实际上只是一个物质位。
- 一个原因可能是Java不允许您更改该地址,因此它给了您一个引用的副本(安全)。
- 很好+1因为在第4行你可以听沉默的声音,我会补充,或者去喝杯咖啡,但谨慎地说是很快的咖啡!对于那些真正想要更改对象成员值的人,需要使用从对象调用的setter和getter。如果希望在创建该对象类型的所有对象中使用相同的默认值,则需要一个静态成员。如果需要修改,则需要一个静态方法来修改。这就是为什么那些高于"modify"的响应对象的成员必须在主类内部使用静态方法的原因。
- 然后我想可以说Java使用"通过引用复制"语义。您只是传递一个容器,该容器的内容是堆上对象的内存地址。因此,如果我调用doSomething(someObject);,在所说的方法中,我说someObject = new Object();,我只是在修改someObject容器中的内存地址;我根本不更改调用方容器的内存地址。它不是严格地传递值,也不是严格地传递引用,而是共享两者的某些属性的东西。
- 还有一件事:Java中的语义在某种程度上类似于复制写(牛)语义,因为你把地址的副本传递给一个方法。此时调用方法时,调用方的变量和方法局部变量指向同一对象,因此被认为是"共享"同一指针值。但是,当您将不同的对象分配给本地传入变量时,变量的内存引用将更改,但调用方变量的引用不会更改。那是母牛。
- 听起来你刚刚通过了推荐信?我将支持这样一个事实,Java仍然是一个复制的通过引用语言。事实上,它是一个复制的参考并不改变术语。两个引用仍然指向同一个对象。这是一个纯粹主义者的论点…
- 我猜想Java是用指针设计的。否则,为什么存在NullPointerException?然而,对于这个美妙的解释来说,获得指针只会使事情变得复杂。
- 那么,在理论上的第9行System.out.println(person.getName());中,会出现什么?"汤姆还是杰瑞?这是我克服这种困惑的最后一件事。
- 我想你从没听说过江户记1〔3〕。Java确实有指针。但你可能不会相信我的话,所以试试这个:"4.3.1对象一个对象是一个类实例或数组。参考值(通常只是引用)是指向这些对象的指针,以及一个特殊的空引用,它引用了"没有对象"。"从Java语言规范8,第4.3.1节。
- 所有这些难题都来自于我们自然语言的简单缺点。Java传递值,但这些值恰好是引用,因为有些人也将变量名称为引用,所以可以猜测Java传递的实际上是那些名称。因此,如果你倾向于看到它,请记住,Java中的一些名称指向一个对象的引用,因此它是对引用的引用(变量名(AK'引用))=引用(AK'真引用)=堆上的对象)。你通过了第二个,真正的参考,而不是第一个。
- 我读过很多关于这个混乱的太阳命名法的文章,但这是唯一一个可读、有趣和清晰易懂的。谢谢您!
Java总是通过值,从来没有例外。
那么,为什么任何人都会对此感到困惑,相信Java是通过引用,还是认为他们有Java作为引用传递的例子呢?关键是Java在任何情况下都不能直接访问对象本身的值。对对象的唯一访问是通过对该对象的引用。因为Java对象总是通过引用访问,而不是直接访问,通常将字段和变量和方法参数作为对象进行讨论,而迂腐地说它们只是对对象的引用。混淆源于术语的这种(严格来说,是不正确的)变化。
因此,在调用方法时
- 对于原语参数(int和long等),传递值是原语的实际值(例如3)。
- 对于对象,传递值是对象引用的值。
所以如果你有doSomething(foo)和public void doSomething(Foo foo) { .. },两个foo复制了指向相同对象的引用。
当然,通过值传递对对象的引用看起来非常像(并且在实践中与)通过一个对象一个引用传递。
- 由于原语的值是不可变的(如字符串),所以这两种情况之间的差异并不真正相关。
- 确切地。通过可观察到的JVM行为,可以看出,基元可以通过引用传递,并且可以在堆中生存。他们没有,但这实际上是不可见的。
- 原语是不变的?在Java 7中是新的吗?
- 本文总结得很好:academic.regis.edu/dbahr/generalpages/introtoprogramming/&hellip;
- @卡洛舍伯格:"原始人是不变的吗?"。重点是,您可以假装"原语"实际上是对不可变对象的(可变)引用。
- 指针是不可变的,基元通常是可变的。字符串也不是基元,它是一个对象。此外,字符串的底层结构是可变数组。唯一不变的是长度,即数组的固有性质。
- 这是另一个指出论点的语义本质的答案。在这个答案中提供的引用的定义将使Java"通过引用"。作者在最后一段中同样承认,它从"通过引用"中"在实践中是不可区分的"。我怀疑OP之所以要求是因为理解Java实现,而在于理解如何正确地执行。使用Java。如果它在实践中是无法区分的,那么就没有必要去关心它,甚至思考它将是浪费时间。
- 这个答案是如何正确的?正如"值类型"一样,"传递值"的概念意味着您正在处理直接存在于变量中的数据,从而在分配时被复制。仅仅因为Java不提供对原始指针的访问并不重要:对象的行为与原始数据类型基本不同。它们都不是传递值
- 如果基元是通过引用传递的,则修改其参数的方法调用将影响调用方。事实并非如此。
- 我认为正确的方式是说Java是经过引用的,如果它是对象,因为你正在传递对象的引用值。如果是基元类型,则是传递值。爱因斯坦就是这么说的…使事情简单但不简单:d!和平的每一个人!
Java通过值传递引用。
所以不能更改传入的引用。
- 但是不断重复的"不能改变参数中传递的对象的值"显然是错误的。您可能无法使它们引用不同的对象,但可以通过调用它们的方法来更改它们的内容。在我看来,这意味着你失去了推荐人的所有利益,没有获得额外的保证。
- 我从来没有说过"不能更改参数中传递的对象的值"。我会说"你不能改变作为一个方法参数传递的对象引用的值",这是一个关于Java语言的真实声明。显然,您可以更改对象的状态(只要它不是不变的)。
- 我并不反对你。抱歉,如果不清楚的话!
- 请记住,实际上无法在Java中传递对象;对象仍停留在堆上。可以传递指向对象的指针(这些指针被复制到被调用方法的堆栈帧上)。所以您永远不会更改传入的值(指针),但是您可以自由地跟随它并更改它指向的堆上的内容。这是传递值。
- Java通过值传递引用。您可以更改参考点所在内存的数据。在该方法中,您可以更改引用变量,但在函数退出后,引用将恢复(但不是该引用指向的内存)。
- 听起来你刚刚通过了推荐信?我将支持这样一个事实,Java仍然是一个复制的通过引用语言。事实上,它是一个复制的参考并不改变术语。两个引用仍然指向同一个对象。这是一个纯粹主义者的论点…
- Java不传递对象,它将指针的值传递给对象。这将在新变量中创建指向原始对象的内存位置的新指针。如果在方法中更改此指针变量的值(它指向的内存地址),则方法调用程序中使用的原始指针将保持不变。如果要将参数调用为引用,则事实上它是原始引用的副本,而不是原始引用本身,因此现在有两个对对象的引用,这意味着它是传递值
- @提议32这简直太荒谬了。显然,在可能的最低水平上,会有一些值,因为没有值就没有任何值。这是非常愚蠢的,因为你提出的事实不是Java是否纯粹是通过价值,而是为什么在传递价值和通过引用之间的意义是没有意义的。同样,如果没有数据,就没有数据,因此很明显,每个逐引用实现都将在某个级别处理数据。但是将概念抽象到这样的程度是荒谬的。为什么不调用Java"实体转移"
- 但是有一些区别,因为一些语言确实通过引用,这是一个与基本指针不同的数据类型,这基本上就是Java所使用的。考虑一个引用只是一个指针之上的抽象级别。如果您有一个引用,您可以修改函数调用者使用的实际内存位置的值。Java不这样工作,但是它传递一个包含对象指针值的新内存位置,而不是指针的引用。在爪哇中,不能通过调用某些语言来修改从调用方传递到函数中的值。
- 区别在于引用和引用之间。如果您只有一个引用,那么您可以随意修改它所引用的内容,而不是引用本身。为此,你需要参考文献。这正是C++中通过引用传递参数时所得到的。您不复制参数值,而是引用它。如果参数本身是一个引用,您将得到一个引用的引用。
我觉得争论"通过参考值和通过值"并不是很有帮助。
如果你说,"Java是通过任何(引用/价值)",在这两种情况下,你不能提供一个完整的答案。这里有一些额外的信息,希望有助于理解记忆中发生的事情。
在到达Java实现之前,堆栈/堆的崩溃过程:价值观以一种很好的有序的方式在一堆堆盘子里进进出出,就像自助餐厅里的一堆盘子。堆中的内存(也称为动态内存)是无序的。JVM只会在任何地方找到空间,并释放空间,因为不再需要使用它的变量。
可以。首先,本地原语进入堆栈。所以这个代码:
1 2 3
| int x = 3;
float y = 101.1f;
boolean amIAwesome = true; |
结果如下:
当您声明和实例化一个对象时。实际对象位于堆中。堆栈上发生了什么?堆上对象的地址。C++程序员将它称为指针,但一些Java开发人员反对"指针"这个词。无论什么。只需知道对象的地址在堆栈上。
像这样:
1 2
| int problems = 99;
String name ="Jay-Z"; |
数组是一个对象,所以它也会出现在堆中。数组中的对象呢?它们得到自己的堆空间,每个对象的地址都在数组中。
那么,当你调用一个方法时,会传入什么呢?如果传入一个对象,那么实际上传入的是该对象的地址。有些人可能会说地址的"值",有些人说它只是对对象的引用。这就是"参考"和"价值"支持者之间神圣战争的起源。你所说的并不重要,因为你知道传递的是对象的地址。
1 2 3 4 5 6 7 8 9
| 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 );
} |
创建一个字符串并在堆中为其分配空间,字符串的地址存储在堆栈中,并给定标识符hisName,因为第二个字符串的地址与第一个字符串相同,所以不会创建新字符串,也不会分配新的堆空间,而是在堆栈上创建新的标识符。然后我们调用shout():创建一个新的堆栈帧,创建一个新的标识符,name,并分配已经存在的字符串的地址。
那么,价值,参考?你说"土豆"。
- 但是,您应该跟进一个更复杂的例子,在这个例子中,一个函数似乎改变了一个变量,使其地址具有引用。
- 人们不是在"围绕堆栈与堆的真正问题跳舞",因为这不是真正的问题。它充其量只是一个实现细节,最坏的情况是完全错误的。(对象很可能生活在堆栈上;谷歌的"转义分析"。大量的对象包含可能不在堆栈上的基元。)真正的问题是引用类型和值类型之间的区别——特别是引用类型变量的值是引用,而不是它所引用的对象。
- 曹说的不对。Java不将任何活动对象放置在堆栈上。它可能是一个"实现细节",因为它在Java规范中没有特别的概要,但事实是所有Java实现都将堆中的所有对象放置在堆中。
- 实际上,我撒谎了:它在规范中,堆栈用于局部和部分结果链接,堆用于对象链接
- 这是一个"实现细节",因为Java从来都不需要实际显示一个对象在内存中的位置,事实上似乎是为了避免泄露信息。它可以把对象放在堆栈上,你永远不会知道。如果你在意的话,你会把注意力放在错误的事情上——在这种情况下,这意味着忽略了真正的问题。
- 不管怎样,"原语进入堆栈"都是不正确的。原始局部变量在堆栈上。(当然,如果它们还没有被优化掉的话。)但是,局部引用变量也是如此。而在对象内定义的原始成员则生活在对象所居住的任何地方。
- 同意这里的评论。堆栈/堆是一个次要问题,不相关。有些变量可能在堆栈上,有些在静态内存中(静态变量),还有很多存在于堆上(所有对象成员变量)。这些变量中没有一个可以通过引用传递:从被调用的方法中,永远不可能更改作为参数传递的变量的值。因此,Java中没有传递引用。
- 它与堆栈与堆分配无关。这与间接性有关。堆栈位置有地址。无论如何,Java都通过值传递所有参数。
- 这个答案是错误的。在堆栈或堆上都可以找到引用和基元。这与整个"传递参考"和"传递值"问题完全无关。您的引用和原语通过值传递,不管它们是在堆中还是在堆栈中。
- 对,所以我有3个关于你的评论的问题:1)在堆栈上找不到对象。在堆栈上可以找到对对象的引用,这正是我在图表中显示的。但对象本身存在于堆中。2)所以,是的,可以在堆中找到原语,但前提是它们是对象的属性。同样,在我的图表中显示。本地原语在堆栈上。3)说"参考文献…"按值传递"是按引用传递的定义。通过它的值传递一个引用"就是简单地通过引用传递它所引用的东西。
- 而且,我并不是说Java是通过引用的。我从一开始就说过,"传递引用与传递值"是一个愚蠢的参数,因为它不能产生任何有用的理解,所以这里有一个模型,用来帮助您理解当传递到方法中时,局部原语的行为方式和对象的行为方式。简单地说"Java是通过引用"是不完整的,因为它经常需要额外的解释。我试图提供一些额外的信息,而不仅仅是一个短音字节。
- @我很理解你的回答。但我很困惑,如果这个答案是真的,那么为什么简单的交换失败了呢?我说的是一个交换方法,它接受两个参数,然后在方法内部创建一个临时对象,然后进行交换。如果可能的话,你能在回答中解释一下那个场景吗?拜托
- 如果不在这个答案中,那么在某个地方。请向我解释为什么交换方法失败!
- 当然,你能更具体点吗?可以发布你在ideone.com中引用的代码,甚至只是pastebin.com吗?"交换方法"这个词有足够的空间,我不想给你不完整的信息。
- @如果这是一个愚蠢的论点,我们如何描述这种区别呢?它以其他语言存在。对于Java来说,其他语言共享一个基本上通用的调用语法是很有意义的。考虑c的ref。
为了显示对比度,比较下面的C++和Java片段:
在C++中:注释:坏代码-内存泄漏!但它证明了这一点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 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中,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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,我还将添加EDCOX1,15,to C++示例,这样我们就可以修改指针"指向"。
- 这是我迄今为止看到的最好的答案之一。它主要避免了其他地方出现的"引用对引用值"的无关语义论证,而是讨论了实际发生的机制。我不得不给出大多数其他"传递价值"的答案-1,因为它们的内容自相矛盾或事实上是错误的,但是这个答案得到+1。
- 但是对于Java中的给定问题,这并没有答案。事实上,Java方法中的所有东西都是通过值,更不用说了。
- 这个示例只是证明了Java在C中使用了非常等价的指针?C中的pass-by值基本上是只读的?最后,这整条线是无法理解的。
Java通过值传递对对象的引用。
基本上,重新分配对象参数不会影响参数,例如,
1 2 3 4 5 6 7 8 9
| 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别名)的副本,它最初指向同一个对象。
- 字符串类和所有其他类之间没有什么区别吗?
我不敢相信还没有人提到芭芭拉·利斯科夫。1974年,当她设计CLU时,遇到了同样的术语问题,她发明了共享调用(也称为对象共享调用和对象调用)这一术语,用于"值为引用的值调用"这一特定情况。
- 我喜欢这个名称上的区别。不幸的是,Java通过共享对象来支持调用,但不支持按值调用(如C++)。Java只支持原始数据类型的值调用,而不是复合数据类型。
- 我真的不认为我们需要一个额外的术语——它只是一个特定类型的值的传递值。添加"按原语调用"会添加任何说明吗?
- 共享调用
- 所以我可以通过共享一些全局上下文对象来传递引用,或者甚至传递包含其他引用的上下文对象?我仍然传递值,但至少我可以访问我可以修改的引用,并使它们指向其他内容。
问题的关键在于,"通过引用"表达式中的单词引用是指与Java中的单词引用的通常含义完全不同的东西。
通常在Java引用中指的是对对象的引用。但是编程语言理论中的"引用/值传递"技术术语指的是保存变量的存储单元,这是完全不同的。
- 通俗地称为指针。
- @格沃格-那什么是"空指针异常"?
- @热:一个不幸的命名异常,在Java之前有明确的术语。C中语义等效的异常称为nullReferenceException。
- 在我看来,在Java术语中使用"引用"是一种妨碍理解的矫揉造作。
- 我学会了将这些所谓的"指向对象的指针"称为"对象的句柄"。这减少了模糊性。
在Java中,所有的东西都是引用的,所以当你有这样的东西:EDCOX1〔0〕Java做如下操作:
创建新的点对象
创建新的点引用,并将该引用初始化为先前创建的点对象上的点(引用)。
从这里,通过点对象生命,您将通过PNT1访问该对象。参考文献。因此,我们可以说,在Java中,通过引用来操作对象。
Java不通过引用传递方法参数,而是按值传递它们。我将使用此站点中的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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);
} |
程序流程:
创建两个不同的点对象,并关联两个不同的引用。
1 2 3
| System. out. println("X1:" + pnt1. x +" Y1:" +pnt1. y);
System. out. println("X2:" + pnt2. x +" Y2:" +pnt2. y);
System. out. println(""); |
如预期,输出将是:
1 2
| X1: 0 Y1: 0
X2: 0 Y2: 0 |
在这一行,"通过值"进入游戏…
1
| tricky (pnt1,pnt2 ); public void tricky (Point arg1, Point arg2 ); |
引用pnt1和pnt2是按价值传递给复杂方法的,这意味着现在你的引用pnt1和pnt2的copies命名为arg1和arg2,所以pnt1和arg1指向同一个对象。(与pnt2和arg2相同)
在tricky方法中:
1 2
| arg1.x = 100;
arg1.y = 100; |
下一个是tricky方法
1 2 3
| Point temp = arg1 ;
arg1 = arg2 ;
arg2 = temp ; |
在这里,您首先创建新的temp点引用,它将指向与arg1引用相同的位置。然后,将引用arg1移动到与arg2引用相同的位置。最后,arg2将指向与temp相同的地方。
从这里开始,tricky方法的范围已经不复存在,您不再可以访问参考:arg1、arg2、temp。但重要的是,当这些引用"在生活中"时,你对它们所做的一切都将永久地影响它们指向的对象。
因此,在执行方法tricky后,当您返回到main时,会出现以下情况:
所以现在,程序的完全执行将是:
1 2 3 4
| X1: 0 Y1: 0
X2: 0 Y2: 0
X1: 100 Y1: 100
X2: 0 Y2: 0 |
- 在爪哇,当你说"在Java中,一切都是引用"时,你的意思是所有的对象都是通过引用传递的。基元数据类型不是通过引用传递的。
- 我可以在主方法中打印交换值。在trickey方法中,添加如下语句arg1.x = 1; arg1.y = 1; arg2.x = 2; arg2.y = 2;so,作为arg1 now holding of pnt2 reference和arg2 holding now pnt1 reference,因此,其打印X1: 2 Y1: 2 X2: 1 Y2: 1。
Java总是通过值传递,而不是通过引用传递。
首先,我们需要了解传递值和传递引用是什么。
传递值意味着您正在内存中复制传入的实际参数值。这是实际参数内容的副本。
传递引用(也称为传递地址)表示存储实际参数的地址副本。
有时,Java可以给出通过引用的错觉。下面的例子让我们看看它是如何工作的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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 ;
} |
这个程序的输出是:
让我们逐步了解:
众所周知,它将在堆中创建一个对象,并将引用值返回到t。例如,假设t的值是0x100234(我们不知道实际的jvm内部值,这只是一个例子)。
1
| new PassByValue().changeValue(t); |
当将引用t传递给函数时,它不会直接传递对象测试的实际引用值,而是创建一个t的副本,然后传递给函数。因为它是按值传递的,所以它传递的是变量的副本,而不是变量的实际引用。因为我们说t的值是0x100234,所以t和f都有相同的值,因此它们指向相同的对象。
如果使用引用f更改函数中的任何内容,它将修改对象的现有内容。这就是为什么我们得到输出changevalue,它在函数中更新。
要更清楚地理解这一点,请考虑以下示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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,如下所示:
希望这会有所帮助。
引用在表示时总是一个值,不管您使用什么语言。获取一个框外视图,让我们看看汇编或一些低级内存管理。在CPU级别,如果任何对象被写入内存或某个CPU寄存器,则对该对象的引用将立即成为一个值。(这就是为什么指针是一个好的定义。它是一个值,同时具有目的)。好的。
内存中的数据有一个位置,在该位置有一个值(字节、字、任何东西)。在汇编中,我们有一个方便的解决方案来给某个位置命名(也称为变量),但是在编译代码时,汇编程序只是用指定的位置替换名称,就像浏览器用IP地址替换域名一样。好的。
从根本上讲,技术上不可能在任何语言中传递对任何事物的引用而不表示它(当它立即成为一个值时)。好的。
假设我们有一个变量foo,它的位置在内存中的第47个字节,它的值是5。我们还有另一个变量ref2foo,它在内存中的第223字节,它的值将是47。这个ref2foo可能是一个技术变量,而不是由程序显式创建的。如果你只看5和47而没有任何其他信息,你只会看到两个值。如果您使用它们作为参考,那么我们必须旅行:好的。
1 2 3 4
| (Name)[Location ] -> [Value at the Location ]
---------------------
(Ref2Foo )[223] -> 47
(Foo )[47] -> 5 |
跳台就是这样工作的。好的。
如果我们想用foo值调用一个方法/函数/过程,有几种可能的方法可以将变量传递给方法,这取决于语言及其几种方法调用模式:好的。
5被复制到一个CPU寄存器(即EAX)。
5被推到堆栈。
47被复制到一个CPU寄存器
47推到堆栈。
223被复制到其中一个CPU寄存器。
223被推到堆栈。
在每种情况下,都会创建一个值(一个现有值的副本)以上的值,现在该值将由接收方法来处理。在方法中编写"foo"时,它要么从eax中读取,要么自动取消引用,要么双重取消引用,过程取决于语言的工作方式和/或foo的类型。在开发人员绕过解引用过程之前,这是隐藏的。因此,引用在表示时是一个值,因为引用是一个必须处理的值(在语言级别)。好的。
现在我们已经将foo传递给方法:好的。
- 案例1。2。如果更改foo(Foo = 9),它只会影响本地作用域,因为您有一个值的副本。从方法内部,我们甚至无法确定原始foo在内存中的位置。
- 案例3。4。如果使用默认语言构造并更改FoO(EDCOX1,1),它可以全局地改变FoO(取决于语言,例如Java或Pascal的EDCOX1×2)var MeDOX1(3)。但是,如果语言允许您绕过取消引用过程,您可以更改47,比如对49。在这一点上,如果你读到foo,它似乎已经被改变了,因为你已经改变了指向它的本地指针。如果您要在方法(Foo = 12中修改这个foo),您可能会妨碍程序的执行(aka)。segfault)因为您将写入一个与预期不同的内存,所以您甚至可以修改一个用于保存可执行程序的区域,并且写入该区域将修改正在运行的代码(foo现在不在47)。但是foo的47的值并没有全局变化,只有方法内部的值,因为47也是方法的副本。
- 案例5。6。如果在方法内部修改223,它将创建与3相同的混乱。或4。(一个指针,指向一个现在不正确的值,再次用作指针),但这仍然是一个局部问题,因为223已被复制。但是,如果您能够取消引用Ref2Foo(即223),达到并修改47的指向值,即对49的指向值,则会影响全局foo,因为在这种情况下,方法得到了223的副本,但引用的47只存在一次,而将其更改为49将导致Ref2Foo双重取消对错误值的引用。
对无关紧要的细节吹毛求疵,即使是通过引用传递的语言也会将值传递给函数,但这些函数知道它们必须将其用于取消引用的目的。这个传递引用作为值对程序员来说是隐藏的,因为它实际上是无用的,并且术语只是传递引用。好的。
严格的传递值也是无用的,这意味着每当我们调用一个数组作为参数的方法时,就必须复制一个100兆字节的数组,因此Java不能通过值严格传递。每种语言都会通过对这个巨大数组的引用(作为一个值),并且如果该方法可以在方法中本地改变,或者允许该方法(如Java所做的)来全局地修改数组(从调用方的视图中),并且一些语言允许修改引用本身的值,那么就可以采用复制写入机制。好的。
简而言之,在Java自身的术语中,Java是通过值传递的值:要么是实际值,要么是引用的表示值。好的。好啊。
- 在引用传递的语言中,传递的事物(引用)是短暂的;接收者不应该"假定"复制它。在爪哇中,传递一个数组是一个"对象标识符"——相当于一个纸条,它表示"对象24601",当第二万四千六百零一个对象构造成一个数组时。接收者可以复制"object 24601"到任何它想要的地方,任何有纸条说"object 24601"的人都可以用任何数组元素做任何它想要的事情。当然,所传递的位模式实际上不会说"object 24601",但是……
- …关键点是,标识数组的对象ID的接收者可以将该对象ID存储在任何需要的地方,并将其提供给任何需要的人,任何接收者都可以随时访问或修改数组。相比之下,如果一个数组是通过引用传递的,而该语言类似于支持此类内容的pascal,则被调用的方法可以对数组执行它想要的任何操作,但不能以允许代码在返回后修改数组的方式存储引用。
- 实际上,在pascal中,您可以获取每个变量的地址,无论是通过引用传递的还是本地复制的,addr是非类型化的,@是通过类型化的,以后您可以修改引用的变量(本地复制的变量除外)。但我不明白你为什么要这么做。示例中的纸条(object 24601)是一个引用,其目的是帮助在内存中查找数组,它本身不包含任何数组数据。如果重新启动程序,即使同一数组的内容与上一次运行中的内容相同,它也可能获得不同的对象ID。
- 我认为"@"运算符不是标准pascal的一部分,而是作为公共扩展实现的。它是标准的一部分吗?我的观点是,在一种具有真正的pass-by-ref的语言中,没有能力构造指向一个短暂对象的非短暂指针,在通过引用传递数组之前,在宇宙中任何地方保存对数组唯一引用的代码可以知道,除非收件人"欺骗",否则它以后仍将保存唯一引用。在Java中实现这一点的唯一安全方法是构造一个临时对象…
- …它封装了一个AtomicReference,既不暴露引用,也不暴露其目标,而是包含对目标执行操作的方法;一旦对象被传递到的代码返回,则其创建者保存直接引用的AtomicReference将失效并放弃。这将提供适当的语义,但它将是缓慢和棘手的。
Java是按值调用
它是如何工作的
您总是传递引用值位的副本!
如果它是原始数据类型,那么这些位包含原始数据类型本身的值,这就是为什么我们在方法内部更改头的值,那么它不会反映外部的更改。
如果它是一个对象数据类型,如foo foo=new foo(),那么在这种情况下,对象地址的副本会像文件快捷方式一样传递,假设我们在c:desktop有一个文本文件abc.txt,假设我们对同一文件进行快捷方式,并将其放入c:desktopabc快捷方式中,这样当您从c:desktopabc.txt访问文件并写入"堆栈溢出"时'关闭文件,然后再次从快捷方式打开文件,然后写'是程序员要学习的最大的在线社区'然后总的文件更改将是'堆栈溢出是程序员要学习的最大的在线社区'这意味着无论从何处打开文件,每次我们访问同一个文件时,h在此,我们可以假设foo是一个文件,并假设foo存储在123hd7h(原始地址,如c:desktopabc.txt)地址和234jdid(复制地址,如c:desktopabc快捷方式,其中实际包含文件的原始地址)。所以为了更好的理解制作快捷方式文件并感觉…
不,这不是通时参考。 >
Java是由通值根据Java语言规范: >
When the method or constructor is invoked (§15.12), the values of the actual argument expressions initialize newly created parameter variables, each of the declared type, before execution of the body of the method or constructor. The Identifier that appears in the DeclaratorId may be used as a simple name in the body of the method or constructor to refer to the formal parameter.
据我所知,Java只知道按值调用。这意味着对于基本数据类型,您将使用副本,对于对象,您将使用对象引用的副本。然而,我认为存在一些陷阱;例如,这不起作用:
这将填充hello world而不是world hello,因为在swap函数中,您使用的是对主引用没有影响的copy。但是,如果对象不是不变的,您可以更改它,例如:
这将在命令行中填充hello world。如果将stringbuffer更改为string,它将生成hello,因为string是不可变的。例如:
1 2 3 4 5 6 7 8 9
| 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 );
} |
但是,您可以为这样的字符串创建一个包装器,使其能够与字符串一起使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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这样的不可变对象。
- +1对于交换测试——可能是区分按引用传递和按值传递引用的最直接和最相关的方法。如果你可以很容易地编写一个函数swap(a, b),它(1)从调用者的pov中交换a和b,(2)在静态类型允许的范围内是不可知的类型(意味着将它与另一个类型一起使用只需要更改声明的a和b类型),(3)不需要调用者若要显式传递指针或名称,则该语言支持按引用传递。
- "。对于基本数据类型,您将使用副本;对于对象,您将使用对象引用的副本"-完全写入!
让我试着用四个例子来解释我的理解。Java是按值传递,而不是按引用传递。
/**
通过值
在Java中,所有的参数都是通过值传递的,即分配一个方法参数对调用方是不可见的。
*/
例1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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";
}
} |
结果
1 2 3
| output : output
value : Nikhil
valueflag : false |
例2:
/****传递值**/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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";
}
} |
结果
1 2 3
| output : output
value : Nikhil
valueflag : false |
例3:
/**这个"传递值"有一种"传递引用"的感觉。
有些人说基元类型和字符串是"传递值"对象是"按引用传递"。
但从这个例子中,我们可以理解它只是一个错误的传递值,请记住,这里我们将引用作为值传递。ie:引用是按值传递的。这就是为什么能够改变,并且在本地范围之后仍然保持不变的原因。但是我们不能改变原始范围之外的实际引用。下一个passByValueObjectCase2示例演示了这意味着什么。
*/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| 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";
}
} |
结果
1 2
| output : output
student : Student [id=10, name=Anand] |
例4:
/**
除了在示例3中提到的(PasByValueObjultCase1.java)之外,我们不能改变原始范围之外的实际引用。
注:我没有粘贴private class Student的代码。Student的类定义与示例3相同。
*/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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";
}
} |
结果
1 2
| output : output
student : Student [id=10, name=Nikhil] |
在Java中,您永远不能通过引用,而显而易见的方式之一是当您希望从方法调用返回多个值时。在C++中考虑下面的代码位:
1 2 3 4 5 6 7 8 9 10
| void getValues(int& arg1, int& arg2) {
arg1 = 1;
arg2 = 2;
}
void caller() {
int x;
int y;
getValues(x, y);
cout <<"Result:" << x <<"" << y << endl;
} |
有时你想在爪哇使用相同的模式,但你不能,至少不是直接的。相反,您可以这样做:
1 2 3 4 5 6 7 8 9 10
| 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中,您将数组的指针作为一个值传递到EDCOX1(0)中。这就足够了,因为该方法随后会修改数组元素,按照惯例,您希望元素0包含返回值。显然,您可以用其他方法来实现这一点,例如构造代码以使其不必要,或者构造一个可以包含返回值或允许设置返回值的类。但是在C++中可用的简单模式在Java中是不可用的。
我想我会贡献这个答案来从规范中添加更多细节。
首先,通过引用传递和通过值传递有什么区别?
Passing by reference means the called functions' parameter will be the
same as the callers' passed argument (not the value, but the identity
- the variable itself).
Pass by value means the called functions' parameter will be a copy of
the callers' passed argument.
或者来自维基百科,关于引用传递的主题
In call-by-reference evaluation (also referred to as
pass-by-reference), a function receives an implicit reference to a
variable used as argument, rather than a copy of its value. This
typically means that the function can modify (i.e. assign to) the
variable used as argument—something that will be seen by its caller.
关于传递值的主题
In call-by-value, the argument expression is evaluated, and the
resulting value is bound to the corresponding variable in the function [...].
If the function or procedure is able to assign values to its
parameters, only its local copy is assigned [...].
第二,我们需要知道Java在它的方法调用中使用什么。Java语言规范状态
When the method or constructor is invoked (§15.12), the values of the
actual argument expressions initialize newly created parameter
variables, each of the declared type, before execution of the body of
the method or constructor.
因此,它将参数的值赋给(或绑定)相应的参数变量。
这个参数的值是多少?
让我们考虑引用类型,即Java虚拟机规范状态。
There are three kinds of reference types: class types, array types,
and interface types. Their values are references to dynamically
created class instances, arrays, or class instances or arrays that
implement interfaces, respectively.
Java语言规范也指出
The reference values (often just references) are pointers to these objects, and a special null reference, which refers to no object.
参数的值(某些引用类型)是指向对象的指针。注意,变量、具有引用类型返回类型的方法调用和实例创建表达式(new ...都解析为引用类型值。
所以
1 2 3 4 5 6
| public void method (String param ) {}
...
String var = new String("ref");
method (var );
method (var. toString());
method (new String("ref")); |
所有这些都将对String实例的引用的值绑定到该方法新创建的参数param上。这正是传递值定义所描述的。因此,Java是按值传递的。
您可以跟随引用来调用方法或访问被引用对象的字段,这一事实与会话完全无关。传递引用的定义是
This typically means that the function can modify (i.e. assign to) the
variable used as argument—something that will be seen by its caller.
在爪哇中,修改变量意味着重新分配它。在Java中,如果在方法中重新分配变量,它将不会被调用方忽略。修改变量引用的对象是完全不同的概念。
在Java虚拟机规范中也定义了原始值。类型的值是相应的整数或浮点值,适当编码(8、16、32、64等位)。
- 感谢您首先讨论这些术语的定义。这使你的回答与群众不同,是正确理解的必要条件。
这个区别,或者只是我记忆中的印象,和原来海报的印象一样,就是:Java总是通过价值传递。在爪哇,所有对象(在爪哇,除了原语以外)都是引用。这些引用是按值传递的。
- 我觉得你的倒数第二句话很误导人。"Java中的所有对象都是引用"是不正确的。只有对这些对象的引用才是引用。
正如许多人之前提到的,Java总是通过价值传递。
下面是另一个帮助您理解差异的示例(经典交换示例):
印刷品:
Before: a = 2, b = 3
After: a = 2, b = 3
这是因为ia和ib是新的局部引用变量,具有相同的传递引用值(它们分别指向a和b)。因此,试图更改IA或IB的引用只会在本地范围内发生更改,而不会超出此方法的范围。
Java只通旁路哈希值。这是非常简单的验证实例。 >
1 2 3 4 5 6 7 8 9
| 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号。
在Java中,只传递引用并通过值传递:
Java参数都通过值传递(当方法使用时复制引用):
在原始类型的情况下,Java行为很简单:该值将复制到基元类型的另一个实例中。
对于对象,这是相同的:对象变量是指针(bucket),只保存使用"new"关键字创建的对象地址,并像基本类型一样复制。
行为可能与基元类型不同:因为复制的对象变量包含相同的地址(到同一对象)对象的内容/成员可能仍然在方法内被修改,稍后在外部进行访问,从而产生一种错觉,认为(包含)对象本身是通过引用传递的。
"字符串"对象似乎是城市传说中的一个完美反例,即"对象是通过引用传递的":
实际上,在方法中,您将永远无法更新作为参数传递的字符串的值:
字符串对象,由声明为final且不能修改的数组保存字符。只有对象的地址可以用"new"替换。使用"new"更新变量,将不允许从外部访问对象,因为变量最初是由值传递并复制的。
- 那么它是关于对象的byref,是关于原语的byval?
- @MOX请阅读:对象不是通过引用传递的,这是一个分类账:string a=new string("unchanged");
- public void changeit(string changeit)changeit="changed";changeit(a);assert(a.equals("changed"));
- 这是"它不是通过引用传递"语义论证的另一个很好的例子。这个答案正好用粗体的第一句话说:"只传递引用。"要么传递引用,要么不传递引用。你刚刚说你的推荐信已经通过了。通过说"它不是通过引用传递的,因为引用被传递了,但是……"你试图通过破坏英语来表达观点。
- 亚伦"通过引用"并不意味着"传递一个值,它是Java中的一个称为引用"的类型的成员。"参考"的两种用法有着不同的含义。
- @飞利浦是因为软件社区的一个子集做出了一些非常武断的决定。你所描述的是关于"参照传递"如何工作的观点,而不是一个普遍接受的定义。即使你引用了一些与你一致的计算机科学书籍,那也只是意味着那些作者会与你分享你的观点。英语是英语,不管"传递参考定义纯粹主义者"多么想争论这一点,他们只是在争论关于非普遍观点的语义学。这里的一些答案对NIT的主观定义很挑剔。
- 答案很混乱。不能更改方法中的字符串对象(通过引用传递)的原因是字符串对象在设计上是不可变的,不能执行strParam.setChar ( i, newValue )之类的操作。也就是说,字符串和其他任何东西一样都是通过值传递的,而且,由于字符串是非基元类型,因此该值是对用new创建的对象的引用,您可以使用string.intern()来检查这一点。
- 相反,不能通过param="another string"(相当于new string("another string"))更改字符串,因为param的引用值(现在指向"another string")不能从方法体返回。但对于任何其他对象都是如此,区别在于,当类接口允许时,可以执行param.changeme(),被引用的顶级对象将发生更改,因为param指向它,尽管param的引用值本身(C术语中的寻址位置)无法从方法中弹出。
我一直认为这是"传抄"。它是值的副本,无论是原始值还是引用值。如果它是一个原语,它是作为值的位的副本;如果它是一个对象,它是引用的副本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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 PopBaseX的输出:
name= Maxx
name= Fido
基本包装类和字符串是不可变的,因此使用这些类型的任何示例都不会与其他类型/对象相同。
我已经为这里的任何编程语言创建了一个专门针对这些问题的线程。
Java也被提及。以下是简短的总结:
- Java按值传递参数
- "按值"是Java将参数传递给方法的唯一方法
- 使用给定对象的方法作为参数将更改对象作为引用指向原始对象。如果方法本身会改变一些值)
对一些帖子做一些修改。
C不支持按引用传递。它总是传递值。C++支持通过引用,但不是默认的,非常危险。
不管Java中的值是什么:原语还是对象的粗略地址,它总是通过值传递。
如果Java对象"行为"类似于它是通过引用传递的,那么这是一个可变属性,与传递机制完全无关。
我不知道为什么这么混乱,也许是因为这么多的Java"程序员"没有被正式训练,因此不理解在记忆中到底发生了什么?
- + 1。C所支持的是将引用(C调用指针)作为第一类值,然后按值传递它们。
- 是的,在C语言中,您不仅可以在对象上创建指针,还可以在任意变量上创建指针,因此您可以轻松地模拟任何变量的"引用调用"。在爪哇,这只适用于具有周围对象的变量,你可以给出这些变量。
长话短说,Java对象具有一些非常特殊的属性。
一般来说,Java有原始类型(EDCOX1,0,EDCOX1,1,EDCOX1,2,EDCX1,3,等等),它们是由值直接传递的。然后Java有对象(从EDCOX1中得出的所有内容4)。对象实际上总是通过引用来处理(引用是一个你不能触摸的指针)。这意味着实际上,对象是通过引用传递的,因为引用通常并不有趣。但是,这并不意味着您不能更改指向哪个对象,因为引用本身是通过值传递的。
这听起来奇怪和困惑吗?让我们考虑一下C如何实现传递引用和传递值。在C中,默认约定是pass-by值。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对象和它的引用的理解。Java中的对象通过引用副本(或别名)传递给方法。
Java通过值传递参数,仅通过值传递参数。
长话短说:
For those coming from C#: THERE IS NO"out" parameter.
For those coming from PASCAL: THERE IS NO"var" parameter.
这意味着不能从对象本身更改引用,但可以始终更改对象的属性。
解决方法是使用StringBuilder参数而不是String参数。你可以一直使用数组!
- 我想你是通过说you can't change the reference from the object itself才明白的。
- 因此,一个对象可以通过使用它的公共接口而改变,但不能用其他东西代替,即顺从,而这个Java实现了两个世界的最佳。
在所有的答案中,我们看到Java是通过价值传递的,或者说是"@ Gevorg"。写着:"传递变量值的副本",这是我们应该一直铭记在心的想法。
我将重点放在帮助我理解这个想法的例子上,它是对以前答案的补充。
从Java中的[1 ]中,你总是通过复制传递参数;也就是说,你总是在函数内创建一个新的值实例。但有些行为会让你觉得你是在引用别人。
通过copy/by值传递的示例
[参考文献1]中的示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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 ) |
[参考文献2]中的示例
shows nicely the mechanism
watch max 5 min
(通过引用传递)传递变量值的副本
[参考文献1]中的示例(记住数组是一个对象)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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 ) |
正在复制复杂对象本身,但保留内部引用。
[参考文献3]中的示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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 LyDaNo
参考文献2 Mehran Sahami教授
参考文献3
- 默认情况下,Java被称为"按引用传递"。通常,当可变对象传递给某些方法时,对该对象引用所做的任何更改也将使用相同的对象引用反映到其他部分。但在不可变对象的情况下,如果引用被重新分配给其他一些对象,那么它将不会反映到其他部分,而是创建全新的对象。
- @ SaeleNdRasejh Java从来没有经过引用,句号。关于这一点的明确证据,请参阅Gaurav的答案。
- "变量值的传递副本"稍有误导性。实际参数是一个值,它是计算表达式的结果。表达式中不需要变量。
这是回答我的问题的最好方法…
首先,我们必须理解,在Java中,参数传递行为…
1 2 3 4 5 6 7 8 9 10 11
| public void foo (Object param )
{
// some code in foo...
}
public void bar ()
{
Object obj = new Object();
foo (obj );
} |
和…
1 2 3 4 5 6 7 8
| public void bar ()
{
Object obj = new Object();
Object param = obj ;
// some code in foo...
} |
不考虑堆栈位置,这在本讨论中不相关。
事实上,我们在Java中寻找的是变量赋值是如何工作的。我在医生那里找到的:
One of the most common operators that you'll encounter is the simple assignment operator"=" [...] it assigns the value on its right to the operand on its left:
int cadence = 0;
int speed = 0;
int gear = 1;
This operator can also be used on objects to assign object references [...]
很明显,这个操作符是如何以两种不同的方式工作的:赋值和赋值引用。最后,当它是一个物体时…第一,当它不是一个物体时,也就是说,当它是一个原始物体时。但是,我们能理解Java的函数PARAM可以通过值传递并通过引用吗?
真理在法典中。让我们试试看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| 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
");
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!
");
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?
");
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!
");
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?
");
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!
");
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!
");
}
} |
这是我运行的输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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]中,分配的对象不是另一个对象的副本,应该称为传递引用。但是,如果我们仔细看[6]中的内容,我们就不能确定是否没有复制过…???????
我们不能确定,因为在[6]中,对象是相同的,然后我们为其中一个对象分配了一个新对象,之后,对象的值就不同了!如果它们是相同的,那么它们现在又是如何区别的呢?它们在这里也应该是一样的!???????
我们需要记住文档以了解发生了什么:
This operator can also be used on objects to assign object references
所以我们的两个变量在存储引用…我们的变量在[4]之后有相同的引用,在[6]之后有不同的引用。如果可能的话,这意味着对象的赋值是通过对象引用的副本来完成的,否则,如果对象不是引用的副本,那么[6]中变量的打印值应该是相同的。因此,对象(引用)和原语一样,通过赋值(人们通常称之为传递值)被复制到变量中。这是Java中唯一的通行证。
这真是一个相当,相当简单: >
对原始变量(EG)型。int,boolean,char等…),当你使用它的name参数为法的价值,你是穿在它(含5,true,他们'c')。获取"copied this value","变量值retains甚至invocation后其法。 > 对变频型(Eg =。String,Object等…),当你使用它的name参数测定的方法,你是在它(含低功率值的参考值,对"点"的对象)。这个参考值获取"copied","变量值retains甚至invocation后其法。"pointing参考变量的不到相同的对象。 > 要么你总是穿路,再由价值的东西。 > 这样说比较C++在你可以有一个方法把年int&C,或在#在你能把ref int(虽然,在这房子,你也要穿的时候使用refmodifier变量"name"的方法。) >
- 还记得在Java中EDCOX1的6个s是不可变的。
- 我发现将类类型变量视为保存"对象ID"很有帮助。Java的对象的默认字符串表示方式与这样的描述结合得很好。如果将Foo设置为"car 1234",并将Foo复制到Bar,那么Foo和Bar都将持有"car 1234"。说Foo.SetColor(Colors.Blue)会把"汽车1234"涂成蓝色。我对此类存储位置使用的另一种描述是"混杂对象引用",因为将此类引用传递给对象的代码无法控制收件人如何共享它。
- 变量不会进入Java工作的方式。实际参数来自计算表达式。
Java按值复制引用。因此,如果您将其更改为其他内容(例如,使用EDOCX1[1]),则引用在方法之外不会更改。对于本机类型,它总是按值传递。
通常是由Java拷贝参考参考在这过去的均值,它是通过由基本价值。你可以改变的内容的"参考级如果是可变的,但你不能改变参考本身。换句话说我的地址不会被改变,但由于它是由过去的点式值的内容,是可以由地址已经改变了。在家里,由immutable of the content of the参考,要么是不能改变的。 >
- 除非程序员指定"最后",否则Java中不存在"常数引用"之类的东西。
- 我所说的常量引用是指,在函数中说new myclass()不能改变引用本身。如果我放对了,对象引用是按值传递的,这意味着传递了引用的副本,这样您就可以更改引用所引用的数据,但不能用new操作符更改它并分配一个新对象。
- 所以,确定你的答案。如果它是一个常量,则不能在被调用的方法中重新分配它,并且可以,除非指定final.。
毫无疑问,Java是"价值传递"。此外,由于Java(主要是面向对象的)和对象与引用一起工作,所以很容易被混淆,并认为它是"通过引用"。
传递值是指将值传递给方法,如果方法更改传递的值,则实际实体不会更改。另一方面,pass-by-reference意味着将引用传递给方法,如果方法更改了该方法,则传递的对象也会更改。
在爪哇中,通常当我们把对象传递给一个方法时,我们基本上传递对象的引用作为一个值,因为Java就是这样工作的,它与引用和地址一起工作,直到堆中的对象运行为止。
但要测试它是传递值还是传递引用,可以使用基元类型和引用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @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 );
} |
输出是:
1 2 3 4
| incremented = 105
passed ==> 5
incremented = 105
passed ==> 5 |
因此,在这两种情况下,无论方法内部发生什么,都不会更改实际对象,因为该对象的值是传递的,而不是对象本身的引用。
但是当您将一个自定义对象传递给一个方法以及方法并对其进行更改时,它也会更改实际对象,因为即使传递了该对象,也会将其引用作为值传递给该方法。让我们试试另一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| @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();
}
} |
在这种情况下,输出为:
1 2
| Person [age=24, name=John]
Person [age=45, name=Tom] |
Java编程语言中最大的困惑之一是Java是按值传递还是通过引用传递。
首先,我们应该理解什么是传递值或传递引用。
传递值:将方法参数值复制到另一个变量,然后传递复制的对象,这就是它被称为传递值的原因。
传递引用:将对实际参数的别名或引用传递给方法,这就是它被称为传递引用的原因。
假设我们有一个像下面这样的班级气球。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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 ;
}
} |
我们有一个简单的程序,用一个通用方法交换两个对象,类如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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("red color="+red. getColor());
System. out. println("blue color="+blue. getColor());
foo (blue );
System. out. println("blue color="+blue. getColor());
}
private static void foo (Balloon balloon ) { //baloon=100
balloon. setColor("Red"); //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 ;
}
} |
当我们执行上述程序时,我们得到如下输出。
1 2 3
| red color=Red
blue color=Blue
blue color=Red |
如果查看输出的前两行,很明显swap方法不起作用。这是因为Java是通过值传递的,这个SWAP-()方法测试可以与任何编程语言一起使用,以检查它是通过值还是通过引用传递。
让我们一步一步地分析程序的执行情况。
1 2
| Balloon red = new Balloon("Red");
Balloon blue = new Balloon("Blue"); |
当我们使用new操作符创建一个类的实例时,该实例被创建,变量包含保存对象的内存的引用位置。对于我们的示例,假设"red"指向50,"blue"指向100,这是两个气球对象的内存位置。
现在,当我们调用swap()方法时,会创建两个分别指向50和100的新变量o1和o2。
下面的代码片段解释了swap()方法执行过程中发生的事情。
1 2 3 4 5
| 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()方法的执行情况。
1 2 3 4 5
| private static void foo(Balloon balloon) { //baloon=100
balloon.setColor("Red"); //baloon=100
balloon = new Balloon("Green"); //baloon=200
balloon.setColor("Blue"); //baloon = 200
} |
第一行是重要的一行,当我们调用一个方法时,方法会在引用位置的对象上被调用。此时,气球指向100,因此颜色变为红色。
在下一行中,气球引用更改为200,并且在内存位置200处的对象上执行任何其他方法,并且对内存位置100处的对象没有任何影响。这就解释了程序输出的第三行打印蓝色=红色。
我希望上面的解释清楚了所有的疑惑,只记得变量是引用或指针,它的副本传递给方法,所以Java总是通过值传递。当您了解堆和堆栈内存以及存储不同对象和引用的位置时,您会更加清楚。
与其他一些语言不同,Java不允许您选择传递值或传递引用,所有参数都是按值传递的。方法调用可以将两种类型的值传递给基元值(例如int和double的值)的方法副本和对象引用的副本。
当方法修改基元类型参数时,对参数的更改对调用方法中的原始参数值没有影响。
当涉及到对象时,不能将对象本身传递给方法。所以我们传递对象的地址,它保存在引用变量中。
Java如何创建和存储对象:当我们创建对象时,我们将对象的地址存储在引用变量中。"scanner input"是类型和引用变量,"="是赋值运算符,"new"要求系统提供所需的空间量。创建对象的关键字new右边的构造函数由关键字new隐式调用。使用赋值运算符将创建的对象的地址(右变量的结果,即表达式)分配给左变量(即指定了名称和类型的引用变量)。"new account()"称为"类实例创建表达式"。
尽管对象的引用是按值传递的,但方法仍然可以通过使用对象引用的副本调用其公共方法与被引用对象进行交互。由于存储在参数中的引用是作为参数传递的引用的副本,因此被调用方法中的参数和调用方法中的参数引用内存中的同一对象。
出于性能原因,将引用传递到数组而不是数组对象本身是有意义的。因为如果传递数组对象,则Java中的所有内容都是通过值传递的,将传递每个元素的副本。对于大型阵列,这将浪费时间并消耗为元素副本提供了大量的存储空间。
在下面的图片中,你可以看到我们有两个参考变量(这些被称为C/C++中的指针,并且我认为这个术语使它更容易理解这个特性)。基本变量和引用变量保存在堆栈内存中(下图左侧)。ARARY1和ARARY2参考变量"点"(如C/C++程序员称之为)或参考A和B数组,它们是堆内存(在下面的图像右侧)对象(这些引用变量保存的对象的值)。
如果我们将array1引用变量的值作为参数传递给reversearray方法,则会在该方法中创建一个引用变量,并且该引用变量开始指向同一个数组(a)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Test
{
public static void main (String args )
{
int[] array1 = { 1, 10, -7 };
reverseArray (array1 );
}
public void reverseArray (Int [] array1 )
{
// ...
}
} |
所以,如果我们说
在ReverseArray方法中,它将在数组A中进行更改。
在ReverseArray方法(array2)中,我们还有另一个引用变量指向数组C。
在ReverseArray方法中,ReverseArray方法中的引用变量array1将停止指向数组A,并开始指向数组C(第二幅图像中的虚线)。
如果我们返回引用变量array2的值作为方法reverseArray的返回值,并将该值赋给主方法中的引用变量array1,那么主方法中的array1将开始指向数组C。
1
| return array2; // This code is in reverseArray method. |
所以让我们马上写下我们已经做过的所有事情。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Test
{
public static void main (String args )
{
int[] array1 = { 1, 10, -7 };
int[] array2 = { 5, -190, 0 };
array1 = reverseArray (array1 ); /* array1 of
main starts pointing to c instead of a */
}
public void reverseArray (Int [] array1 )
{
int[] array2 = { -7, 0, -1 };
array1 [0] = 5; // array a becomes 5, 10, -7
array1 = array2 ; /* array1 of reverseArray starts
pointing to c instead of a (not shown in image below) */
return array2 ;
}
} |
现在ReverseArray方法结束了,它的引用变量(array1和array2)也消失了。这意味着我们现在只有主方法array1和array2中的两个引用变量分别指向C和B数组。没有引用变量指向对象(数组)A。因此它符合垃圾收集的条件。
您还可以在main中将array2的值赋给array1。array1将开始指向b。
Java总是使用按值调用。这意味着该方法获取所有参数值的副本。
考虑以下3种情况:
1)试图更改原语变量
1 2 3 4
| public static void increment(int x) { x++; }
int a = 3;
increment(a); |
x将复制a的值并增加x,a保持不变
2)尝试更改对象的原始字段
1 2 3 4
| public static void increment(Person p) { p.age++; }
Person pers = new Person(20); // age = 20
increment(pers); |
p将复制pers的引用值,并增加"年龄"字段,变量引用同一对象,因此年龄发生了变化。
3)试图改变参考变量的参考值
1 2 3 4 5 6 7 8 9
| 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后,p2复制pers1和pers2的引用值,并与值交换,因此pers1和pers2保持不变。
所以。在将引用值的副本传递给此对象的方法中,只能更改对象的字段。
这么多冗长的答案。让我举一个简单的例子:
- Java总是通过值传递所有的东西
- 也就是说引用是按值传递的
简而言之,不能修改任何传递的参数的值,但可以调用方法或更改传递的对象引用的属性。
看看这个代码。此代码不会引发NullPointerException…它会印上"Vinay"
1 2 3 4 5 6 7 8 9 10 11
| 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通过引用,那么它应该已经抛出EDCOX1 OR 2,因为引用被设置为NULL。
- 静电正在使它脱落。public class main public static void main(string[]args)main m=new main();m.aaa();public void aaa()string temp="vinay";print(temp);system.err.println(temp);private void print(string temp)temp=null;上述代码不NPE。
- 你没明白你想说什么……
- 不回答问题。行动要求的是解释,而不仅仅是证据。
Java严格按值传递当我说pass-by-value时,它意味着每当调用者调用被调用者时,参数(即:要传递给另一个函数的数据)都会被复制并放置在形式参数(被调用者接收输入的局部变量)中。Java只在一个通过值环境中实现从一个函数到其他函数的数据通信。
重要的一点是要知道,即使是C语言也只能通过值传递:即:数据从调用者复制到被调用者,而且被调用者执行的操作始终位于同一内存位置,并且我们传递给它们的是从(&;)运算符获取的该位置的地址,并且形式参数中使用的标识符声明为指针变量(*),使用该变量我们可以进入内存位置以访问其中的数据。
因此,这里的形参只不过是该位置的别名。对该位置所做的任何修改在变量的作用域(标识该位置)处于活动状态时都是可见的。
在爪哇中,没有指针的概念(即:没有所谓的指针变量),尽管我们可以把引用变量当作指针,从技术上讲,我们把它称为句柄。之所以调用Java中的句柄作为句柄,是因为指针变量不仅能够执行单个撤销,而且可以执行多次撤销。例如:p中的int *p;表示p指向整数。c中的int **p;表示p是指向整数的指针。我们在Java中没有这个设施,所以它绝对正确和技术上合理地说它是一个句柄,在C.中也有指针算法的规则,它允许对有约束的指针执行算术运算。
在C语言中,我们使用指针变量作为传递引用来调用传递地址和接收地址的机制,因为我们在形式参数中传递地址并接收它们作为指针变量,但是在编译器级别,地址被复制到指针变量中(因为这里的数据是地址,甚至是它的数据),因此我们可以100%地使用是否C严格按值传递(因为我们只传递数据)
(如果我们直接用C传递数据,我们称之为传递值。)
在Java中,当我们这样做时,我们用句柄来处理它们,因为它们不被称为指针变量,如In(如上所述),即使我们正在传递引用,也不能说它是通过引用的,因为我们没有用Java中的指针变量来收集它。
因此Java严格使用逐值机制
主要的基础知识必须是引用的知识,
When an object reference is passed to a method, the reference itself
is passed by use of call-by-value. However, since the value being
passed refers to an object, the copy of that value will still refer to
the same object referred to by its corresponding argument.
JAVA:初学者指南,第六版,Herbert Schildt
检查语言是否支持按引用传递的简单测试只是编写一个传统的交换。你能用Java编写一个传统的交换(A,B)方法/函数吗?
传统的交换方法或函数接受两个参数并交换它们,以便传递到函数的变量在函数外部发生更改。它的基本结构看起来像
(非Java)基本交换函数结构
1 2 3 4 5
| swap(Type arg1, Type arg2) {
Type temp = arg1;
arg1 = arg2;
arg2 = temp;
} |
如果您可以用您的语言编写这样的方法/函数,以便调用
1 2 3
| Type var1 = ...;
Type var2 = ...;
swap(var1,var2); |
实际上,切换变量var1和var2的值,该语言支持按引用传递。但是Java不允许它支持只传递值而不是指针或引用。
- 你可能想澄清你的最后一句话。我对"只传递值而不是指针……"的第一个反应是,Java实现可能是这样做的,传递指针。您不能取消对该指针的引用这一事实似乎无关紧要。
值调用一词意味着方法只获取调用方提供的值。相反,引用调用意味着该方法获取调用方提供的变量的位置(地址)。
Java编程语言总是使用按值调用。这意味着该方法获取所有参数值的副本。特别是,尽管方法可以更改调用方变量引用的对象,但它不能修改变量本身。
在下面的示例中,println(sb_actual)调用打印"foo",而不是"bar",因为addFoo()中对sb_formal的赋值不会更改调用方的sb_actual变量。
1 2 3 4 5 6 7 8 9 10 11
| void addFoo (StringBuffer sb_formal ) {
sb_formal. append("Foo"); // Modifies the object created by the caller.
sb_formal = new StringBuffer(); // Creates a new object.
sb_formal. append("Bar"); // Modifies the new object.
}
void caller () {
StringBuffer sb_actual = new StringBuffer();
addFoo (sb_actual );
System. out. println(sb_actual ); // sb_actual still refers to the original object here.
} |
- private void test1(string s)s="h";//从未使用值,因为这与更改原始对象相同private void test2(string[]s)s[0]="h";//更改原始对象中的内容s=new string[]"k"//s现在只是本地对象s[0]="l";//因为s对象rreference是按值传递的,//即不能更改原始对象引用("指针")或对象或结构本身
Java通过引用引用值和类类型传递基元类型现在,人们喜欢喋喋不休地争论"通过引用"是否是描述Java等的正确方式。事实上是这样。重点是:
传递对象不会复制该对象。
传递给函数的对象可以由函数修改其成员。
传递给函数的基元值不能由函数修改。复印件。
在我的书中,这就是所谓的参照传递。
brian bi-哪些编程语言是通过引用传递的?
- 我不确定技术上是否正确地提到副本是为原始类型制作的。基元类型是不可变的,这就是为什么不能在传递基元类型的方法内修改基元类型的原因。这种差异对于数字之类的东西是可以忽略的,但是对于潜在的大字符串来说,有一个重要的区别。
- 这个答案完全不正确,只会造成混乱。Java是纯值传递语言。让您困惑的是,该值可以是指向对象的指针。传递引用意味着可以更改调用方端对象的标识。例如,将新对象分配给方法参数也会影响在调用该方法的代码中传递的指针。
- @丹尼斯字符串不是原始的,它们是对象。
- 它不是关于"在你的书中"是什么。"通过参考"和"通过价值"是行业标准术语,有非常具体的定义。通过这些定义,Java是"按值传递",没有例外。
在一年attempt to add,这更是我的思想,包括《学习指南SCJP型的话题。这是从制造的指南,是通到太阳/ Oracle的行为测试的Java源代码,所以这是一个很好的使用为这所有的讨论。 >
Passing Variables into Methods (Objective 7.3)
7.3 Determine the effect upon object references and primitive values when they are passed into methods that perform assignments or other modifying operations on the parameters.
Methods can be declared to take primitives and/or object references. You need to know how (or if) the caller's variable can be affected by the called method. The difference between object reference and primitive variables, when passed into methods, is huge and important. To understand this section, you'll need to be comfortable with the assignments section covered in the first part of this chapter.
Passing Object Reference Variables
When you pass an object variable into a method, you must keep in mind that you're passing the object reference, and not the actual object itself. Remember that a reference variable holds bits that represent (to the underlying VM) a way to get to a specific object in memory (on the heap). More importantly, you must remember that you aren't even passing the actual reference variable, but rather a copy of the reference variable. A copy of a variable means you get a copy of the bits in that variable, so when you pass a reference variable, you're passing a copy of the bits representing how to get to a specific object. In other words, both the caller and the called method will now have identical copies of the reference, and thus both will refer to the same exact (not a copy) object on the heap.
For this example, we'll use the Dimension class from the java.awt package:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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. } |
When we run this class, we can see that the modify() method was indeed able to modify the original (and only) Dimension object created on line 4.
1 2 3 4 5
| C:\Java Projects
eference>java ReferenceTest
Before modify() d.height = 10
dim = 11
After modify() d.height = 11 |
Notice when the Dimension object on line 4 is passed to the modify() method, any changes to the object that occur inside the method are being made to the object whose reference was passed. In the preceding example, reference variables d and dim both point to the same object.
Does Java Use Pass-By-Value Semantics?
If Java passes objects by passing the reference variable instead, does that mean Java uses pass-by-reference for objects? Not exactly, although you'll often hear and read that it does. Java is actually pass-by-value for all variables running within a single VM. Pass-by-value means pass-by-variable-value. And that means, pass-by-copy-of- the-variable! (There's that word copy again!)
It makes no difference if you're passing primitive or reference variables, you are always passing a copy of the bits in the variable. So for a primitive variable, you're passing a copy of the bits representing the value. For example, if you pass an int variable with the value of 3, you're passing a copy of the bits representing 3. The called method then gets its own copy of the value, to do with it what it likes.
And if you're passing an object reference variable, you're passing a copy of the bits representing the reference to an object. The called method then gets its own copy of the reference variable, to do with it what it likes. But because two identical reference variables refer to the exact same object, if the called method modifies the object (by invoking setter methods, for example), the caller will see that the object the caller's original variable refers to has also been changed. In the next section, we'll look at how the picture changes when we're talking about primitives.
The bottom line on pass-by-value: the called method can't change the caller's variable, although for object reference variables, the called method can change the object the variable referred to. What's the difference between changing the variable and changing the object? For object references, it means the called method can't reassign the caller's original reference variable and make it refer to a different object, or null. For example, in the following code fragment,
1 2 3 4 5 6 7 8
| void bar() {
Foo f = new Foo();
doStuff(f);
}
void doStuff(Foo g) {
g.setName("Boo");
g = new Foo();
} |
reassigning g does not reassign f! At the end of the bar() method, two Foo objects have been created, one referenced by the local variable f and one referenced by
the local (argument) variable g. Because the doStuff() method has a copy of the reference variable, it has a way to get to the original Foo object, for instance to call the setName() method. But, the doStuff() method does not have a way to get to the f reference variable. So doStuff() can change values within the object f refers to, but doStuff() can't change the actual contents (bit pattern) of f. In other words, doStuff() can change the state of the object that f refers to, but it can't make f refer to a different object!
Passing Primitive Variables
Let's look at what happens when a primitive variable is passed to a method:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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 );
}
} |
In this simple program, the variable a is passed to a method called modify(),
which increments the variable by 1. The resulting output looks like this:
1 2 3
| Before modify() a = 1
number = 2
After modify() a = 1 |
Notice that a did not change after it was passed to the method. Remember, it was a copy of a that was passed to the method. When a primitive variable is passed to a method, it is passed by value, which means pass-by-copy-of-the-bits-in-the-variable.
- 通过传递值,就像Java一样,"呼叫者的变量"是没有意义的。实际的参数是对表达式的计算;根本不需要涉及变量。
这有点难以理解,但Java总是复制值-点是,通常值是一个引用。所以你最终得到的是同一个物体而没有考虑它…
第1部分:不动产清单有一个120平方英尺的蓝色"小房子",目前停在1234主街,前面有一个精心修剪的草坪和花坛。好的。
当地一家公司的房地产经纪人被雇用,并被告知为那所房子保留一份清单。好的。
我们叫那个房地产经纪人"鲍勃"。你好,鲍勃。好的。
Bob用一个网络摄像头来实时记录实际房屋的任何变化,从而使他的清单保持最新,他称之为tinyHouseAt1234Main。他还记录了有多少人询问过这个清单。鲍勃为房子设计的整数viewTally今天是42。好的。
每当有人想知道1234主街蓝色小房子的情况,他们就会问鲍勃。好的。
Bob查了一下清单tinyHouseAt1234Main,把它的颜色、漂亮的草坪、阁楼床和堆肥厕所等都告诉了他们,然后他把他们的询问加到了viewTally上。但他并没有告诉他们真实的实际地址,因为鲍勃的公司专门经营可以随时搬迁的小房子。现在的数字是43。好的。
在另一家公司,房地产经纪人可能会明确地说,他们的挂牌"指向"了位于主大街1234号的房子,这意味着旁边有一点*,因为他们主要处理很少搬家的房子(尽管可能有理由这样做)。鲍勃的公司不费心这么做。好的。
现在,当然,鲍勃不会亲自去把房子放在卡车上直接展示给客户——这是不切实际的,也是一种荒谬的资源浪费。传递一份完整的计数表是一回事,但总是在整个房子里传递是昂贵和荒谬的。好的。
(旁白:鲍勃的公司也不会每次有人问起,3D打印出一所上市公司的新的和独特的副本。这就是新贵,同样被称为基于网络的公司及其子公司所做的事情——这是昂贵和缓慢的,人们经常混淆这两个公司,但不管怎样,他们还是很受欢迎)。好的。
在其他一些更靠近大海的老公司,像鲍勃这样的房地产经纪人甚至可能不存在来管理上市。客户可以咨询Rolodex"Annie"(简称EDOCX1(简称5))了解房子的直接地址。客户没有像Bob那样从清单中读取参考的房屋详细信息,而是从Annie(&处获得房屋地址,直接前往1234 Main St,有时不知道他们可能在那里找到什么。好的。
一天,鲍勃的公司开始提供一项新的自动化服务,这项服务需要客户感兴趣的房子上市。好的。
好吧,有这个信息的人是Bob,所以客户让Bob调用服务并将列表的副本发送给它。好的。
jobKillingAutomatedListingService(Listing tinyHouseAt1234Main, int viewTally)Bob把…好的。
服务在其末尾称这个清单为houseToLookAt,但实际上它收到的是Bob清单的精确副本,其中的值完全相同,指的是位于1234大街的房子。好的。
这个新的服务也有它自己的内部记录,记录有多少人看过这个列表。该服务接受Bob出于专业礼貌而进行的计数,但它并不真正关心,而且还是用它自己的本地副本覆盖它。今天的记录是1,而鲍勃的记录是43。好的。
房地产公司称之为"传递价值",因为Bob传递了他的viewTally和他的上市tinyHouseAt1234Main的当前价值。他实际上并没有穿过整个实体房子,因为那是不切实际的。他也不会像安妮(&)那样传递真实的物理地址。好的。
但他正在传递一份他对房子的参考价值。在某些方面似乎是一种愚蠢的学究式的区别,但他的公司就是这样运作的…………好的。第二部分:事情变得混乱和危险…
新的自动化服务并不像其他一些流行的金融和科学公司那样功能性和面向数学的服务,可能会产生不可预见的副作用…好的。
一旦给出了一个列表对象,它允许客户使用远程无人机机队,在1234主街重新粉刷真正的房子!它允许客户控制一台机器人推土机来实际挖掘花坛!这太疯狂了!!!!好的。
该服务还允许客户将houseToLookAt完全重定向到另一个地址的其他房子,而不涉及Bob或他的列表。突然间,他们看到的可能是4321榆树街,这与鲍勃的上市没有任何关系(谢天谢地,他们不能再做损害)。好的。
鲍勃在他的实时网络摄像头上看这些。屈从于他唯一的工作职责,他告诉客户新的难看的油漆工作和突然缺乏抑制吸引力。毕竟,他的名单仍在主街1234号。新服务的houseToLookAt无法改变这一点。鲍勃像往常一样准确、尽职地报告了他的埃多克斯的详细情况,直到他被解雇或房子被一无所有所毁。好的。
实际上,该服务唯一不能做的事情就是将Bob原始列表的houseToLookAt副本地址从1234主街更改为其他地址,或更改为虚无,或更改为某种随机类型的对象,如鸭嘴兽。Bob的列表仍然指向1234主街,不管它值多少钱。他像往常一样传递它的当前值。好的。
将清单传递给新的自动化服务的这种奇怪的副作用,对于询问它如何工作的人来说是令人困惑的。真的,遥控机器人改变1234主屋的状态与实际去那里并因为安妮给你地址而造成破坏的能力有什么区别??好的。
如果您通常关心的是列表中的房子的状态被复制和传递,那么这似乎是一种吹毛求疵的语义争论,对吧?好的。
我的意思是,如果你真的在收拾房子并把它们搬到其他地址(而不是像移动或小房子那样的平台的预期功能),或者你在访问、重命名和洗牌整个社区,就像某种低级的上帝在玩疯子,那么也许你更关心abo。但传递这些特定的地址引用,而不仅仅是最新价值的房子细节的副本…好的。好啊。
有一个非常简单的方法来理解这一点。让我们用C++来传递引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #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;
} |
结果如何?
因此,在bar()为传入的"引用"分配了一个新值之后,它实际上更改了从main本身传入的值,解释了主打印99中最后一个f.foo()调用。
现在,让我们看看java说了什么。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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);
}
} |
它说:
喂,传给BAR的foo的主引用仍然保持不变!
这个例子清楚地表明,当我们说"通过引用"时,Java和C++是不一样的。从本质上讲,Java将"引用"作为"值"传递给函数,意味着Java是通过值传递的。
- 在你的C++版本中有一个问题,当EDCOX1×3的范围超出了你的主要方法时,你会冒SeGebug的风险吗?
- 的确。AH来自Java使用了10年。但这个想法仍然有效。我现在就修好了。
- 我认为前面的更好,因为它可以编译。我只是对这种行为好奇,对不起。
- 这个答案只帮助那些来自C++背景的人,他们愿意根据C++的定义定义"引用"的方式。情况并非总是如此。
一切都是按值传递的。基本体和对象引用。但是,如果对象的接口允许,则可以对其进行更改。
当您将一个对象传递给一个方法时,您将传递一个引用,并且该对象可以通过方法实现进行修改。
1 2 3
| void bithday(Person p) {
p.age++;
} |
对象本身的引用是按值传递的:可以重新分配参数,但更改不会反映回:
1 2 3 4 5 6 7
| void renameToJon(Person p) {
p = new Person("Jon"); // this will not work
}
jack = new Person("Jack");
renameToJon(jack);
sysout(jack); // jack is unchanged |
实际上,"P"是引用(指向对象的指针),不能更改。
基元类型是按值传递的。对象的引用也可以被视为基元类型。
总而言之,一切都是按价值传递的。
简单程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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使用传递值,因此对于原始数据类型(int、char等),函数中的更改不会反映在调用函数中。但是当你传递一个对象时,在函数中你改变了它的数据成员或者调用了能改变对象状态的成员函数,调用函数将得到这些改变。
- 每个文件只能定义一个类。这不包括嵌套类和内部类。考虑到这将是一个新的程序员将要阅读的内容,您应该向用户解释这一点;允许他们在自己的机器上复制代码。
- @mrres1不完全正确。每个文件只能定义一个公共顶级类/接口。支持每一个文件的几个类是第一个Java版本的残余,它没有嵌套类,但仍然支持,尽管常常不赞成。
我做了一个小图表,显示了数据是如何被创建和传递的。
注意:基元值作为值传递,对该值的第一个引用是该方法的参数
这意味着:
- 您可以在函数内部更改myObject的值。
- 但是在函数内部不能更改myObject引用的内容,因为point不是myObject。
- 记住,point和myObject都是引用,不同的引用指向同一个new Point(0,0)。
使用基元数据类型的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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
}
} |
使用对象的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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
}
} |
与其他语言不同,Java不允许您选择传递值或通过引用。
所有参数都是按值传递的。
方法调用可以将两个types of values传递给一个方法
- 原始值的副本(例如int和double类型的值)
- 对象引用的副本。
Objects themselves cannot be passed to methods。当方法修改基元类型参数时,对参数的更改对调用方法中的原始参数值没有影响。
对于引用类型参数也是如此。如果修改引用类型参数使其引用另一个对象,则只有该参数引用新对象。存储在调用方变量中的引用仍然引用原始对象。
参考文献:Java?如何编程(早期对象),第十版
Java是按值传递的。
这个问题已经有了很好的答案。不知怎么地,我从来就不清楚传递值/引用是关于原始数据类型还是关于对象的。因此,为了让我满意和清晰,我用下面的代码进行了测试;这可能有助于某些人寻求类似的清晰性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| 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指向同一个DummyObject。
本主题答案之一(https://stackoverflow.com/a/12429953/4233180)中的插图有助于加深理解。
- 首先,你说"Java是按值传递的",然后你说"……",否则改变是反映出来的,因为修改只在对象的"引用"上进行。
最短答案:)
- Java具有传递值(并通过值传递引用)。
- C也有旁路参考
在C中,这是通过"out"和"ref"关键字完成的。
传递引用:以这样的方式传递变量:方法内部的重新分配即使在方法外部也会反映出来。
下面是一个按引用传递的示例(c)。这个特性在Java中不存在。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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-library(c):按引用和out传递数组
另请参见:msdn-library(c):通过值和引用传递
- 在Java中,我们可以做到:EDCOX1,0,假设我们有:EDOCX1,1
- 你说得对。对于一个简单的传递引用来说,这是一个可能的替代方法。但是通过引用可以做更强大的事情。例如,它可以分配多个值:例如,假设您创建了一个方法static void InitArray(out int[] a1, out int[] a2){...},则为int[] array1; int[] array2; InnitArrays(out array1, out array2);
在我看来,"传递价值"是一个可怕的方式来单独描述两个相似但不同的事件。我想他们应该先问我。
对于原语,我们将把原语的实际值传递给方法(或构造函数),不管它是整数"5"、字符"c"还是您所拥有的。然后,这个实际值就变成了它自己的本地原语。但是对于对象,我们所做的就是给同一个对象一个附加的引用(本地引用),这样我们现在就有两个引用指向同一个对象。
我希望这个简单的解释能有所帮助。
- "传递值"是计算机科学中的一个标准术语,自20世纪50年代以来就一直存在。现在没有必要抱怨它。
我试图简化上面的例子,只保留问题的本质。让我以一个易于记忆和正确应用的故事来介绍这一点。故事是这样的:你有一只宠物狗,吉米,它的尾巴有12英寸长。当你出国旅行时,你把它留给兽医几周。
兽医不喜欢吉米的长尾巴,所以他想把它切成两半。但是作为一个好兽医,他知道他没有权利残害别人的狗。因此,他首先克隆了狗(用新的关键字),并割断了克隆人的尾巴。当狗最终回到你身边时,它机智地拥有原来12英寸的尾巴。美满结局!
下次旅行时,你会不知不觉地把狗带到一个邪恶的兽医那里。他也是一个讨厌长尾巴的人,所以他把它减到可怜的2英寸。但他这样做是为了你亲爱的吉米,而不是克隆人。当你回来的时候,看到吉米可怜地摇着一根2英寸长的树桩,你很震惊。
这个故事的寓意是:当你把宠物传给别人时,你会全心全意地放弃。把宠物交给兽医保管。他可以随意摆弄它。通过值、引用和指针传递都只是技术上的争论。除非兽医先克隆它,否则他最终会把原来的狗弄残。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| 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按值传递参数,但对于对象变量,这些值本质上是对对象的引用。由于数组是对象,下面的示例代码显示了不同之处。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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 |
+---+ |
已经有了很好的答案。我想通过分享一个非常简单的例子来编译一个小的例子(它将编译)对比C++中的逐个引用和Java中的逐值行为。
几点:
术语引用是重载的。在Java中,它只是指一个指针。在术语pass by reference中,它是指对传入的原始变量的引用。
Java是通过值传递的,但是允许我们通过传递一个Java引用(即指针)作为值来引用PASS。这意味着它传递了一个Java引用的副本。
C++通过使用"&"字符来声明引用参数(它恰好是用于指示C和C++中的变量的地址)的相同字符,从而允许传递引用。例如,如果我们传入一个指针,参数和参数不仅仅指向同一个对象,而是指向同一个变量。如果一个被设置为不同的地址或为空,另一个也被设置为空。
在下面的C++示例中,我通过引用传递一个指向空终止字符串的指针。在下面的Java示例中,我通过值将Java引用传递给字符串(同样,与字符串的指针相同)。注意注释中的输出。
C++参考例子:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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 PASS"Java引用"的价值示例
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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
}
} |
- Java不允许我们"仿效通过引用"。一个用户不能接受答案并不是整个网站的巨大弱点。
- 解释你所说的"Java不允许我们效仿"的意思。至于弱点,这是意见分歧。
- 老实说,你可以简单地说Java只为原始类型传递值,从而简化了这个答案。从对象继承的所有内容都是有效的按引用传递的,其中引用是您传递的指针。
- @斯库巴·史蒂夫,如果这就是你从我的岗位上得到的,我完全没能传达我的信息。Java中的所有东西都是通过值传递的,永远不会通过引用。传递对象时,实际上是传递对该对象的引用,但引用是按值传递的(即传递引用的副本)。同样,这不是我个人的观点,而是按定义传递的。
- 这对任何语言的参考都是正确的…除非显式地传递一个引用引用,一般来说,这样做没有什么意义。
- 情况并非如此。这在C、Java、JavaScript中是正确的,但在PASCAL中是不正确的(它具有与参考参数相同的"可变参数")和C++(请参阅我所发布的示例)
- 传递引用语言的其他示例包括C、PHP、VB.NET和Fortran。
- 我认为C++没有"传递引用"这个词。在Java中,说"传递引用"是很常见的,因为当你传递一个对象时,你不能复制对象,就像C++一样。它是指向内存中某个位置的指针,但是是一个新的指针。在C++中,它是指向指针的指针…所以,我更喜欢使用其他术语,而不是假设C++是每个人都必须遵循和理解的标准。
- JuanMendes,我只是用C++作为例子,我可以给你其他语言的例子。"传递引用"一词存在于C++存在之前很久。这是一个教科书术语,有着非常具体的定义。根据定义,Java不是通过引用传递的。如果你愿意的话,你可以继续用这个词,但是你的用法与教科书的定义不一致。它不仅仅是指向指针的指针。它是语言提供的允许"通过引用"的结构。请仔细看看我的例子,但请不要相信我的话,自己去查。
斯科特·斯坦奇菲尔德先生写了一个很好的答案。这是您要验证的类正是他的意思:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| 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。
围绕这个问题的许多困惑来自Java试图重新定义"按值传递"和"通过引用"这一事实。重要的是要理解这些是行业术语,不能在该上下文之外正确理解。它们是用来帮助您编写代码的,理解起来很有价值,所以让我们先看看它们的含义。
在这里可以找到对这两者的很好描述。
传递值函数接收到的值是调用方正在使用的对象的副本。它对于函数是完全唯一的,您对该对象所做的任何操作都只能在函数中看到。
pass by reference函数接收到的值是对调用方使用的对象的引用。函数对该值引用的对象所做的任何操作都将被调用方看到,并且它将从该点开始处理这些更改。
从这些定义中可以清楚地看到,引用是通过值传递的这一事实是不相关的。如果我们接受这个定义,那么这些术语就变得毫无意义了,所有的语言都只是传递值而已。
无论您如何传递引用,它都只能通过值传递。这不是重点。重点是您将对自己的对象的引用传递给了函数,而不是它的副本。你可以扔掉你收到的推荐信这一事实无关紧要。同样,如果我们接受了这个定义,这些术语就变得毫无意义了,而且每个人都在传递价值。
而且,C++的特殊"按引用"语法不是传递引用的唯一定义。它纯粹是一种方便的语法,用于在传入指针后不需要使用指针语法。它仍在传递一个指针,编译器只是在向您隐藏这个事实。它仍然按值传递指针,编译器只是对您隐瞒了这一点。
因此,通过这种理解,我们可以看到Java并看到它实际上都有。所有Java原语类型都是按值传递的,因为您接收到调用方对象的副本,不能修改它们的副本。所有Java引用类型都是按引用传递的,因为您接收到对调用方对象的引用,并且可以直接修改它们的对象。
不能修改调用方的引用这一事实与传递引用无关,并且在支持传递引用的每种语言中都是正确的。
- Java没有重新定义这些术语。没有人。它只是避免了C术语"指针"。
- 这些术语早在Java或C指针之前就已经存在,它们只是实现其中的一种方法。如果你接受Java的定义,那么它们就变得毫无意义,因为通过这个定义,所有创建的语言都只是通过值。
分两步理解:
不能更改对对象本身的引用,但可以使用此传递的参数作为对对象的引用。
如果要更改引用后面的值,则只能在堆栈上声明一个同名"d"的新变量。查看带有标志@的引用,您会发现引用已更改。
1 2 3 4 5 6 7 8 9 10 11 12
| 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
} |
Java是一直通备用副本的参数值,通过银行有什么对象定义的变量,都有使用和参考参考,是可变的,记忆的存储器地址的对象是在记忆的地方。 >
检查评论对理解什么会发生在我跟他们的执行;数字节目流的执行。 > 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class Example
{
public static void test (Cat ref )
{
// 3 - <ref> is a copy of the reference
// 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());
}
} |
- "传递其内部值"毫无意义。
- @ejp谢谢你的留言,请原谅我从2013年开始的糟糕英语,我已经编辑了整个事情,如果你看到更好的措辞,你可以建议或编辑
- 我想添加一件事,如果您更改了cat的名称而不是创建一个新的名称,那么即使方法返回后,它也会反映在内存中。
Java通过引用操作对象,所有对象变量都是引用。但是,Java不通过引用传递方法参数,而是通过值传递它们。
以badswap()方法为例:
1 2 3
| public void badSwap(int var1, int
var2{ int temp = var1; var1 = var2; var2 =
temp; } |
当badswap()返回时,作为参数传递的变量仍将保留其原始值。如果我们将参数类型从.nbsp和nbsp转换为nbsp对象,方法也会失败,因为Java也通过值传递对象引用。现在,这是一个棘手的问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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只不过是对象引用。当您使用PASSPNT1&Nbsp和NPbT2&No.BaseType()和Nbsp方法时,Java就像其他任何参数一样通过值传递引用。这意味着传递给方法的引用实际上是原始引用的副本。下面的图1显示了Java将对象传递给一个方法后指向同一个对象的两个引用。
Java复制并通过值传递引用和引用,而不是对象。因此,方法操作将改变对象,因为引用指向原始对象。但由于这些参考是复制品,交换将失败。如图2所示,方法引用交换,而不是原始引用。不幸的是,在方法调用之后,只剩下未映射的原始引用。要使交换在方法调用之外成功,我们需要交换原始引用,而不是副本。
Java通过值传递所有东西!!
//通过传入名称和年龄来创建对象:
1 2 3
| PersonClass variable1 = new PersonClass("Mary", 32);
PersonClass variable2; |
//variable2和variable1现在都引用同一对象
1 2 3 4
| variable2 = variable1;
PersonClass variable3 = new PersonClass("Andre", 45); |
//variable1现在指向variable3
//这个函数的输出是什么?
1 2 3 4 5
| System. out. println(variable2 );
System. out. println(variable1 );
Mary 32
Andre 45 |
如果你能理解这个例子,我们就完成了。否则,请访问本网页了解详细说明:
网页
- 这实际上并不能解释任何关于Java的REF/VAL属性的潜力。
在Java中,一切都是按价值调用的,正如我试图通过下面的程序理解的那样
类S
1 2 3 4 5
| class S {
String name ="alam";
public void setName (String n ){
this. name=n ;
}} |
类样本
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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);
}} |
产量
alam
alam
taleev
alam
taleev
harry
正如我们已经用实例变量名和值taleev定义了类S一样,我们从它初始化的所有对象都将具有值为taleev的名称变量,但是如果我们更改任何对象的名称值,那么它只更改该类(对象)副本的名称,而不是每个类的名称,因此之后,当我们执行system.out.println(s.name)时,它只打印taleev,我们不能更改名称的值t我们最初定义的值是对象的值,而不是实例变量的值,因此一旦定义了实例变量,我们就无法更改它。
所以我认为这就是Java只处理参考值而不是引用的方式。
基本变量的内存分配可以理解为
Java仅通过值传递。例如,没有传递引用,您可以看到下面的示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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);
}
} |
输出:
1 2 3
| before change 50
after add 600
after change 50 |
- 请澄清原语是传递值,而对象是通过引用传递的。考虑这样一种方法:void changeName(Person person, String newName){ person.name = newName; }确实修改了原始对象。如果这是真的传递值,我们不希望给定的person参数是原始对象的副本吗?
- @尽管对它们的操作表现类似于按引用传递,但仍按值传递snobof thegolfclub对象。考虑到void changePerson(Person person){ person = new Person(); },调用方对Person对象的引用将保持不变。对象本身按值传递,但其成员可能会受到更改的影响。为了实现引用传递,我们必须能够将参数重新分配给新对象,并将更改反映在调用方中。
在Java中有一个用于引用的解决方案。让我用这个例子来解释一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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)
}} |
希望这有帮助!
传递值的底线:被调用方法无法更改调用方的变量,尽管对于对象引用变量,被调用的方法可以更改对象引用的变量。改变变量有什么不同改变目标?对于对象引用,这意味着被调用的方法不能重新分配调用方的原始引用变量并使其引用其他对象,或为空。
我从Java认证的书中获取了这个代码和解释,并做了一些小的改动。我想这是一个很好地说明了对象的传递值。在下面的代码中,重新分配G不会重新分配F!在bar()方法的末尾,两个foo对象已创建,一个由局部变量f引用,另一个由局部(参数)变量g。
因为dostuff()方法有一个引用变量的副本,所以它有一种方法到原始foo对象,例如调用setname()方法。但是,dostuff()方法无法F引用变量。所以dostuff()可以更改对象f引用的值to,但dostuff()不能更改其他函数中f.的实际内容(位模式)words,dostuff()可以更改f引用的对象的状态,但它不能使F引用另一个对象!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| 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:test.abc.foo@62f72617的对象引用
Java通过引用操作对象,所有对象变量都是引用。但是,Java不通过引用传递方法参数,而是通过值传递它们。
根据C++术语:
基本类型及其包装器-传递值
其他复杂数据类型-按引用传递
(虽然Java完全通过值,但在第二种情况下,它将引用传递给对象,在这种情况下,如果改变了对象的值,则反映在main函数中,因此我称之为C++引用的传递引用。)如果你从C++中取笑,那么Java对于原始类型和它们的包装类(如int、整数、布尔、布尔)都是按值传递的,也就是说,如果传递这些数据类型的值,原始函数就不会有变化。对于所有其他类型的数据类型,Java只是通过它们,如果进行了任何更改,则可以在原始函数中看到更改(可以根据C++术语称为"按引用传递")。
- 请为此提供一个明确的参考,即一个特定的C++官方规范的特定部分的引用。(如果你用C++特性模拟Java参数的行为,我怀疑这就是你如何描述C++中的表达式。但这并不是重点。我们在这里讨论的是语言模型,众所周知,Java和C++各自的语言模型是非常不同的。
Java是按值传递而不是通过引用传递的。Java编程语言中最大的困惑之一是Java是按值传递还是通过引用传递。
首先,我们应该理解什么是旁路值或旁路参考。
传递值:将方法参数值复制到另一个变量,然后传递复制的对象,这就是它被称为传递值的原因。
传递引用:将对实际参数的别名或引用传递给方法,这就是它被称为传递引用的原因。
假设我们有一个像下面这样的class Balloon。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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 ;
}
} |
我们有一个简单的程序,用一个通用方法来处理swap两个objects,class如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class Test {
public static void main (String[] args ) {
Balloon red = new Balloon ("Red");
Balloon blue = new Balloon ("Blue");
swap (red, blue );
System. out. println("red color="+red. getColor());
System. out. println("blue color="+blue. getColor());
foo (blue );
System. out. println("blue color="+blue. getColor());
}
private static void foo (Balloon balloon ) {
balloon. setColor("Red");
balloon = new Balloon ("Green");
balloon. setColor("Blue");
}
//Generic swap method
public static void swap (Object o1, Object o2 ){
Object temp = o1 ;
o1 =o2 ;
o2 =temp ;
}
} |
当我们执行上述程序时,我们得到如下输出。
1 2 3
| red color=Red
blue color=Blue
blue color=Red |
如果你看一下output的前两行,很明显swap方法不起作用。这是因为Java是pass by value,这个swap()方法测试可以与任何编程语言一起使用,以检查它是pass by value还是pass by reference。
初学者的简单答案如果您传递一个复杂的对象(就像一只狗一样,显然这是一个常见的例子),它与传递引用是相同的。如果传递复杂对象的字段(如只传递狗的名称),则与传递值相同(更改该值不会更改该字段的狗/父级)。
请注意,不"复杂"(不象我们的狗)的对象(如整数或字符串)是通过值传递的。因此,即使它们是对象,并且您可以对它们做任何操作,它们也不能在方法内修改…是的,这是****的,没有任何意义…
示例(不是狗):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class HelloWorld {
private Integer i ;
private String s ;
private Boolean b ;
public static void main (String[] args ) {
HelloWorld h = new HelloWorld ();
h. s ="Bill";
h. ModMe(h. s);
h. i = 2;
h. ModMe(h. i);
h. b = true;
h. ModMe(h. b);
System. out. println(h. s +"" + h. i +"" + h. b);
h. ModMe(h );
System. out. println(h. s +"" + h. i +"" + h. b);
String test ="TEST";
h. ModMe(test );
System. out. println(test );
}
public void ModMe (Object o ) {
if (o instanceof HelloWorld ) {
((HelloWorld ) o ). i = (int) Math. pow(((HelloWorld ) o ). i, 2);
((HelloWorld ) o ). b = !((HelloWorld ) o ). b;
((HelloWorld ) o ). s = ((HelloWorld ) o ). s. concat(" modded successfully");
} else if (o instanceof Integer) {
o = (Integer) o + (Integer) o ;
} else if (o instanceof String) {
o = ((String) o ). concat(" is modified.");
} else if (o instanceof Boolean) {
o = !(Boolean) o ;
}
}
} |
如果运行该命令,则如果将整个对象用作参数,则对象h将被修改;如果仅将字段s i或b用作参数,则不会修改字段s i或b。如果进行调试,您会注意到对于字段,只要运行值更改处的行,对象的ID就会更改。因此,它将自动执行与"new integer"、"new string"和"new boolean"相同的操作。
Java编程语言仅通过值传递参数,也就是说,不能从内部更改调用方法中的参数值调用的方法。
但是,当对象实例作为参数传递给方法时,参数的值不是对象本身,而是对对象。您可以在被调用的方法中更改对象的内容,但是不是对象引用。
对许多人来说,这看起来像是一个参照物,从行为上来说,它有与传递引用有很多相同之处。然而,有两个原因这是不准确的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| The following code example illustrates this point :
1 public class PassTest {
2
3 // Methods to change the current values
4 public static void changeInt (int value ) {
5 value = 55;
6 }
7 public static void changeObjectRef (MyDate ref ) {
8 ref = new MyDate (1, 1, 2000);
9 }
10 public static void changeObjectAttr (MyDate ref ) {
11 ref. setDay(4);
12 }
13
14 public static void main (String args []) {
15 MyDate date ;
16 int val ;
17
18 // Assign the int
19 val = 11;
20 // Try to change it
21 changeInt (val );
22 // What is the current value?
23 System. out. println("Int value is:" + val );
24
25 // Assign the date
26 date = new MyDate (22, 7, 1964);
27 // Try to change it
28 changeObjectRef (date );
29 // What is the current value?
30 System. out. println("MyDate:" + date );
31
32 // Now change the day attribute
33 // through the object reference
34 changeObjectAttr (date );
35 // What is the current value?
36 System. out. println("MyDate:" + date );
37 }
38 } |
1 2 3 4 5 6 7 8
| This code outputs the following:
java PassTest
Int value is: 11
MyDate: 22-7-1964
MyDate: 4-7-1964
The MyDate object is not changed by the changeObjectRef method;
however, the changeObjectAttr method changes the day attribute of the
MyDate object. |
- 这是非常误导人的。您当然可以从方法中更改参数的值。
Java通过值传递对对象的引用。
因此,如果对引用参数指向的对象进行了任何修改,它将被反射回原始对象。
但如果引用参数指向另一个对象,则原始引用仍将指向原始对象。
tl;dr:最小示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package foobar;
import java.util.ArrayList;
public class FooBar {
public static void main (String[] args ) {
ArrayList <Integer > list1 = new ArrayList <>(); // An object.
list1. add(1);
ArrayList <Integer > list2 = new ArrayList <>(); // Another object.
list2. add(2);
int x = 42; // A primitive (same with Integer).
sideEffects (list1, list2, x );
System. out. println(list1 ); // Output: [1] (unchanged)
System. out. println(list2 ); // Output: [2, 3] (changed !)
System. out. println(x ); // Output: 42 (not changed)
}
private static void sideEffects (ArrayList <Integer > list1, ArrayList <Integer > list2, int x ) {
list1 = list2 ;
list1. add(3);
x = 21;
}
} |
很简单!如果变量是基元,则按值传递;如果变量是对象,则按引用传递。
这就是全部
- 这是错误的。由于大量的注释和答案解释,它是混淆的,因为在Java对象中使用引用。但是,短语"pass by reference"的意思是特定的。pass-by-value"意味着要复制方法的内容——一个全新的内容。""通过引用"意味着指向方法的东西在两个地方共享。Java总是总是"按值传递"。当你发送一个对象时,它复制了引用,但这并没有使它"通过引用"。
- 你的回答有些道理。我不知道为什么人们对这个问题投了反对票。