use*_*315 6270 java methods parameter-passing pass-by-reference pass-by-value
我一直认为Java是传递引用的.
但是,我看过一些博客文章(例如,这个博客)声称它不是.
我不认为我理解他们所做的区别.
解释是什么?
erl*_*ndo 5586
Java始终是按值传递的.不幸的是,当我们传递一个对象的值时,我们将引用传递给它.这对初学者来说很困惑.
它是这样的:
public static void main(String[] args) {
Dog aDog = new Dog("Max");
Dog oldDog = aDog;
// we pass the object to foo
foo(aDog);
// aDog variable is still pointing to the "Max" dog when foo(...) returns
aDog.getName().equals("Max"); // true
aDog.getName().equals("Fifi"); // false
aDog == oldDog; // true
}
public static void foo(Dog d) {
d.getName().equals("Max"); // true
// change d inside of foo() to point to a new Dog instance "Fifi"
d = new Dog("Fifi");
d.getName().equals("Fifi"); // true
}
Run Code Online (Sandbox Code Playgroud)
在上面的例子中aDog.getName()仍将返回"Max".值aDog内main未在功能改变foo与Dog "Fifi"作为对象基准由值来传递.如果它是通过引用传递的,则aDog.getName()in main将"Fifi"在调用之后返回foo.
同样:
public static void main(String[] args) {
Dog aDog = new Dog("Max");
Dog oldDog = aDog;
foo(aDog);
// when foo(...) returns, the name of the dog has been changed to "Fifi"
aDog.getName().equals("Fifi"); // true
// but it is still the same dog:
aDog == oldDog; // true
}
public static void foo(Dog d) {
d.getName().equals("Max"); // true
// this changes the name of d to be "Fifi"
d.setName("Fifi");
}
Run Code Online (Sandbox Code Playgroud)
在上面的例子中,Fifi是调用后的狗的名字,foo(aDog)因为对象的名字是在里面设置的foo(...).任何操作foo执行上d是这样的,对于所有的实际目的,它们被执行的aDog,但它是不是可以改变变量的值aDog本身.
Sco*_*eld 2944
我刚刚注意到你引用了我的文章.
Java规范说Java中的所有内容都是按值传递的.在Java中没有"pass-by-reference"这样的东西.
理解这一点的关键是类似的东西
Dog myDog;
Run Code Online (Sandbox Code Playgroud)
是不是狗; 它实际上是指向狗的指针.
这意味着,就在你拥有的时候
Dog myDog = new Dog("Rover");
foo(myDog);
Run Code Online (Sandbox Code Playgroud)
你实际上是将创建的对象的地址传递Dog给foo方法.
(我说的主要是因为Java指针不是直接地址,但最容易想到它们)
假设Dog对象驻留在内存地址42.这意味着我们将42传递给该方法.
如果方法被定义为
public void foo(Dog someDog) {
someDog.setName("Max"); // AAA
someDog = new Dog("Fifi"); // BBB
someDog.setName("Rowlf"); // CCC
}
Run Code Online (Sandbox Code Playgroud)
让我们来看看发生了什么.
someDog设置为值42someDog跟随Dog它指向(Dog地址42处的对象)Dog(地址为42的那个)将他的名字改为MaxDog创建了一个新的.让我们说他在地址74someDog给74Dog它指向(Dog地址74处的对象)Dog(地址为74的那个)将他的名字改为Rowlf现在让我们考虑一下方法之外会发生什么:
没有myDog改变?
关键是.
记住这myDog是一个指针,而不是一个实际的Dog,答案是否定的.myDog仍然有42的价值; 它仍然指向原始Dog(但请注意,因为行"AAA",它的名字现在是"Max" - 仍然是相同的狗;它myDog的值没有改变.)
按照地址并改变最后的内容是完全有效的; 但是,这不会改变变量.
Java的工作方式与C完全相同.您可以分配指针,将指针传递给方法,按照方法中的指针操作并更改指向的数据.但是,您无法更改指针指向的位置.
在C++,Ada,Pascal和其他支持pass-by-reference的语言中,您实际上可以更改传递的变量.
如果Java具有pass-by-reference语义,那么foo我们在上面定义的方法会在BBB上myDog分配的位置发生变化someDog.
将引用参数视为传入的变量的别名.当分配该别名时,传入的变量也是如此.
Eng*_*uad 1652
Java总是按值而不是通过引用传递参数.
让我通过一个例子解释一下:
public class Main{
public static void main(String[] args){
Foo f = new Foo("f");
changeReference(f); // It won't change the reference!
modifyReference(f); // It will modify the object that the reference variable "f" refers to!
}
public static void changeReference(Foo a){
Foo b = new Foo("b");
a = b;
}
public static void modifyReference(Foo c){
c.setAttribute("c");
}
}
Run Code Online (Sandbox Code Playgroud)
我将逐步解释这个:
声明一个名为ftype 的引用,Foo并为其赋予一个Foo带有属性的新对象类型"f".
Foo f = new Foo("f");
Run Code Online (Sandbox Code Playgroud)

从方法方面,声明Foo具有名称的类型的引用,a并且最初分配它null.
public static void changeReference(Foo a)
Run Code Online (Sandbox Code Playgroud)

在调用方法时changeReference,a将为引用分配作为参数传递的对象.
changeReference(f);
Run Code Online (Sandbox Code Playgroud)

声明一个名为btype 的引用,Foo并为其赋予一个Foo带有属性的新对象类型"b".
Foo b = new Foo("b");
Run Code Online (Sandbox Code Playgroud)

a = b对引用进行新的赋值a,而不是 f对其属性为的对象进行赋值"b".

当您调用modifyReference(Foo c)方法时,将c创建一个引用并为该对象分配属性"f".

c.setAttribute("c");将更改引用c指向它的对象的属性,并且它与引用f指向它的对象相同.

我希望你现在明白如何将对象作为参数传递在Java中:)
Mar*_*ace 715
这将为您提供一些有关Java如何工作的见解,以至于在您下次讨论Java通过引用传递或通过值传递时,您只需微笑:-)
第一步请从脑海中删除以'p'"_ _ _ _ _ _ _"开头的单词,特别是如果您来自其他编程语言.Java和'p'不能写在同一本书,论坛,甚至txt中.
第二步记住,当你将一个Object传递给一个方法时,你传递的是Object引用,而不是Object本身.
现在想想Object的引用/变量是什么/是什么:
在下面(请不要尝试编译/执行此...):
1. Person person;
2. person = new Person("Tom");
3. changeName(person);
4.
5. //I didn't use Person person below as an argument to be nice
6. static void changeName(Person anotherReferenceToTheSamePersonObject) {
7. anotherReferenceToTheSamePersonObject.setName("Jerry");
8. }
Run Code Online (Sandbox Code Playgroud)
怎么了?
一张图片胜过千言万语:

请注意,anotherReferenceToTheSamePersonObject箭头指向Object而不是指向变量person!
如果你没有得到它,那么只要相信我,并记住最好说Java是通过值传递的.那么,通过参考值传递.哦,更好的是传递变量值的副本!;)
现在可以随意讨厌我,但请注意,在讨论方法参数时,传递原始数据类型和对象之间没有区别.
你总是传递一份参考值的副本!
Java是按值传递的,因为在方法中你可以根据需要修改引用的Object,但无论你怎么努力,你都永远无法修改将继续引用的传递变量(不是p _ _ _ _ _ _ _)同样的对象无论如何!
上面的changeName函数永远不能修改传递的引用的实际内容(位值).换句话说,changeName不能使Person人引用另一个Object.
当然,你可以缩短它,只是说 Java是值得传递的!
SCd*_*CdF 656
Java的始终是按值传递,没有例外,永远.
那么如何让任何人都对此感到困惑,并相信Java是通过引用传递的,或者认为他们有一个Java作为参考传递的例子?关键是Java 在任何情况下都不会直接访问对象本身的值.对对象的唯一访问是通过对该对象的引用.因为Java对象总是通过引用访问,而不是直接访问,所以通常将字段和变量以及方法参数作为对象进行讨论,而当它们只是对对象的引用时.这种混淆源于这种(严格来说,不正确的)命名法的变化.
所以,在调用方法时
int,long等),pass by value是基元的实际值(例如,3).所以,如果你有doSomething(foo)和public void doSomething(Foo foo) { .. }两个FOOS已复制引用指向同一个对象.
当然,通过值传递对对象的引用看起来非常像(并且在实践中无法区分)通过引用传递对象.
ScA*_*er2 314
Java按值传递引用.
因此,您无法更改传入的引用.
cut*_*eth 227
我觉得争论"传递参考与传递价值"并不是非常有用.
如果你说,"Java是通过任何东西(参考/价值)",在任何一种情况下,你都没有提供完整的答案.这里有一些额外的信息,有助于理解记忆中发生的事情.
在我们进入Java实现之前,堆栈/堆上的崩溃过程:值以一种非常有序的方式在堆栈中上下移动,就像在自助餐厅的堆栈一样.堆中的内存(也称为动态内存)是杂乱无章的.JVM只是在任何地方找到空间,并释放它,因为不再需要使用它的变量.
好的.首先,本地原语进入堆栈.所以这段代码:
int x = 3;
float y = 101.1f;
boolean amIAwesome = true;
Run Code Online (Sandbox Code Playgroud)
结果如下:

声明和实例化对象时.实际的对象在堆上.什么在堆栈上?堆上对象的地址.C++程序员会将其称为指针,但是一些Java开发人员反对"指针"这个词.随你.只要知道对象的地址就在堆栈上.
像这样:
int problems = 99;
String name = "Jay-Z";
Run Code Online (Sandbox Code Playgroud)

数组是一个对象,所以它也在堆上.那么阵列中的对象呢?它们获得自己的堆空间,每个对象的地址都在数组内部.
JButton[] marxBros = new JButton[3];
marxBros[0] = new JButton("Groucho");
marxBros[1] = new JButton("Zeppo");
marxBros[2] = new JButton("Harpo");
Run Code Online (Sandbox Code Playgroud)

那么,当你调用一个方法时会传入什么?如果传入一个对象,那么实际传入的是对象的地址.有些人可能会说地址的"价值",有些人说它只是对象的引用.这是"参考"和"价值"支持者之间圣战的起源.你所说的并不像你理解的那样重要,传入的是对象的地址.
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);
}
Run Code Online (Sandbox Code Playgroud)
创建一个String,并在堆中分配空间,并将字符串的地址存储在堆栈中并给出标识符hisName,因为第二个String的地址与第一个String的地址相同,没有创建新的String,没有分配新的堆空间,但是在堆栈上创建了新的标识符.然后我们调用shout():创建一个新的堆栈帧并创建一个新的标识符,name并为其分配已存在的String的地址.

那么,价值,参考?你说"土豆".
Ecl*_*pse 183
在C++中:注意:错误的代码 - 内存泄漏! 但它证明了这一点.
void cppMethod(int val, int &ref, Dog obj, Dog &objRef, Dog *objPtr, Dog *&objPtrRef)
{
val = 7; // Modifies the copy
ref = 7; // Modifies the original variable
obj.SetName("obj"); // Modifies the copy of Dog passed
objRef.SetName("objRef"); // Modifies the original Dog passed
objPtr->SetName("objPtr"); // Modifies the original Dog pointed to
// by the copy of the pointer passed.
objPtr = new Dog("newObjPtr"); // Modifies the copy of the pointer,
// leaving the original object alone.
objPtrRef->SetName("objRefPtr"); // Modifies the original Dog pointed to
// by the original pointer passed.
objPtrRef = new Dog("newObjPtrRef"); // Modifies the original pointer passed
}
int main()
{
int a = 0;
int b = 0;
Dog d0 = Dog("d0");
Dog d1 = Dog("d1");
Dog *d2 = new Dog("d2");
Dog *d3 = new Dog("d3");
cppMethod(a, b, d0, d1, d2, d3);
// a is still set to 0
// b is now set to 7
// d0 still have name "d0"
// d1 now has name "objRef"
// d2 now has name "objPtr"
// d3 now has name "newObjPtrRef"
}
Run Code Online (Sandbox Code Playgroud)
在Java中
public static void javaMethod(int val, Dog objPtr)
{
val = 7; // Modifies the copy
objPtr.SetName("objPtr") // Modifies the original Dog pointed to
// by the copy of the pointer passed.
objPtr = new Dog("newObjPtr"); // Modifies the copy of the pointer,
// leaving the original object alone.
}
public static void main()
{
int a = 0;
Dog d0 = new Dog("d0");
javaMethod(a, d0);
// a is still set to 0
// d0 now has name "objPtr"
}
Run Code Online (Sandbox Code Playgroud)
Java只有两种类型的传递:内置类型的值,以及对象类型的指针值.
Joh*_*ing 162
Java按值传递对象的引用.
Han*_*Gay 158
基本上,重新分配Object参数不会影响参数,例如,
private void foo(Object bar) {
bar = null;
}
public static void main(String[] args) {
String baz = "Hah!";
foo(baz);
System.out.println(baz);
}
Run Code Online (Sandbox Code Playgroud)
将打印出"Hah!"而不是null.这个工作的原因是因为bar是一个值的副本baz,它只是一个引用"Hah!".如果它本身就是实际参考,那么foo将重新定义baz为null.
Jör*_*tag 147
我无法相信没人提到Barbara Liskov.当她在1974年设计的CLU,她就遇到了这个相同的术语问题,她发明了长期通过共享呼叫(也称为按对象共享呼叫和呼叫的对象的值)调用的"这种特殊情况下,该值为参考".
Jac*_*esB 112
的问题的关键是,字参考在表述"通过引用传递"是指某物从字的通常含义完全不同的参考在Java中.
通常在Java 引用中表示对对象的引用.但是,编程语言理论中通过引用/值传递的技术术语正在讨论对存储变量的存储器单元的引用,这是完全不同的.
Srl*_*rle 82
在java中,所有内容都是引用,所以当你有类似的东西时:
Point pnt1 = new Point(0,0);Java确实如下:

Java不通过引用传递方法参数; 它按值传递它们.我将使用此站点中的示例:
public static void tricky(Point arg1, Point arg2) {
arg1.x = 100;
arg1.y = 100;
Point temp = arg1;
arg1 = arg2;
arg2 = temp;
}
public static void main(String [] args) {
Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);
System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y);
System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
System.out.println(" ");
tricky(pnt1,pnt2);
System.out.println("X1: " + pnt1.x + " Y1:" + pnt1.y);
System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
}
Run Code Online (Sandbox Code Playgroud)
程序流程:
Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);
Run Code Online (Sandbox Code Playgroud)
使用两个不同的引用关联创建两个不同的Point对象.

System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y);
System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
System.out.println(" ");
Run Code Online (Sandbox Code Playgroud)
正如预期的输出将是:
X1: 0 Y1: 0
X2: 0 Y2: 0
Run Code Online (Sandbox Code Playgroud)
在这条线上'传递价值'进入游戏......
tricky(pnt1,pnt2); public void tricky(Point arg1, Point arg2);
Run Code Online (Sandbox Code Playgroud)
参考文献pnt1和pnt2被按值传递给棘手的方法,这意味着现在你参考pnt1,并pnt2有自己的copies命名arg1和arg2.所以pnt1,并arg1 点到同一个对象.(与pnt2和相同arg2)

在tricky方法中:
arg1.x = 100;
arg1.y = 100;
Run Code Online (Sandbox Code Playgroud)

接下来的tricky方法
Point temp = arg1;
arg1 = arg2;
arg2 = temp;
Run Code Online (Sandbox Code Playgroud)
在这里,您首先创建新的tempPoint引用,它将指向相同的位置,如arg1引用.然后您将参考arg1以点像同一个地方arg2的参考.最后arg2会指向同一个地方temp.

从这里的范围tricky方法走了,你没有获得任何更多的引用:arg1,arg2,temp.但重要的是,当你在生活中使用这些引用时所做的一切都将永久地影响它们所指向的对象.
所以在执行方法之后tricky,当你返回时main,你会遇到这种情况:

所以现在,完全执行程序将是:
X1: 0 Y1: 0
X2: 0 Y2: 0
X1: 100 Y1: 100
X2: 0 Y2: 0
Run Code Online (Sandbox Code Playgroud)
Gan*_*esh 79
Java总是按值传递,而不是通过引用传递
首先,我们需要了解通过值传递的内容和通过引用传递的内容.
按值传递意味着您在内存中复制传入的实际参数值.这是实际参数内容的副本.
通过引用传递(也称为按地址传递)意味着存储实际参数的地址的副本.
有时Java可以给出通过引用传递的错觉.让我们看看它是如何工作的,使用下面的例子:
public class PassByValue {
public static void main(String[] args) {
Test t = new Test();
t.name = "initialvalue";
new PassByValue().changeValue(t);
System.out.println(t.name);
}
public void changeValue(Test f) {
f.name = "changevalue";
}
}
class Test {
String name;
}
Run Code Online (Sandbox Code Playgroud)
该程序的输出是:
Run Code Online (Sandbox Code Playgroud)changevalue
让我们一步一步地理解:
Test t = new Test();
Run Code Online (Sandbox Code Playgroud)
众所周知,它将在堆中创建一个对象,并将引用值返回给t.例如,假设t的值是0x100234(我们不知道实际的JVM内部值,这只是一个例子).

new PassByValue().changeValue(t);
Run Code Online (Sandbox Code Playgroud)
将参考t传递给函数时,它不会直接传递对象测试的实际参考值,但会创建t的副本,然后将其传递给函数.由于它是通过值传递的,因此它传递变量的副本而不是它的实际引用.由于我们说t的值是0x100234,t和f都将具有相同的值,因此它们将指向同一个对象.

如果使用引用f更改函数中的任何内容,它将修改对象的现有内容.这就是我们获得输出的原因changevalue,该输出在函数中更新.
要更清楚地理解这一点,请考虑以下示例:
public class PassByValue {
public static void main(String[] args) {
Test t = new Test();
t.name = "initialvalue";
new PassByValue().changeRefence(t);
System.out.println(t.name);
}
public void changeRefence(Test f) {
f = null;
}
}
class Test {
String name;
}
Run Code Online (Sandbox Code Playgroud)
这会抛出NullPointerException吗?不,因为它只传递了引用的副本.在通过引用传递的情况下,它可能抛出一个NullPointerException,如下所示:

希望这会有所帮助.
kar*_*dog 69
获取框外视图,让我们看看Assembly或一些低级内存管理.在CPU级别,如果将任何内容写入内存或其中一个CPU寄存器,则对任何内容的引用会立即变为值.(这就是为什么指针是一个很好的定义.它是一个值,它同时具有目的).
内存中的数据有一个位置,在该位置有一个值(字节,字,等等).在Assembly中,我们有一个方便的解决方案,可以为某个位置(也就是变量)提供一个名称,但是在编译代码时,汇编器只需将Name替换为指定的位置,就像浏览器用IP地址替换域名一样.
在核心的情况下,技术上不可能在不表示任何语言的情况下将引用传递给任何语言(当它立即变为值时).
比方说,我们有一个变量富,它的位置是在内存中的第47字节,它的值是5,我们有另一个变量Ref2Foo这是在第223次内存字节,并且它的值是47.这Ref2Foo可能是一个技术性的变量,不是由程序明确创建的.如果您只查看5和47而没有任何其他信息,您将只看到两个值.如果您将它们用作参考,那么5我们必须前往:
(Name)[Location] -> [Value at the Location]
---------------------
(Ref2Foo)[223] -> 47
(Foo)[47] -> 5
Run Code Online (Sandbox Code Playgroud)
这就是跳转表的工作原理.
如果我们想用Foo的值调用方法/函数/过程,有几种可能的方法将变量传递给方法,具体取决于语言及其几种方法调用模式:
在高于某个值的每种情况下 - 现有值的副本 - 都已创建,现在由接收方法来处理它.当你在方法中写入"Foo"时,它要么从EAX读出,要么自动 解除引用,或者双重解引用,这个过程取决于语言的工作方式和/或Foo的类型.这是开发人员隐藏的,直到她绕过解除引用过程.因此,引用是表示的值,因为引用是必须处理的值(在语言级别).
现在我们已经将Foo传递给了方法:
Foo = 9)它只影响本地范围,因为您有一个值的副本.从方法内部我们甚至无法确定原始Foo所在的内存位置.Foo = 11),它可以全局更改Foo(取决于语言,即Java或类似Pascal的procedure findMin(x, y, z: integer;var m: integer);).但是,如果该语言允许您绕过取消引用过程,您可以更改47,比方说49.在那一点上,如果你读它,Foo似乎已被改变,因为你已经改变了它的本地指针.如果你要在方法(Foo = 12)中修改这个Foo,你可能会搞砸程序的执行(又名.segfault),因为你会写一个不同于预期的内存,你甚至可以修改一个注定要保存可执行文件的区域.程序和写入它将修改运行代码(Foo现在不在47).但是Foo的值47并没有全局变化,只有方法内部的变化,因为47它也是方法的副本.223在方法内修改它会产生与3.或4中相同的混乱.(一个指针,指向一个现在错误的值,它再次用作指针)但这仍然是一个本地问题,因为223被复制了.但是,如果您能够取消引用Ref2Foo(即223),达到并修改指向的值47,比如说,49它将全局影响Foo ,因为在这种情况下,方法得到了一个副本,223 但引用的47只存在一次,并且更改了对49每一个会导致Ref2Foo双解引用到一个错误的值.对无关紧要的细节进行挑剔,即使是通过引用传递的语言也会将值传递给函数,但这些函数知道它们必须将它用于解除引用目的.这个传递参考值只是程序员隐藏的,因为它实际上是无用的,术语只是通过引用传递.
严格的pass-by-value也没用,这意味着每次调用一个以数组为参数的方法时都必须复制一个100 MB的数组,因此Java不能严格按值传递.每种语言都会传递对这个庞大数组的引用(作为一个值),并且如果该数组可以在方法内部进行本地更改,或者允许该方法(如Java那样)全局修改数组,则使用写时复制机制(来自调用者的视图)和一些语言允许修改引用本身的值.
因此,简而言之,在Java自己的术语中,Java是值传递,其中值可以是:实数值或作为引用表示的值.
Him*_*ora 65
Java是一种价值呼叫
这个怎么运作
你总是传递一份参考值的副本!
如果它是原始数据类型,则这些位包含原始数据类型本身的值,这就是为什么如果我们更改方法内的header的值,那么它不会反映外部的更改.
如果它是一个像Foo foo = new Foo()这样的对象数据类型,那么在这种情况下,对象地址的副本会像文件快捷方式一样传递,假设我们在C:\ desktop中有一个文本文件abc.txt,并假设我们设置了快捷方式相同的文件,并将其放在C:\ desktop\abc-shortcut中,所以当您从C:\ desktop\abc.txt访问该文件并写入'Stack Overflow'并关闭该文件并再次从快捷方式打开该文件然后您write '是程序员学习的最大的在线社区'然后总文件更改将是'Stack Overflow是程序员学习的最大的在线社区',这意味着每次我们访问时打开文件的位置无关紧要相同的文件,这里我们可以假设Foo作为文件并假设foo存储在123hd7h(原始地址如C:\ desktop\abc.txt)地址和234jdid(复制地址如C:\ desktop\abc-shortcut实际包含内部文件的原始地址)..所以为了更好地理解制作快捷方式文件和感觉...
kuk*_*das 55
据我所知,Java只知道按值调用.这意味着对于原始数据类型,您将使用副本,对于对象,您将使用对象的引用副本.不过我觉得有一些陷阱; 例如,这不起作用:
public static void swap(StringBuffer s1, StringBuffer s2) {
StringBuffer temp = s1;
s1 = s2;
s2 = temp;
}
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer("Hello");
StringBuffer s2 = new StringBuffer("World");
swap(s1, s2);
System.out.println(s1);
System.out.println(s2);
}
Run Code Online (Sandbox Code Playgroud)
这将填充Hello World而不是World Hello,因为在交换函数中,您使用copys,它对main中的引用没有影响.但是,如果您的对象不是不可变的,您可以更改它,例如:
public static void appendWorld(StringBuffer s1) {
s1.append(" World");
}
public static void main(String[] args) {
StringBuffer s = new StringBuffer("Hello");
appendWorld(s);
System.out.println(s);
}
Run Code Online (Sandbox Code Playgroud)
这将在命令行上填充Hello World.如果将StringBuffer更改为String,它将生成Hello,因为String是不可变的.例如:
public static void appendWorld(String s){
s = s+" World";
}
public static void main(String[] args) {
String s = new String("Hello");
appendWorld(s);
System.out.println(s);
}
Run Code Online (Sandbox Code Playgroud)
但是你可以为这样的String创建一个包装器,它可以使它与字符串一起使用:
class StringWrapper {
public String value;
public StringWrapper(String value) {
this.value = value;
}
}
public static void appendWorld(StringWrapper s){
s.value = s.value +" World";
}
public static void main(String[] args) {
StringWrapper s = new StringWrapper("Hello");
appendWorld(s);
System.out.println(s.value);
}
Run Code Online (Sandbox Code Playgroud)
编辑:我相信这也是使用StringBuffer的原因,当涉及到"添加"两个字符串,因为你可以修改原始对象,你不能用像String这样的不可变对象.
spi*_*man 51
让我试着在四个例子的帮助下解释我的理解.Java是按值传递的,而不是按引用传递
/**
通过价值
在Java中,所有参数都按值传递,即调用者无法分配方法参数.
*/
例1:
public class PassByValueString {
public static void main(String[] args) {
new PassByValueString().caller();
}
public void caller() {
String value = "Nikhil";
boolean valueflag = false;
String output = method(value, valueflag);
/*
* 'output' is insignificant in this example. we are more interested in
* 'value' and 'valueflag'
*/
System.out.println("output : " + output);
System.out.println("value : " + value);
System.out.println("valueflag : " + valueflag);
}
public String method(String value, boolean valueflag) {
value = "Anand";
valueflag = true;
return "output";
}
}
Run Code Online (Sandbox Code Playgroud)
结果
output : output
value : Nikhil
valueflag : false
Run Code Online (Sandbox Code Playgroud)
例2:
/****通过价值**/
public class PassByValueNewString {
public static void main(String[] args) {
new PassByValueNewString().caller();
}
public void caller() {
String value = new String("Nikhil");
boolean valueflag = false;
String output = method(value, valueflag);
/*
* 'output' is insignificant in this example. we are more interested in
* 'value' and 'valueflag'
*/
System.out.println("output : " + output);
System.out.println("value : " + value);
System.out.println("valueflag : " + valueflag);
}
public String method(String value, boolean valueflag) {
value = "Anand";
valueflag = true;
return "output";
}
}
Run Code Online (Sandbox Code Playgroud)
结果
output : output
value : Nikhil
valueflag : false
Run Code Online (Sandbox Code Playgroud)
例3:
/**这个'通过价值传递'有一种'通过参考传递'的感觉
有些人说原始类型和'String'是'按值传递'而对象是'按引用传递'.
但是从这个例子中,我们可以理解它只是通过值传递,记住这里我们将引用作为值传递.ie:引用按值传递.这就是为什么能够改变并且在本地范围之后仍然适用的原因.但是我们不能改变原始范围之外的实际参考.这意味着下一个PassByValueObjectCase2示例.
*/
public class PassByValueObjectCase1 {
private class Student {
int id;
String name;
public Student() {
}
public Student(int id, String name) {
super();
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + "]";
}
}
public static void main(String[] args) {
new PassByValueObjectCase1().caller();
}
public void caller() {
Student student = new Student(10, "Nikhil");
String output = method(student);
/*
* 'output' is insignificant in this example. we are more interested in
* 'student'
*/
System.out.println("output : " + output);
System.out.println("student : " + student);
}
public String method(Student student) {
student.setName("Anand");
return "output";
}
}
Run Code Online (Sandbox Code Playgroud)
结果
output : output
student : Student [id=10, name=Anand]
Run Code Online (Sandbox Code Playgroud)
例4:
/**
除了Example3(PassByValueObjectCase1.java)中提到的内容之外,我们无法更改原始范围之外的实际引用."
注意:我没有粘贴代码private class Student.类定义Student与Example3相同.
*/
public class PassByValueObjectCase2 {
public static void main(String[] args) {
new PassByValueObjectCase2().caller();
}
public void caller() {
// student has the actual reference to a Student object created
// can we change this actual reference outside the local scope? Let's see
Student student = new Student(10, "Nikhil");
String output = method(student);
/*
* 'output' is insignificant in this example. we are more interested in
* 'student'
*/
System.out.println("output : " + output);
System.out.println("student : " + student); // Will it print Nikhil or Anand?
}
public String method(Student student) {
student = new Student(20, "Anand");
return "output";
}
}
Run Code Online (Sandbox Code Playgroud)
结果
output : output
student : Student [id=10, name=Nikhil]
Run Code Online (Sandbox Code Playgroud)
San*_*eev 51
已经有很好的答案可以解决这个问题。我想通过分享一个非常简单的示例(将进行编译)做出一点贡献,以比较 c ++中的“按引用传递”和Java中的“按值传递”之间的行为。
几点:
C ++通过参考示例:
using namespace std;
#include <iostream>
void change (char *&str){ // the '&' makes this a reference parameter
str = NULL;
}
int main()
{
char *str = "not Null";
change(str);
cout<<"str is " << str; // ==>str is <null>
}
Run Code Online (Sandbox Code Playgroud)
Java通过值示例传递“ Java参考”
public class ValueDemo{
public void change (String str){
str = null;
}
public static void main(String []args){
ValueDemo vd = new ValueDemo();
String str = "not null";
vd.change(str);
System.out.println("str is " + str); // ==> str is not null!!
// Note that if "str" was
// passed-by-reference, it
// WOULD BE NULL after the
// call to change().
}
}
Run Code Online (Sandbox Code Playgroud)
编辑
一些人发表了评论,似乎表明他们不是在看我的例子,还是没有得到c ++例子。不知道断开连接在哪里,但是不清楚c ++示例。我在pascal中发布了相同的示例,因为我认为通过引用传递在pascal中看起来更干净,但是我可能是错的。我可能只会让人们更加困惑;我希望不是。
在pascal中,通过引用传递的参数称为“ var参数”。在下面的过程setToNil中,请注意在参数“ ptr”之前的关键字“ var”。当指针传递给该过程时,它将通过reference传递。请注意行为:当此过程将ptr设置为nil(pascal表示NULL)时,它将把参数设置为nil -在Java中不能这样做。
program passByRefDemo;
type
iptr = ^integer;
var
ptr: iptr;
procedure setToNil(var ptr : iptr);
begin
ptr := nil;
end;
begin
new(ptr);
ptr^ := 10;
setToNil(ptr);
if (ptr = nil) then
writeln('ptr seems to be nil'); { ptr should be nil, so this line will run. }
end.
Run Code Online (Sandbox Code Playgroud)
编辑2
Ken Arnold,James Gosling(发明Java的人)和David Holmes的“ THE Java Programming Language”摘录,第2章,第2.6.5节
方法的所有参数都“按值”传递。换句话说,方法中参数变量的值是指定为参数的调用程序的副本。
他继续就物体提出了相同的观点。。。
您应该注意,当参数是对象引用时,是“按值”传递的是对象引用而不是对象本身。
在同一部分的末尾,他对java仅通过值传递而从不传递引用进行了更广泛的说明。
Java编程语言不会通过引用传递对象。它 按值传递对象引用。由于相同引用的两个副本引用了相同的实际对象,因此通过一个引用变量进行的更改将通过另一个变量可见。仅有一个参数传递模式- 按值传递- 有助于简化操作。
本书的这一部分对Java中的参数传递以及按引用传递和按值传递之间的区别(由Java的创建者)进行了很好的解释。我会鼓励任何人阅读它,特别是如果您仍然不相信的话。
我认为这两个模型之间的差异非常微妙,除非您在实际使用传递引用的地方进行了编程,否则很容易错过两个模型之间的差异。
我希望这能解决辩论,但可能不会。
编辑3
我可能对这个职位有些痴迷。可能是因为我觉得Java的制造商无意间散布了错误信息。如果他们没有使用“引用”一词作为指针,而是使用了其他东西(例如dingleberry),那就没有问题了。您可能会说:“ Java通过值而不是通过引用传递dingleberries”,并且不会有人感到困惑。(因此,在引用按引用与值进行传递时,我将引用称为dinglebarries。)
这就是只有Java开发人员对此有疑问的原因。他们看着“指称”一词,以为自己确切地知道那是什么意思,因此他们甚至不必理会相反的论点。
无论如何,我注意到dhackner在一个较早的帖子中发表了评论,他做出了我非常喜欢的气球类比。如此之多,以至于我决定将一些剪贴画粘合在一起,制作出一系列动画片来说明这一点。
按值传递引用 -引用的更改不会反映在调用者的作用域中,但对象的更改会反映在调用者的作用域中。这是因为引用已复制,但是原始副本和副本都引用同一对象。

通过引用传递-没有引用的副本。调用者和被调用函数都共享单个引用。对引用或对象数据的任何更改都会反映在调用者的作用域中。

编辑4
我看过有关此主题的文章,这些文章描述了Java中参数传递的低级实现,我认为这很好并且非常有帮助,因为它使抽象概念具体化。但是,对我来说,问题更多的是语言规范中描述的行为,而不是行为的技术实现。这是Java语言规范第8.4.1节的摘录:
调用方法或构造函数时(第15.12节),实际参数表达式的值会在执行方法或构造函数的主体之前,初始化每个声明类型的新创建的参数变量。出现在DeclaratorId中的标识符可用作方法或构造函数主体中的简单名称,以引用形式参数。
这意味着,java在执行方法之前创建了传递的参数的副本。像大多数上过大学的编译器的人一样,我使用的是《 The Dragon Book》,这是THE编译器的书。在第1章中对“按值调用”和“按引用调用”有很好的描述。“按值调用”描述与Java规范完全匹配。
当我使用编译器时,我使用了1986年的第一版书,它比Java早了9到10年。但是,我遇到了2007年第二版的副本,其中实际上提到了Java!标有“参数传递机制”的第1.6.6节很好地描述了参数传递。以下是“按值调用”标题下的摘录,其中提到了Java:
在按值调用中,将对实际参数求值(如果它是一个表达式)或将其复制(如果它是一个变量)。该值放置在属于被调用过程的相应形式参数的位置。此方法用在C和Java中,是C ++和大多数其他语言中的常用选项。
Jar*_*aus 48
您永远不能通过Java中的引用传递,并且明显的一种方法是当您想要从方法调用返回多个值时.考虑C++中的以下代码:
void getValues(int& arg1, int& arg2) {
arg1 = 1;
arg2 = 2;
}
void caller() {
int x;
int y;
getValues(x, y);
cout << "Result: " << x << " " << y << endl;
}
Run Code Online (Sandbox Code Playgroud)
有时你想在Java中使用相同的模式,但你不能; 至少不是直接的.相反,你可以做这样的事情:
void getValues(int[] arg1, int[] arg2) {
arg1[0] = 1;
arg2[0] = 2;
}
void caller() {
int[] x = new int[1];
int[] y = new int[1];
getValues(x, y);
System.out.println("Result: " + x[0] + " " + y[0]);
}
Run Code Online (Sandbox Code Playgroud)
正如之前的答案中所解释的那样,在Java中,您将指向数组的指针作为值传入getValues.这就足够了,因为该方法然后修改了数组元素,按照惯例,您期望元素0包含返回值.显然,您可以通过其他方式执行此操作,例如构造代码以使其不必要,或者构造可以包含返回值或允许设置它的类.但是,在C++中可用的简单模式在Java中不可用.
Sot*_*lis 47
我想我会提供这个答案,以便从规格中添加更多细节.
通过引用传递意味着被调用函数的参数将与调用者的传递参数相同(不是值,而是标识 - 变量本身).
按值传递意味着被调用函数的参数将是调用者传递的参数的副本.
或者来自维基百科,关于传递参考的主题
在逐个引用的评估(也称为pass-by-reference)中,函数接收对用作参数的变量的隐式引用,而不是其值的副本.这通常意味着函数可以修改(即赋值)用作参数的变量 - 其调用者将看到的内容.
在call-by-value中,计算参数表达式,并将结果值绑定到函数[...]中的相应变量.如果函数或过程能够为其参数赋值,则仅指定其本地副本[...].
其次,我们需要知道Java在其方法调用中使用了什么.在Java语言规范状态
当调用方法或构造函数(第15.12节)时,实际参数表达式的值在执行方法体或构造函数体之前初始化新创建的参数变量,每个声明的类型.
因此它将参数的值赋予(或绑定)到相应的参数变量.
争论的价值是什么?
让我们考虑引用类型,在Java虚拟机规范状态
有三种引用类型:类类型,数组类型和接口类型.它们的值分别是对动态创建的类实例,数组或类实例或实现接口的数组的引用.
在Java语言规范还规定
引用值(通常只是引用)是指向这些对象的指针,以及一个特殊的空引用,它指的是没有对象.
参数(某种引用类型)的值是指向对象的指针.请注意,变量,具有引用类型返回类型的方法的调用以及实例创建表达式(new ...)都将解析为引用类型值.
所以
public void method (String param) {}
...
String var = new String("ref");
method(var);
method(var.toString());
method(new String("ref"));
Run Code Online (Sandbox Code Playgroud)
all将String实例引用的值绑定到方法新创建的参数中param.这正是pass-by-value的定义所描述的内容.因此,Java是按值传递的.
您可以按照引用来调用方法或访问引用对象的字段这一事实与对话完全无关.传递参考的定义是
这通常意味着函数可以修改(即赋值)用作参数的变量 - 其调用者将看到的内容.
在Java中,修改变量意味着重新分配它.在Java中,如果在方法中重新分配变量,它将不会被调用者忽视.修改变量引用的对象完全是一个不同的概念.
此处还在Java虚拟机规范中定义了原始值.该类型的值是相应的积分或浮点值,适当编码(8,16,32,64等位).
shs*_*mer 36
区别,或者也许只是我记忆中的方式,因为我曾经与原始海报的印象相同:Java总是按价值传递.Java中的所有对象(在Java中,除了基元之外的任何东西)都是引用.这些引用按值传递.
use*_*316 36
在Java中,只传递引用并按值传递:
Java参数都是按值传递的(当方法使用时会复制引用):
对于基本类型,Java行为很简单:将值复制到基本类型的另一个实例中.
在Objects的情况下,这是相同的:对象变量是仅包含使用"new"关键字创建的Object的地址的指针(桶),并且像原始类型一样被复制.
行为可能与原始类型不同:因为复制的对象变量包含相同的地址(对于同一个对象)对象的内容/成员可能仍然在方法中被修改并且稍后在外部访问,从而产生(包含)对象的错觉本身是通过引用传递的.
"字符串"对象似乎是城市传说中"对象通过引用传递" 的完美反例:
实际上,在一个永远无法实现的方法中,更新作为参数传递的String的值:
String对象,由声明为final的数组保存,不能修改.只有Object的地址可能被另一个使用"new"替换.使用"new"更新变量,不会让Object从外部访问,因为变量最初是按值传递并复制的.
pek*_*pek 35
正如许多人之前提到的那样,Java始终是按值传递的
public class Test {
public static void main(String[] args) {
Integer a = new Integer(2);
Integer b = new Integer(3);
System.out.println("Before: a = " + a + ", b = " + b);
swap(a,b);
System.out.println("After: a = " + a + ", b = " + b);
}
public static swap(Integer iA, Integer iB) {
Integer tmp = iA;
iA = iB;
iB = tmp;
}
}
Run Code Online (Sandbox Code Playgroud)
打印:
之前:a = 2,b = 3
之后:a = 2,b = 3
这是因为iA和iB是新的本地引用变量,它们具有相同的传递引用值(它们分别指向a和b).因此,尝试更改iA或iB的引用只会在本地范围内更改,而不会在此方法之外.
Gau*_*rav 31
Java只通过值传递.一个非常简单的例子来验证这一点.
public void test() {
MyClass obj = null;
init(obj);
//After calling init method, obj still points to null
//this is because obj is passed as value and not as reference.
}
private void init(MyClass objVar) {
objVar = new MyClass();
}
Run Code Online (Sandbox Code Playgroud)
小智 29
我一直认为它是"通过副本".它是值的原始或引用的副本.如果它是原始的,则它是作为值的位的副本,如果它是Object,则它是引用的副本.
public class PassByCopy{
public static void changeName(Dog d){
d.name = "Fido";
}
public static void main(String[] args){
Dog d = new Dog("Maxx");
System.out.println("name= "+ d.name);
changeName(d);
System.out.println("name= "+ d.name);
}
}
class Dog{
public String name;
public Dog(String s){
this.name = s;
}
}
Run Code Online (Sandbox Code Playgroud)
java PassByCopy的输出:
name = Maxx
name = Fido
原始包装类和字符串是不可变的,因此使用这些类型的任何示例都不会与其他类型/对象相同.
小智 25
一些帖子的一些更正.
C不支持通过引用传递.它总是通过价值.C++确实支持按引用传递,但不是默认值,而且非常危险.
Java中的值是什么并不重要:对象的原始或地址(大致),它总是按值传递.
如果Java对象"行为"就像它通过引用传递一样,那就是可变性的属性,并且与传递机制完全无关.
我不确定为什么会这么混乱,也许是因为很多Java"程序员"没有经过正式培训,因此不了解内存中真正发生了什么?
Pau*_*eze 23
简而言之,Java对象具有一些非常特殊的属性.
一般来说,Java有原始类型(int,bool,char,double,等等)直接由值来传递.然后Java有对象(从中衍生出来的所有东西java.lang.Object).实际上,对象总是通过引用来处理(引用是一个你无法触摸的指针).这意味着实际上,对象是通过引用传递的,因为引用通常不是很有趣.但它确实意味着您无法更改指向的对象,因为引用本身是按值传递的.
这听起来有点奇怪和令人困惑吗?让我们考虑C如何通过引用传递并按值传递.在C中,默认约定是按值传递.void foo(int x)按值传递int.void foo(int *x)是一个不需要的函数int a,而是一个指向int的指针:foo(&a).可以使用它与&运算符一起传递变量地址.
把它带到C++,我们有参考.引用基本上(在此上下文中)隐藏等式的指针部分的语法糖:void foo(int &x)被调用foo(a),其中编译器本身知道它是引用,并且a应该传递非引用的地址.在Java中,所有引用对象的变量实际上都是引用类型,实际上强制通过引用调用大多数意图和目的,而没有由例如C++提供的细粒度控制(和复杂性).
Chr*_*ian 23
Java的按值传递参数,并通过价值ONLY.
长话短说:
对于来自C#的人:没有"out"参数.
来自PASCAL的人:没有"var"参数.
这意味着您无法更改对象本身的引用,但您始终可以更改对象的属性.
解决方法是使用StringBuilder参数String.你总是可以使用数组!
Mic*_*ael 21
与其他一些语言不同,Java不允许您选择按值传递或按引用传递 - 所有参数都按值传递.方法调用可以将两种类型的值传递给方法 - 原始值的副本(例如,int和double的值)以及对对象的引用的副本.
当方法修改基本类型参数时,对参数的更改不会影响调用方法中的原始参数值.
对于对象,对象本身不能传递给方法.所以我们传递在引用变量中保存的对象的地址.
Java如何创建和存储对象.当我们创建一个对象时,我们将对象的地址存储在一个引用变量中."扫描仪输入"是类型和参考变量,"="是赋值运算符,"新"是从系统请求所需的空间量.创建对象的关键字new右侧的构造函数由关键字new隐式调用.使用assign运算符将创建的对象的地址(右变量的结果,即表达式)分配给左变量(具有指定名称和类型的引用变量)."new Account()"被称为"类实例创建表达式".
虽然对象的引用是按值传递的,但是方法仍然可以通过使用对象引用的副本调用其公共方法来与引用的对象进行交互.由于存储在参数中的引用是作为参数传递的引用的副本,因此被调用方法中的参数和调用方法中的参数引用内存中的同一对象.
由于性能原因,传递对数组的引用而不是数组对象本身是有意义的.因为Java中的所有内容都是按值传递的,所以如果传递了数组对象,则会传递每个元素的副本.对于大型阵列,这会浪费时间并为元素的副本消耗大量存储空间.
在下面的图像中,您可以看到我们有两个引用变量(这些在C/C++中称为指针.我认为该术语使得更容易理解此功能.)在main方法中.原始和引用变量保存在堆栈存储器中(左下图中).这些引用变量"指向"(作为C/C++程序员称之为)或引用作为对象的a和b数组(这些引用变量保存的值是对象的地址)在堆内存中(下图中的右侧).
如果我们将array1引用变量的值作为参数传递给reverseArray方法,则在方法中创建一个引用变量,该引用变量开始指向同一个数组(a).
public class Test
{
public static void main(String args)
{
int[] array1 = { 1, 10, -7 };
reverseArray(array1);
}
public void reverseArray(Int[] array1)
{
// ...
}
}
Run Code Online (Sandbox Code Playgroud)
...
array1[0] = 5;
Run Code Online (Sandbox Code Playgroud)
所以,如果我们说
array1 = array2;
Run Code Online (Sandbox Code Playgroud)
在reverseArray方法中,它将在数组a中进行更改.
我们在reverseArray方法(array2)中有另一个指向数组c的引用变量.如果我们要说
return array2;
Run Code Online (Sandbox Code Playgroud)
在reverseArray方法中,方法reverseArray中的引用变量array1将停止指向数组a并开始指向数组c(第二个图像中的虚线).
如果我们返回引用变量array2的值作为方法reverseArray的返回值并将此值赋给main方法中的引用变量array1,则main中的array1将开始指向数组c.
您也可以将main2中的array2值赋给array1.array1将开始指向b.
Fel*_*ira 20
这是回答问题的最佳方式...
首先,我们必须了解,在Java中,参数传递行为 ......
public void foo(Object param)
{
// some code in foo...
}
public void bar()
{
Object obj = new Object();
foo(obj);
}
Run Code Online (Sandbox Code Playgroud)
与......完全一样
public void bar()
{
Object obj = new Object();
Object param = obj;
// some code in foo...
}
Run Code Online (Sandbox Code Playgroud)
不考虑堆栈位置,这与本讨论无关.
所以,事实上,我们在Java中寻找的是变量赋值的工作原理.我在文档中找到了它:
您将遇到的最常见的运算符之一是简单赋值运算符"="[...] 它将右侧的值分配给左侧的操作数:
int cadence = 0;
int speed = 0;
int gear = 1;此运算符也可用于对象以分配对象引用 [...]
很清楚这个算子如何以两种不同的方式起作用:赋值和赋值.最后一个,当它是一个对象时...第一个,当它不是一个对象时,也就是当它是一个原语时.但这样,我们可以了解到Java的功能PARAMS可以通过按值和传递按引用?
事实是在代码中.我们来试试吧:
public class AssignmentEvaluation
{
static public class MyInteger
{
public int value = 0;
}
static public void main(String[] args)
{
System.out.println("Assignment operator evaluation using two MyInteger objects named height and width\n");
MyInteger height = new MyInteger();
MyInteger width = new MyInteger();
System.out.println("[1] Assign distinct integers to height and width values");
height.value = 9;
width.value = 1;
System.out.println("-> height is " + height.value + " and width is " + width.value + ", we are different things! \n");
System.out.println("[2] Assign to height's value the width's value");
height.value = width.value;
System.out.println("-> height is " + height.value + " and width is " + width.value + ", are we the same thing now? \n");
System.out.println("[3] Assign to height's value an integer other than width's value");
height.value = 9;
System.out.println("-> height is " + height.value + " and width is " + width.value + ", we are different things yet! \n");
System.out.println("[4] Assign to height the width object");
height = width;
System.out.println("-> height is " + height.value + " and width is " + width.value + ", are we the same thing now? \n");
System.out.println("[5] Assign to height's value an integer other than width's value");
height.value = 9;
System.out.println("-> height is " + height.value + " and width is " + width.value + ", we are the same thing now! \n");
System.out.println("[6] Assign to height a new MyInteger and an integer other than width's value");
height = new MyInteger();
height.value = 1;
System.out.println("-> height is " + height.value + " and width is " + width.value + ", we are different things again! \n");
}
}
Run Code Online (Sandbox Code Playgroud)
这是我的运行输出:
Assignment operator evaluation using two MyInteger objects named height and width [1] Assign distinct integers to height and width values -> height is 9 and width is 1, we are different things! [2] Assign to height's value the width's value -> height is 1 and width is 1, are we the same thing now? [3] Assign to height's value an integer other than width's value -> height is 9 and width is 1, we are different things yet! [4] Assign to height the width object -> height is 1 and width is 1, are we the same thing now? [5] Assign to height's value an integer other than width's value -> height is 9 and width is 9, we are the same thing now! [6] Assign to height a new MyInteger and an integer other than width's value -> height is 1 and width is 9, we are different things again!
在[2]中,我们有不同的对象,并将一个变量的值分配给另一个.但是在[3]中分配新值后,对象具有不同的值,这意味着在[2]中,赋值是原始变量的副本,通常称为按值传递,否则,在[3]中打印的值]应该是一样的.
在[4]中,我们仍然有不同的对象,并将一个对象分配给另一个.在[5]中分配新值后,对象具有相同的值,这意味着在[4]中,分配的对象不是另一个的副本,应该称为pass-by-reference.但是,如果我们仔细研究[6],我们就不能确定没有复制了...... ?????
我们不能这么肯定,因为在[6]中对象是相同的,然后我们为其中一个分配了一个新对象,之后,对象具有不同的值!如果他们是相同的,他们现在如何区别?他们在这里也应该是一样的!?????
我们需要记住文档以了解发生了什么:
此运算符也可用于对象以分配对象引用
所以我们的两个变量是存储引用...我们的变量在[4]之后具有相同的引用和[6]之后的不同引用...如果这样的事情是可能的,这意味着对象的赋值是通过对象的副本完成的参考,否则,如果它不是参考文献的副本,[6]中变量的印刷值应该相同.因此,对象(引用)就像基元一样,通过赋值复制到变量,人们通常称之为传值.这是Java中唯一的传递方式.
Fer*_*osa 19
这非常非常简单:
对于基本类型的变量(如int,boolean,char,等...),当你使用它的名字的方法参数,要传递包含在它的价值(5,true,或'c').此值被"复制",即使在方法调用之后,变量仍保留其值.
对于引用类型的变量(例如String,Object等...),当你使用它的名字的方法参数,要传递包含的值(该参考值是"点"的对象).此参考值被"复制",即使在方法调用之后,变量仍保留其值.引用变量保持"指向"同一对象.
无论哪种方式,你总是按价值传递东西.
比较一下,你可以用c ++来获取一个方法int&,或者在C#中你可以拿一个ref int(虽然,在这种情况下,你还必须ref在将变量的名称传递给方法时使用修饰符.)
小智 19
纵观所有问题的答案,我们看到的Java传递的价值或者说作为@Gevorg写道:"传址复制的最值变量值",这是我们应该在心中所有的时间的想法.
我专注于帮助我理解这个想法的例子,它是以前答案的补充.
从[1]在Java中,你总是通过副本传递参数; 那就是你总是在函数内部创建一个新的值实例.但是有些行为可以让你认为你是通过参考传递的.
传递副本:当一个变量传递给方法/函数时,会产生一个副本(有时我们会听到当你传递基元时,你正在制作副本).
通过引用传递:当一个变量传递给方法/函数时,方法/函数中的代码对原始变量进行操作(您仍然通过复制传递,但对复杂对象内部值的引用是两个版本的一部分)变量,包括函数内的原始和版本.复制对象本身正在被复制,但内部引用被保留)
[ref 1]的例子
void incrementValue(int inFunction){
inFunction ++;
System.out.println("In function: " + inFunction);
}
int original = 10;
System.out.print("Original before: " + original);
incrementValue(original);
System.out.println("Original after: " + original);
We see in the console:
> Original before: 10
> In Function: 11
> Original after: 10 (NO CHANGE)
Run Code Online (Sandbox Code Playgroud)
[ref 2]的例子
很好地显示机制 最多5分钟
[ref 1]中的示例 (记住数组是一个对象)
void incrementValu(int[] inFuncion){
inFunction[0]++;
System.out.println("In Function: " + inFunction[0]);
}
int[] arOriginal = {10, 20, 30};
System.out.println("Original before: " + arOriginal[0]);
incrementValue(arOriginal[]);
System.out.println("Original before: " + arOriginal[0]);
We see in the console:
>Original before: 10
>In Function: 11
>Original before: 11 (CHANGE)
Run Code Online (Sandbox Code Playgroud)
正在复制复杂对象本身,但保留了内部引用.
[ref 3]的例子
package com.pritesh.programs;
class Rectangle {
int length;
int width;
Rectangle(int l, int b) {
length = l;
width = b;
}
void area(Rectangle r1) {
int areaOfRectangle = r1.length * r1.width;
System.out.println("Area of Rectangle : "
+ areaOfRectangle);
}
}
class RectangleDemo {
public static void main(String args[]) {
Rectangle r1 = new Rectangle(10, 20);
r1.area(r1);
}
}
Run Code Online (Sandbox Code Playgroud)
矩形区域为200,长度= 10,宽度= 20
我想分享的最后一件事是讲座的这一刻: 内存分配 ,我发现非常有助于理解Java传递的价值,或者更确切地说是"传递变量值的副本",正如@Gevorg写的那样.
小智 18
Java编程语言中最大的困惑之一是Java是Pass by Value还是Pass by Reference.
首先,我们应该理解通过值或通过引用传递的含义.
传递值:将方法参数值复制到另一个变量,然后传递复制的对象,这就是它被称为按值传递的原因.
通过引用传递:将实际参数的别名或引用传递给方法,这就是它被称为按引用传递的原因.
假设我们有一个类Balloon,如下所示.
public class Balloon {
private String color;
public Balloon(){}
public Balloon(String c){
this.color=c;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
Run Code Online (Sandbox Code Playgroud)
我们有一个简单的程序,使用通用方法交换两个对象,该类如下所示.
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;
}
}
Run Code Online (Sandbox Code Playgroud)
当我们执行上面的程序时,我们得到以下输出.
red color=Red
blue color=Blue
blue color=Red
Run Code Online (Sandbox Code Playgroud)
如果你查看输出的前两行,很明显交换方法不起作用.这是因为Java是通过值传递的,这个swap()方法测试可以与任何编程语言一起使用,以检查它是通过值传递还是通过引用传递.
让我们一步一步地分析程序的执行情况.
Balloon red = new Balloon("Red");
Balloon blue = new Balloon("Blue");
Run Code Online (Sandbox Code Playgroud)
当我们使用new运算符创建类的实例时,将创建实例,并且变量包含保存对象的内存的引用位置.对于我们的示例,我们假设"red"指向50并且"blue"指向100,这些是两个Balloon对象的内存位置.
现在,当我们调用swap()方法时,会创建两个新变量o1和o2,分别指向50和100.
所以下面的代码片段解释了swap()方法执行中发生的事情.
public static void swap(Object o1, Object o2){ //o1=50, o2=100
Object temp = o1; //temp=50, o1=50, o2=100
o1=o2; //temp=50, o1=100, o2=100
o2=temp; //temp=50, o1=100, o2=50
} //method terminated
Run Code Online (Sandbox Code Playgroud)
请注意,我们正在更改o1和o2的值,但它们是"红色"和"蓝色"参考位置的副本,因此实际上,"红色"和"蓝色"的值没有变化,因此输出也没有变化.
如果你已经理解了这一点,你可以很容易地理解混乱的原因.由于变量只是对象的引用,我们对传递引用感到困惑,因此Java通过引用传递.但是,我们传递的是引用的副本,因此它通过了值.我希望它现在能解决所有的疑虑.
现在让我们分析一下foo()方法的执行情况.
private static void foo(Balloon balloon) { //baloon=100
balloon.setColor("Red"); //baloon=100
balloon = new Balloon("Green"); //baloon=200
balloon.setColor("Blue"); //baloon = 200
}
Run Code Online (Sandbox Code Playgroud)
当我们调用方法时,第一行是重要的一行,在参考位置的Object上调用该方法.此时,气球指向100,因此它的颜色变为红色.
在下一行中,气球参考被更改为200,并且执行的任何其他方法都发生在存储器位置200处的对象上,并且对存储器位置100处的对象没有任何影响.这解释了我们的程序输出的第三行打印蓝色=红.
我希望上面的解释清楚所有的疑问,只要记住变量是引用或指针,并将其副本传递给方法,因此Java总是按值传递.当您了解堆内存和堆栈内存以及存储不同对象和引用的位置时,会更清楚.
fat*_*ici 17
Java通过常量引用传递,其中传递了引用的副本,这意味着它基本上是按值传递.如果类是可变的,您可能会更改引用的内容,但您无法更改引用本身.换句话说,地址不能改变,因为它是通过值传递的,但地址指向的内容可以改变.在不可变类的情况下,引用的内容也不能改变.
Pla*_*dru 16
Java总是使用按值调用.这意味着该方法获取所有参数值的副本.
考虑下面3种情况:
public static void increment(int x) { x++; }
int a = 3;
increment(a);
Run Code Online (Sandbox Code Playgroud)
x将复制a的值并将增加x,a保持不变
public static void increment(Person p) { p.age++; }
Person pers = new Person(20); // age = 20
increment(pers);
Run Code Online (Sandbox Code Playgroud)
p将复制pers的参考值并将增加age字段,变量引用同一个对象,因此年龄会发生变化
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);
Run Code Online (Sandbox Code Playgroud)
在调用swap p1之后,来自pers1和pers2的p2复制参考值与值交换,因此pers1和pers2保持不变
所以.您可以在方法中仅更改对象的字段,将参考值的副本传递给此对象.
Gee*_*Bee 16
这么多长的答案.让我举一个简单的说法:
简而言之,您无法修改传递的任何参数的值,但您可以调用方法或更改传递的对象引用的属性.
Rah*_*mar 16
毫无疑问,Java肯定是"按价值传递".此外,由于Java(大多数)是面向对象的,并且对象使用引用,因此很容易混淆并认为它是"通过引用传递"
按值传递意味着您将值传递给方法,如果方法更改传递的值,则实体不会更改.另一方面,通过引用传递意味着将引用传递给方法,并且如果方法更改它,则传递的对象也会更改.
在Java中,通常当我们将一个对象传递给一个方法时,我们基本上将对象的引用作为一个值传递,因为这就是Java的工作原理; 就堆中的Object而言,它适用于引用和地址.
但是要测试它是否真的按值传递或通过引用传递,您可以使用基本类型和引用:
@Test
public void sampleTest(){
int i = 5;
incrementBy100(i);
System.out.println("passed ==> "+ i);
Integer j = new Integer(5);
incrementBy100(j);
System.out.println("passed ==> "+ j);
}
/**
* @param i
*/
private void incrementBy100(int i) {
i += 100;
System.out.println("incremented = "+ i);
}
Run Code Online (Sandbox Code Playgroud)
输出是:
incremented = 105
passed ==> 5
incremented = 105
passed ==> 5
Run Code Online (Sandbox Code Playgroud)
因此,在这两种情况下,方法内部发生的任何事情都不会更改真实的Object,因为该对象的值已传递,而不是对象本身的引用.
但是当您将自定义对象传递给方法并且方法更改它时,它也会更改实际对象,因为即使您传递了对象,也会将其作为值传递给方法.我们试试另一个例子:
@Test
public void sampleTest2(){
Person person = new Person(24, "John");
System.out.println(person);
alterPerson(person);
System.out.println(person);
}
/**
* @param person
*/
private void alterPerson(Person person) {
person.setAge(45);
Person altered = person;
altered.setName("Tom");
}
private static class Person{
private int age;
private String name;
public Person(int age, String name) {
this.age=age;
this.name =name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Person [age=");
builder.append(age);
builder.append(", name=");
builder.append(name);
builder.append("]");
return builder.toString();
}
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,输出是:
Person [age=24, name=John]
Person [age=45, name=Tom]
Run Code Online (Sandbox Code Playgroud)
小智 13
当我说通过值时,它意味着每当调用者调用被调用者时,参数(即:要传递给另一个函数的数据)被复制并放入形式参数(被调用者的本地变量用于接收输入).Java仅通过值环境从一个函数到其他函数进行数据通信.
重要的一点是要知道即使是C语言也只是通过值严格传递:
即:数据从调用者复制到被调用者,并且被调用者执行的操作更多地在同一个内存位置上,我们传递给他们的是我们从(&)运算符获取的那个位置的地址和形式参数中使用的标识符被声明为一个指针变量(*),我们可以使用它来获取内存位置以访问其中的数据.
因此,形式参数只不过是该位置的别名.并且在该位置上进行的任何修改都是可见的,其中变量的范围(标识该位置)是活着的.
在Java中,没有指针的概念(即:没有任何东西称为指针变量),尽管我们可以在java中将引用变量视为技术指针,我们将其称为句柄.我们之所以将指向地址的指针调用为java中的句柄,是因为指针变量不仅可以执行单个解除引用,而且还可以执行多次解除引用:例如:int *p;P表示p指向整数,int **p;而C表示p指向指向我们在Java中没有这个功能的整数的指针,所以它绝对正确且在技术上合法地说它作为句柄,C中也有指针算法的规则.它允许对具有约束的指针执行算术运算.
在C中,我们调用这样的机制来传递地址并使用指针变量接收它们作为传递,因为我们传递它们的地址并在形式参数中接收它们作为指针变量但在编译器级别将地址复制到指针变量中(因为这里的数据)是地址,即使它的数据)因此我们可以100%确定C严格按值传递(因为我们只传递数据)
(如果我们直接在C中传递数据,我们将其称为pass by value.)
在java中,当我们这样做时,我们用句柄来做; 因为它们不像in中那样被称为指针变量(如上所述),即使我们传递了引用,也不能说它是通过引用传递的,因为我们没有用Java中的指针变量来收集它.
因此Java 严格使用pass by value机制
Mr.*_*bot 13
我看到所有答案都包含相同的内容:按值传递。然而,最近 Brian Goetz 对 Valhalla 项目的更新实际上给出了不同的答案:
事实上,Java 对象是按值传递还是按引用传递是一个常见的“陷阱”问题,答案是“都不是”:对象引用是按值传递的。
您可以在此处阅读更多信息:瓦尔哈拉状态。第 2 部分:语言模型
编辑: Brian Goetz 是 Java 语言架构师,领导诸如 Project Valhalla 和 Project Amber 之类的项目。
编辑 2020-12-08:更新瓦尔哈拉状态
Vin*_*dha 12
看看这段代码.这段代码不会抛出NullPointerException...它将打印"Vinay"
public class Main {
public static void main(String[] args) {
String temp = "Vinay";
print(temp);
System.err.println(temp);
}
private static void print(String temp) {
temp = null;
}
}
Run Code Online (Sandbox Code Playgroud)
如果Java通过引用传递,那么它应该抛出,NullPointerException因为引用设置为Null.
snr*_*snr 12
主要的基础知识必须是引用的,
将对象引用传递给方法时,将使用call-by-value传递引用本身.但是,由于传递的值是指对象,因此该值的副本仍将引用其相应参数引用的同一对象.
Java:初学者指南,第六版,Herbert Schildt
kha*_*iab 11
检查语言是否支持传递引用的简单测试只是编写传统的交换.你能用Java编写传统的swap(a,b)方法/函数吗?
传统的交换方法或函数接受两个参数并交换它们,以便传递给函数的变量在函数外部进行更改.它的基本结构看起来像
(非Java)基本交换功能结构
swap(Type arg1, Type arg2) {
Type temp = arg1;
arg1 = arg2;
arg2 = temp;
}
Run Code Online (Sandbox Code Playgroud)
如果你能用你的语言编写这样的方法/函数,那么就可以调用
Type var1 = ...;
Type var2 = ...;
swap(var1,var2);
Run Code Online (Sandbox Code Playgroud)
实际上切换变量var1和var2的值,语言支持pass-by-reference.但Java不允许这样的事情,因为它支持仅传递值而不是指针或引用.
jac*_*ack 10
不重复,但给那些在阅读了许多答案后可能仍然感到困惑的人的一点:
pass by value在Java中是不等于要pass by value在C ++中,虽然听起来好像是,这可能是为什么有困惑分解它:
pass by value 在 C++ 中意味着传递对象的值(如果是对象),字面意思是对象的副本pass by value 在 Java 中意味着传递对象的地址值(如果是对象),而不是像 C++ 那样真正的对象的“值”(副本)pass by value在 Java 中,对myObj.setName("new")函数内部的对象(例如)进行操作会对函数外部的对象产生影响;通过pass by value在C ++中,它具有NO在一个外面的效果。pass by reference在 C++ 中,对函数中的对象进行操作确实会对外部对象产生影响!与Java 中的相似(只是相似,不相同)pass by value,不是吗?.. 人们总是重复“ Java 中没有通过引用传递”,=> BOOM,混乱开始......所以,朋友们,一切都只是术语定义(跨语言)的差异,您只需要知道它是如何工作的,就是这样(尽管有时我承认它的名称有点令人困惑)!
围绕这个问题的许多困惑来自于 Java 试图重新定义“按值传递”和“按引用传递”的含义这一事实。重要的是要了解这些是行业术语,在该上下文之外无法正确理解。它们旨在在您编写代码时为您提供帮助并且理解起来很有价值,因此让我们首先了解一下它们的含义。
可以在此处找到两者的详细说明。
Pass By Value函数接收到的值是调用者正在使用的对象的副本。它对函数来说是完全独一无二的,你对该对象所做的任何事情都只会在函数中看到。
Pass By Reference函数接收到的值是对调用者正在使用的对象的引用。函数对 value 引用的对象所做的任何事情都会被调用者看到,并且从那时起它将处理这些更改。
从这些定义中可以清楚地看出,引用按值传递的事实是无关紧要的。如果我们接受这个定义,那么这些术语就变得毫无意义,所有语言都只是传递值。
无论您如何传递引用,它都只能按值传递。这不是重点。关键是您将自己对象的引用传递给函数,而不是它的副本。您可以丢弃收到的参考资料这一事实无关紧要。同样,如果我们接受这个定义,这些术语就变得毫无意义,每个人都在传递价值。
不,C++ 特殊的“引用传递”语法并不是引用传递的唯一定义。这纯粹是一种方便的语法,旨在使您在传入指针后不需要使用指针语法。它仍在传递指针,编译器只是向您隐藏了这个事实。它仍然按值传递该指针,编译器只是对你隐藏了它。
所以,有了这样的理解,我们可以看看Java,发现它实际上兼具了两者。所有 Java 基本类型总是按值传递,因为您收到调用者对象的副本并且无法修改它们的副本。所有 Java 引用类型总是按引用传递,因为您收到对调用者对象的引用并且可以直接修改它们的对象。
您无法修改调用者的引用这一事实与传递引用无关,并且在支持传递引用的每种语言中都是如此。
为了增加这一点,我想我会在这个主题中加入SCJP学习指南部分.这是通过对Java行为进行Sun/Oracle测试的指南,因此它是用于此讨论的良好来源.
将变量传递给方法(目标7.3)
7.3确定对象引用和原始值在传递到对参数执行赋值或其他修改操作的方法时的影响.
可以声明方法以获取基元和/或对象引用.您需要知道被调用方法如何(或者如果)调用者的变量会受到影响.传递给方法时,对象引用和原始变量之间的区别是巨大而重要的.要理解本节,您需要熟悉本章第一部分中介绍的作业部分.
传递对象引用变量
将对象变量传递给方法时,必须记住您传递的是对象引用,而不是实际的对象本身.请记住,引用变量包含一些位(表示底层VM)到达内存中特定对象(在堆上)的方式.更重要的是,您必须记住,您甚至没有传递实际的引用变量,而是传递引用变量的副本.变量的副本意味着您获得该变量中位的副本,因此当您传递引用变量时,您将传递表示如何到达特定对象的位的副本.换句话说,调用者和被调用的方法现在都具有相同的引用副本,因此它们都将引用堆上相同的(非副本)对象.
对于此示例,我们将使用java.awt包中的Dimension类:
1. import java.awt.Dimension;
2. class ReferenceTest {
3. public static void main (String [] args) {
4. Dimension d = new Dimension(5,10);
5. ReferenceTest rt = new ReferenceTest();
6. System.out.println("Before modify() d.height = " + d.height);
7. rt.modify(d);
8. System.out.println("After modify() d.height = "
9. }
10.
11.
12.
13. }
14. }
Run Code Online (Sandbox Code Playgroud)
当我们运行这个类时,我们可以看到modify()方法确实能够修改在第4行创建的原始(且唯一)Dimension对象.
C:\Java Projects\Reference>java ReferenceTest Before modify() d.height = 10 dim = 11 After modify() d.height = 11
请注意,当第4行的Dimension对象传递给modify()方法时,对方法内部发生的对象的任何更改都将发送给传递了引用的对象.在前面的示例中,引用变量d和dim都指向同一个对象.
Java是否使用Pass-By-Value语义?
如果Java通过传递引用变量来传递对象,那是否意味着Java使用对象的pass-by-reference?不完全是,虽然你经常会听到并读到它.Java实际上是在单个VM中运行的所有变量的值传递.按值传递意味着传递变量值.这意味着,传递副本的变量!(再次有那个单词复制!)
如果您传递原始变量或引用变量没有区别,那么您总是传递变量中的位副本.因此,对于原始变量,您传递的是表示该值的位的副本.例如,如果传递值为3的int变量,则传递代表3的位的副本.然后,被调用的方法获取其自己的值副本,并根据需要进行操作.
如果您传递的是对象引用变量,那么您将传递表示对象引用的位的副本.然后,被调用的方法获得自己的引用变量副本,以便按照自己喜欢的方式进行操作.但是因为两个相同的引用变量引用完全相同的对象,如果被调用的方法修改了对象(例如,通过调用setter方法),调用者将看到调用者的原始变量引用的对象也已被更改.在下一节中,我们将讨论当我们谈论基元时图像如何变化.
按值传递的底线:被调用的方法不能更改调用者的变量,尽管对于对象引用变量,被调用的方法可以更改引用的变量对象.更改变量和更改对象之间有什么区别?对于对象引用,它意味着被调用的方法不能重新分配调用者的原始引用变量并使其引用不同的对象,或者为null.例如,在以下代码片段中,
void bar() {
Foo f = new Foo();
doStuff(f);
}
void doStuff(Foo g) {
g.setName("Boo");
g = new Foo();
}
Run Code Online (Sandbox Code Playgroud)
重新分配g不会重新分配f!在bar()方法的末尾,创建了两个Foo对象,一个由局部变量f引用,另一个由局部(参数)变量g引用.因为doStuff()方法具有引用变量的副本,所以它有一种方法可以获取原始的Foo对象,例如调用setName()方法.但是,doStuff()方法没有办法获取f引用变量.所以doStuff()可以改变f引用的对象内的值,但doStuff()不能改变f的实际内容(位模式).换句话说,doStuff()可以改变f引用的对象的状态,但它不能使f引用不同的对象!
传递原始变量
让我们看一下将原始变量传递给方法时会发生什么:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
在这个简单的程序中,变量a被传递给一个名为modify()的方法,该方法将变量递增1.结果输出如下所示:
Before modify() a = 1
number = 2
After modify() a = 1
Run Code Online (Sandbox Code Playgroud)
请注意,a在传递给方法后没有更改.请记住,它是传递给方法的副本.当一个原始变量传递给一个方法时,它会通过值传递,这意味着在该变量中传递该位的副本.
与其他一些语言不同,Java不允许您选择按值传递或按引用传递
所有参数都按值传递.
方法调用可以将两个types of values方法传递给方法
Objects themselves cannot be passed to methods.当方法修改基本类型参数时,对参数的更改不会影响调用方法中的原始参数值.
参考类型参数也是如此.如果修改引用类型参数以使其引用另一个对象,则只有参数引用新对象 - 存储在调用方变量中的引用仍引用原始对象.
参考文献:Java™如何编程(早期对象),第十版
小智 8
Java 仅按值传递。没有按引用传递,例如可以看下面的例子。
package com.asok.cop.example.task;
public class Example {
int data = 50;
void change(int data) {
data = data + 100;// changes will be in the local variable
System.out.println("after add " + data);
}
public static void main(String args[]) {
Example op = new Example();
System.out.println("before change " + op.data);
op.change(500);
System.out.println("after change " + op.data);
}
}
Run Code Online (Sandbox Code Playgroud)
输出:
before change 50
after add 600
after change 50
Run Code Online (Sandbox Code Playgroud)
正如迈克尔在评论中所说:
对象仍然按值传递,即使对它们的操作表现得像按引用传递。考虑
void changePerson(Person person){ person = new Person(); }到调用者对 person 对象的引用将保持不变。对象本身是按值传递的,但它们的成员可能会受到更改的影响。要成为真正的引用传递,我们必须能够将参数重新分配给新对象,并将更改反映在调用方中。
长话短说:
结束。
(2)太容易了。现在,如果您想考虑(1)的含义,请想象您有一个Apple类:
class Apple {
private double weight;
public Apple(double weight) {
this.weight = weight;
}
// getters and setters ...
}
Run Code Online (Sandbox Code Playgroud)
然后当您将此类的实例传递给main方法时:
class Main {
public static void main(String[] args) {
Apple apple = new Apple(3.14);
transmogrify(apple);
System.out.println(apple.getWeight()+ " the goose drank wine...";
}
private static void transmogrify(Apple apple) {
// does something with apple ...
apple.setWeight(apple.getWeight()+0.55);
}
}
Run Code Online (Sandbox Code Playgroud)
哦..但是您可能知道,您对执行以下操作会发生什么感兴趣:
class Main {
public static void main(String[] args) {
Apple apple = new Apple(3.14);
transmogrify(apple);
System.out.println("Who ate my: "+apple.getWeight()); // will it still be 3.14?
}
private static void transmogrify(Apple apple) {
// assign a new apple to the reference passed...
apple = new Apple(2.71);
}
}
Run Code Online (Sandbox Code Playgroud)
A:Java 确实通过引用来操作对象,所有对象变量都是引用。但是,Java 不会通过引用传递方法参数;它按值传递它们。
以 badSwap() 方法为例:
public void badSwap(int var1, int var2)
{
int temp = var1;
var1 = var2;
var2 = temp;
}
Run Code Online (Sandbox Code Playgroud)
当 badSwap() 返回时,作为参数传递的变量仍将保持其原始值。如果我们将参数类型从 int 更改为 Object,该方法也会失败,因为 Java 也按值传递对象引用。现在,这就是棘手的地方:
public void tricky(Point arg1, Point arg2)
{
arg1.x = 100;
arg1.y = 100;
Point temp = arg1;
arg1 = arg2;
arg2 = temp;
}
public static void main(String [] args)
{
Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);
System.out.println("X: " + pnt1.x + " Y: " +pnt1.y);
System.out.println("X: " + pnt2.x + " Y: " +pnt2.y);
System.out.println(" ");
tricky(pnt1,pnt2);
System.out.println("X: " + pnt1.x + " Y:" + pnt1.y);
System.out.println("X: " + pnt2.x + " Y: " +pnt2.y);
}
Run Code Online (Sandbox Code Playgroud)
如果我们执行这个 main() 方法,我们会看到以下输出:
X: 0 Y: 0
X: 0 Y: 0
X: 100 Y: 100
X: 0 Y: 0
Run Code Online (Sandbox Code Playgroud)
该方法成功地改变了 pnt1 的值,即使它是按值传递的;然而,pnt1 和 pnt2 的交换失败了!这是混淆的主要来源。在 main() 方法中,pnt1 和 pnt2 只不过是对象引用。当您将 pnt1 和 pnt2 传递给 tricky() 方法时,Java 就像任何其他参数一样按值传递引用。这意味着传递给该方法的引用实际上是原始引用的副本。下面的图 1 显示了在 Java 将对象传递给方法后指向同一对象的两个引用。
Java 按值复制和传递引用,而不是按对象。因此,方法操作将改变对象,因为引用指向原始对象。但是由于引用是副本,交换将失败。该方法引用交换,而不是原始引用。不幸的是,在方法调用之后,您只剩下未交换的原始引用。为了在方法调用之外成功进行交换,我们需要交换原始引用,而不是副本。
小智 8
Java 总是按值传递参数。
Java 中的所有对象引用都是按值传递的。这意味着值的副本将传递给方法。但诀窍是传递值的副本也会更改对象的实际值。
请参考下面的例子,
public class ObjectReferenceExample {
public static void main(String... doYourBest) {
Student student = new Student();
transformIntoHomer(student);
System.out.println(student.name);
}
static void transformIntoDuleepa(Student student) {
student.name = "Duleepa";
}
}
class Student {
String name;
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,它将是 Duleepa!
原因是 Java 对象变量只是指向内存堆中真实对象的引用。因此,即使Java通过值将参数传递给方法,如果变量指向一个对象引用,那么真实的对象也会发生变化。
@Scott Stanchfield 先生写了一个很好的答案。这是您要确切验证他的意思的课程:
public class Dog {
String dog ;
static int x_static;
int y_not_static;
public String getName()
{
return this.dog;
}
public Dog(String dog)
{
this.dog = dog;
}
public void setName(String name)
{
this.dog = name;
}
public static void foo(Dog someDog)
{
x_static = 1;
// y_not_static = 2; // not possible !!
someDog.setName("Max"); // AAA
someDog = new Dog("Fifi"); // BBB
someDog.setName("Rowlf"); // CCC
}
public static void main(String args[])
{
Dog myDog = new Dog("Rover");
foo(myDog);
System.out.println(myDog.getName());
}
}
Run Code Online (Sandbox Code Playgroud)
所以,我们从 main() 传递了一个叫做 的狗Rover,然后我们为我们传递的指针分配了一个新地址,但最后,狗的名字不是Rover,不是Fifi,当然不是Rowlf,但是Max。
分两步理解:
您不能更改对对象本身的引用,但可以将此传递的参数用作对对象的引用。
如果您想更改引用后面的值,您只需在堆栈上声明一个具有相同名称“d”的新变量。查看带有标志的引用,@您会发现引用已更改。
public static void foo(Dog d) {
d.Name = "belly";
System.out.println(d); //Reference: Dog@1540e19d
d = new Dog("wuffwuff");
System.out.println(d); //Dog@677327b6
}
public static void main(String[] args) throws Exception{
Dog lisa = new Dog("Lisa");
foo(lisa);
System.out.println(lisa.Name); //belly
}
Run Code Online (Sandbox Code Playgroud)
我制作了这个小图,显示了如何创建和传递数据
注意:原始值作为值传递,对该值的第一个引用是方法的参数
这意味着:
myObject 内部的值myObject函数内部的引用,因为point不是myObjectpoint和myObject都是引用,是不同的引用,但是,这些引用指向相同的new Point(0,0)有一种非常简单的方法可以理解这一点.让我们以C++传递参考.
#include <iostream>
using namespace std;
class Foo {
private:
int x;
public:
Foo(int val) {x = val;}
void foo()
{
cout<<x<<endl;
}
};
void bar(Foo& ref)
{
ref.foo();
ref = *(new Foo(99));
ref.foo();
}
int main()
{
Foo f = Foo(1);
f.foo();
bar(f);
f.foo();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
结果是什么?
1 1 99 99
因此,在bar()为传入的"引用"分配了一个新值之后,它实际上改变了从main本身传入的那个,解释了来自主打印99的最后一次f.foo()调用.
现在,让我们看看java所说的内容.
public class Ref {
private static class Foo {
private int x;
private Foo(int x) {
this.x = x;
}
private void foo() {
System.out.println(x);
}
}
private static void bar(Foo f) {
f.foo();
f = new Foo(99);
f.foo();
}
public static void main(String[] args) {
Foo f = new Foo(1);
System.out.println(f.x);
bar(f);
System.out.println(f.x);
}
}
Run Code Online (Sandbox Code Playgroud)
它说:
1 1 99 1
Voilà,传递给酒吧的Foo的主要参考仍未改变!
这个例子清楚地表明,当我们说"按引用传递"时,java与C++不同.从本质上讲,java将"引用"作为"值"传递给函数,这意味着java是按值传递的.
Java是按值传递的.
这个帖子已经有了很好的答案.不知何故,关于原始数据类型和对象,我从未明确传递值/引用.因此,我通过以下代码测试了我的满意度和清晰度; 可能会帮助寻求类似清晰度的人:
class Test {
public static void main (String[] args) throws java.lang.Exception
{
// Primitive type
System.out.println("Primitve:");
int a = 5;
primitiveFunc(a);
System.out.println("Three: " + a); //5
//Object
System.out.println("Object:");
DummyObject dummyObject = new DummyObject();
System.out.println("One: " + dummyObject.getObj()); //555
objectFunc(dummyObject);
System.out.println("Four: " + dummyObject.getObj()); //666 (555 if line in method uncommented.)
}
private static void primitiveFunc(int b) {
System.out.println("One: " + b); //5
b = 10;
System.out.println("Two:" + b); //10
}
private static void objectFunc(DummyObject b) {
System.out.println("Two: " + b.getObj()); //555
//b = new DummyObject();
b.setObj(666);
System.out.println("Three:" + b.getObj()); //666
}
}
class DummyObject {
private int obj = 555;
public int getObj() { return obj; }
public void setObj(int num) { obj = num; }
}
Run Code Online (Sandbox Code Playgroud)
如果该行b = new DummyObject()未被注释,则此后进行的修改将在新对象上进行,即新实例化.因此,它不会反映在调用方法的地方.然而,否则,改变反映为仅对对象的"引用"进行修改,即-b指向相同的伪对象.
此主题(/sf/answers/870096741/)中的一个答案中的插图有助于深入理解.
通过引用传递:调用者和被调用者使用相同的变量作为参数.
通过值传递:调用者和被调用者有两个具有相同值的自变量.
使用原始数据类型的示例:
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
}
}
Run Code Online (Sandbox Code Playgroud)
使用对象的示例:
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
}
}
Run Code Online (Sandbox Code Playgroud)
有一个蓝色的,120平方英尺的"Tiny House",目前停放在1234 Main St,前面有一个修剪整齐的草坪和花坛.
聘请了一家本地公司的房地产经纪人并告知他们要保留该房屋的清单.
让我们称之为房地产经纪人"鲍勃".嗨鲍勃.
鲍勃保持他的名单,他称之为tinyHouseAt1234Main最新的网络摄像头,允许他实时记录实际房屋的任何变化.他还记录了有多少人询问上市情况.鲍勃的viewTally房子整数今天是42.
每当有人想要了解1234 Main St的蓝色小屋时,他们会问鲍勃.
鲍勃查看他的清单tinyHouseAt1234Main并告诉他们所有关于它 - 颜色,漂亮的草坪,阁楼床和堆肥厕所等.然后他将他们的询问添加到他的viewTally.他并没有告诉他们真实的实际地址,因为鲍勃的公司专注于可以随时移动的Tiny Houses.这个数字现在是43.
在另一家公司,房地产经纪人可能会明确表示他们的上市"点"指向1234 Main St的房子,表示*旁边有一点,因为他们主要处理很少搬家的房子(尽管可能有理由这样做) .鲍勃的公司不打扰这样做.
现在,鲍勃当然不会把实际的房子放在卡车上直接向客户展示 - 这是不切实际的,也是荒谬的资源浪费.通过他的理货单的完整副本是一回事,但一直绕过整个房子是昂贵和荒谬的.
(旁白:鲍勃的公司也不会每次有人问起3D打印所列出的房屋的新的和独特的副本.这就是新贵,同样命名为基于网络的公司及其衍生产品 - 这是昂贵和缓慢的,以及人们经常让2家公司感到困惑,但无论如何他们都很受欢迎.
在其他一些靠近海洋的老公司,像鲍勃这样的房地产经纪人可能甚至不存在管理房源.客户可以咨询Rolodex"Annie"(&简称)以获取房屋的直接地址.客户不是像鲍勃那样从列表中读取引用的房屋细节,而是从Annie(&)获得房屋地址,然后直接前往1234 Main St,有时不知道他们在那里可能找到什么.
有一天,Bob的公司开始提供一种新的自动化服务,该服务需要客户感兴趣的房屋列表.
好吧,拥有该信息的人是Bob,所以客户端让Bob调用该服务并向其发送该列表的副本.
jobKillingAutomatedListingService(Listing tinyHouseAt1234Main, int viewTally) 鲍勃发送...
该服务最终会调用此列表houseToLookAt,但它收到的确实是Bob的列表的完整副本,其中包含完全相同的VALUE,指的是1234 Main St.的房子.
这项新服务还有自己的内部统计数据,显示有多少人查看过该商家信息.该服务接受Bob的专业礼貌,但它并不真正关心并完全用自己的本地副本覆盖它.今天的计数是1,而鲍勃仍然是43.
自从鲍勃传递他viewTally和他的上市的当前价值以来,房地产公司称之为"按价值传递" tinyHouseAt1234Main.他实际上并没有经过整个实体房子,因为那是不切实际的.他也没有像Annie(&)那样传递真实的实际地址.
但他正在传递他所拥有的参考价值的副本给房子.在某些方面看起来似乎是一种愚蠢的迂腐差异,但这就是他的公司如何运作...... ..............
新的自动化服务,不像其他一些时髦的金融和科学公司那样全功能和数学导向,可能会产生无法预料的副作用......
一旦给出了一个列表对象,它允许客户使用远程无人机机器人车队在1234 Main St 实际重新绘制REAL房屋!它允许客户控制机器人推土机,实际上挖掘花坛!这太疯狂了!!!
该服务还允许客户完全重定向houseToLookAt到另一个地址的其他房子,而不涉及Bob或他的列表.突然之间,他们可能会看到4321 Elm St.,这与Bob的列表没有任何联系(幸好他们不能再造成任何损害).
鲍勃在他的实时网络摄像头上观看了这一切.他辞去了他唯一的工作职责,他告诉客户新的丑陋油漆工作和突然缺乏路边吸引力.他的上市是仍是1234主街,毕竟.新服务houseToLookAt无法改变这一点.鲍勃tinyHouseAt1234Main一如既往地准确,尽职地报告他的细节,直到他被解雇或者房子被完全摧毁了.
真正唯一的服务是不能用它houseToLookAt的Bob原始列表的副本将地址从1234 Main St.更改为其他地址,或者是虚无的,或者像Platypus这样的随机类型的对象.鲍勃的上市仍然总是指向1234 Main St,无论它仍然值得.他像往常一样传递当前的价值.
将列表传递给新的自动化服务的这种奇怪的副作用让那些询问它如何工作的人感到困惑.真的,远程控制机器人的能力有什么区别,改变1234 Main的房子状态,实际上去那里并且因为安妮给你的地址造成严重破坏?
如果您通常关心的是列表中房屋的状态被复制和传递,看起来就像是一种挑剔的语义论证,对吧?
我的意思是,如果你的业务是实际购买房屋并将它们移动到其他地址(不像移动或Tiny Homes那样,这是平台的预期功能),或者你正在访问,重命名和改组整个邻居喜欢某种低级别的上帝玩疯子,那么也许你更关心的是传递那些特定的地址参考而不仅仅是房子细节的最新价值的副本......
现在,人们喜欢无休止地争论"通过引用传递"是否是描述Java等人的正确方法.实际上.关键在于:
在我的书中,这被称为传递参考.
一切都是通过价值传递的.基元和对象引用.但是,如果对象允许,则可以更改对象.
将对象传递给方法时,将传递引用,并且方法实现可以修改该对象.
void bithday(Person p) {
p.age++;
}
Run Code Online (Sandbox Code Playgroud)
对象本身的引用是通过值传递的:您可以重新分配参数,但更改不会反映出来:
void renameToJon(Person p) {
p = new Person("Jon"); // this will not work
}
jack = new Person("Jack");
renameToJon(jack);
sysout(jack); // jack is unchanged
Run Code Online (Sandbox Code Playgroud)
作为效果,"p"是参考(指向对象的指针)并且不能改变.
原始类型按值传递.对象的引用也可以被认为是原始类型.
总结一下,一切都是按价值传递的.
最短的答案:)
在C#中,这是通过"out"和"ref"关键字完成的.
按引用传递:传递变量的方式是即使在方法外部也反映了方法内部的重新分配.
下面是一个传递引用(C#)的例子.java中不存在此功能.
class Example
{
static void InitArray(out int[] arr)
{
arr = new int[5] { 1, 2, 3, 4, 5 };
}
static void Main()
{
int[] someArray;
InitArray(out someArray);
// This is true !
boolean isTrue = (someArray[0] == 1);
}
}
Run Code Online (Sandbox Code Playgroud)
另请参见:MSDN库(C#):按值和引用传递
Java 中有一个解决方法供参考。让我通过这个例子来解释:
public class Yo {
public static void foo(int x){
System.out.println(x); //out 2
x = x+2;
System.out.println(x); // out 4
}
public static void foo(int[] x){
System.out.println(x[0]); //1
x[0] = x[0]+2;
System.out.println(x[0]); //3
}
public static void main(String[] args) {
int t = 2;
foo(t);
System.out.println(t); // out 2 (t did not change in foo)
int[] tab = new int[]{1};
foo(tab);
System.out.println(tab[0]); // out 3 (tab[0] did change in foo)
}}
Run Code Online (Sandbox Code Playgroud)
我希望这有帮助!
小智 6
简单的程序
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
}
}
Run Code Online (Sandbox Code Playgroud)
从C/C++程序员的角度来看,java使用pass by value,因此对于原始数据类型(int,char等),函数中的更改不会反映在调用函数中.但是当您传递一个对象并在函数中更改其数据成员或调用可以更改对象状态的成员函数时,调用函数将获得更改.
Java 确实通过引用来操作对象,并且所有对象变量都是引用。但是,Java 不会通过引用传递方法参数;它按值传递它们。
以 badSwap() 方法为例:
public void badSwap(int var1, int
var2{ int temp = var1; var1 = var2; var2 =
temp; }
Run Code Online (Sandbox Code Playgroud)
当 badSwap() 返回时,作为参数传递的变量仍将保持其原始值。如果我们将参数类型从 int 更改为 Object,该方法也会失败,因为 Java 也按值传递对象引用。现在,这就是棘手的地方:
public void tricky(Point arg1, Point arg2)
{ arg1.x = 100; arg1.y = 100; Point temp = arg1; arg1 = arg2; arg2 = temp; }
public static void main(String [] args) {
Point pnt1 = new Point(0,0); Point pnt2
= new Point(0,0); System.out.println("X:
" + pnt1.x + " Y: " +pnt1.y);
System.out.println("X: " + pnt2.x + " Y:
" +pnt2.y); System.out.println(" ");
tricky(pnt1,pnt2);
System.out.println("X: " + pnt1.x + " Y:" + pnt1.y);
System.out.println("X: " + pnt2.x + " Y: " +pnt2.y); }
Run Code Online (Sandbox Code Playgroud)
如果我们执行这个 main() 方法,我们会看到以下输出:
X:0 Y:0 X:0 Y:0 X:100 Y:100 X:0 Y:0
该方法成功地改变了 pnt1 的值,即使它是按值传递的;然而,pnt1 和 pnt2 的交换失败了!这是混淆的主要来源。在 main() 方法中,pnt1 和 pnt2 只不过是对象引用。当您将pnt1 和pnt2 传递给tricky() 方法时,Java 就像任何其他参数一样按值传递引用。这意味着传递给该方法的引用实际上是原始引用的副本。下面的图 1 显示了在 Java 将对象传递给方法后指向同一对象的两个引用。
Java 按值复制和传递引用,而不是按对象。因此,方法操作将改变对象,因为引用指向原始对象。但由于引用是副本,交换将失败。如图 2 所示,方法引用交换,而不是原始引用。不幸的是,在方法调用之后,您只剩下未交换的原始引用。为了在方法调用之外成功进行交换,我们需要交换原始引用,而不是副本。
在我看来,"按价值传递"是一种可怕的方式来单独描述两个相似但不同的事件.我猜他们应该先问我.
对于原语,我们将原语的实际值传递给方法(或构造函数),无论是整数"5",字符"c",还是你有什么.然后,该实际值成为其自己的本地原语.但是对于对象,我们所做的只是给同一个对象一个额外的引用(一个本地引用),这样我们现在有两个引用指向同一个对象.
我希望这个简单的解释有所帮助
Java始终是按值传递的,参数是传递的变量的副本,所有对象都是使用引用定义的,而reference是一个变量,用于存储对象在内存中的存储地址。
检查注释以了解执行中会发生什么;按照数字显示执行流程。
class Example
{
public static void test (Cat ref)
{
// 3 - <ref> is a copy of the reference <a>
// both currently reference Grumpy
System.out.println(ref.getName());
// 4 - now <ref> references a new <Cat> object named "Nyan"
ref = new Cat("Nyan");
// 5 - this should print "Nyan"
System.out.println( ref.getName() );
}
public static void main (String [] args)
{
// 1 - a is a <Cat> reference that references a Cat object in memory with name "Grumpy"
Cat a = new Cat("Grumpy");
// 2 - call to function test which takes a <Cat> reference
test (a);
// 6 - function call ends, and <ref> life-time ends
// "Nyan" object has no references and the Garbage
// Collector will remove it from memory when invoked
// 7 - this should print "Grumpy"
System.out.println(a.getName());
}
}
Run Code Online (Sandbox Code Playgroud)
传值的底线:被调用的方法不能改变调用者的变量,尽管对于对象引用变量,被调用的方法可以改变变量引用的对象。改变变量和改变对象有什么区别?对于对象引用,这意味着被调用的方法不能重新分配调用者的原始引用变量并使其引用不同的对象,或者为 null。
我从一本关于 Java 认证的书中获取了这段代码和解释,并做了一些小改动。
我认为这是对对象按值传递的一个很好的说明。在下面的代码中,重新分配 g 不会重新分配 f!在 bar() 方法的最后,创建了两个 Foo 对象,一个由局部变量 f 引用,另一个由局部(参数)变量 g 引用。
因为 doStuff() 方法有一个引用变量的副本,所以它有一种获取原始 Foo 对象的方法,例如调用 setName() 方法。但是, doStuff() 方法无法访问 f 引用变量。所以 doStuff() 可以改变 f 所指对象内的值,但是 doStuff() 不能改变 f 的实际内容(位模式)。换句话说,doStuff() 可以改变 f 所引用的对象的状态,但它不能让 f 引用不同的对象!
package test.abc;
public class TestObject {
/**
* @param args
*/
public static void main(String[] args) {
bar();
}
static void bar() {
Foo f = new Foo();
System.out.println("Object reference for f: " + f);
f.setName("James");
doStuff(f);
System.out.println(f.getName());
//Can change the state of an object variable in f, but can't change the object reference for f.
//You still have 2 foo objects.
System.out.println("Object reference for f: " + f);
}
static void doStuff(Foo g) {
g.setName("Boo");
g = new Foo();
System.out.println("Object reference for g: " + g);
}
}
package test.abc;
public class Foo {
public String name = "";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,以下控制台输出中的对象引用未更改:
控制台输出:
f 的对象引用:test.abc.Foo@62f72617
g 的对象引用:test.abc.Foo@4fe5e2c3
f 的 Boo 对象参考:test.abc.Foo@62f72617
小智 5
Java按值传递参数,但对于对象变量,值实际上是对象的引用.由于数组是对象,因此以下示例代码显示了差异.
public static void dummyIncrease(int[] x, int y)
{
x[0]++;
y++;
}
public static void main(String[] args)
{
int[] arr = {3, 4, 5};
int b = 1;
dummyIncrease(arr, b);
// arr[0] is 4, but b is still 1
}
main()
arr +---+ +---+---+---+
| # | ----> | 3 | 4 | 5 |
+---+ +---+---+---+
b +---+ ^
| 1 | |
+---+ |
|
dummyIncrease() |
x +---+ |
| # | ------------+
+---+
y +---+
| 1 |
+---+
Run Code Online (Sandbox Code Playgroud)
Java 按值传递一切!!
//通过传入名称和年龄创建一个对象:
PersonClass variable1 = new PersonClass("Mary", 32);
PersonClass variable2;
Run Code Online (Sandbox Code Playgroud)
//变量2和变量1现在引用同一个对象
variable2 = variable1;
PersonClass variable3 = new PersonClass("Andre", 45);
Run Code Online (Sandbox Code Playgroud)
// 变量 1 现在指向变量 3
variable1 = variable3;
Run Code Online (Sandbox Code Playgroud)
//这是什么输出?
System.out.println(variable2);
System.out.println(variable1);
Mary 32
Andre 45
Run Code Online (Sandbox Code Playgroud)
如果你能理解这个例子,我们就完成了。否则,请访问此网页以获取详细说明:
It seems everything is call by value in java as i have tried to understand by the following program
Class-S
class S{
String name="alam";
public void setName(String n){
this.name=n;
}}
Run Code Online (Sandbox Code Playgroud)
Class-Sample
public class Sample{
public static void main(String args[]){
S s=new S();
S t=new S();
System.out.println(s.name);
System.out.println(t.name);
t.setName("taleev");
System.out.println(t.name);
System.out.println(s.name);
s.setName("Harry");
System.out.println(t.name);
System.out.println(s.name);
}}
Run Code Online (Sandbox Code Playgroud)
Output
alam
alam
taleev
alam
taleev
harry
As we have define class S with instance variable name with value taleev so for all the objects that we initialize from it will have the name variable with value of taleev but if we change the name's value of any objects then it is changing the name of only that copy of the class(Object) not for every class so after that also when we do System.out.println(s.name) it is printing taleev only we can not change the name's value that we have defined originally, and the value that we are changing is the object's value not the instance variable value so once we have define instance variable we are unable to change it
So i think that is how it shows that java deals with values only not with the references
为原始变量的内存分配可以通过理解 这个
我试图简化上面的示例,仅保留问题的实质。让我将其作为一个易于记忆和正确应用的故事来介绍。故事是这样的:您有一只宠物狗吉米(Jimmy),尾巴长12英寸。您出国旅行时,会把它交给兽医待几个星期。
兽医不喜欢吉米的长尾巴,所以他想把它切成两半。但是,作为一名好兽医,他知道自己无权残害他人的狗。因此,他首先制作了一只狗的克隆体(使用了新的关键字),然后剪下了克隆体的尾巴。当狗终于回到你身边时,它的原始尾巴整齐了。美好结局 !
下次旅行时,您会无意中将狗带到邪恶的兽医那里。他还讨厌长尾巴,因此将其削减到了2英寸。但是他是对您亲爱的吉米(Jimmy)这么做的,而不是对它的模仿。当您返回时,您会震惊地看到Jimmy可怜地摇着2英寸的存根。
故事的寓意:当您传授宠物时,您便将宠物的全部和不受约束的监护权交给了兽医。他可以自由发挥任何破坏作用。按值传递,按引用传递,按指针传递都只是技术上的争执。除非兽医首先克隆它,否则他最终将残破原始狗。
public class Doggie {
public static void main(String...args) {
System.out.println("At the owner's home:");
Dog d = new Dog(12);
d.wag();
goodVet(d);
System.out.println("With the owner again:)");
d.wag();
badVet(d);
System.out.println("With the owner again(:");
d.wag();
}
public static void goodVet (Dog dog) {
System.out.println("At the good vet:");
dog.wag();
dog = new Dog(12); // create a clone
dog.cutTail(6); // cut the clone's tail
dog.wag();
}
public static void badVet (Dog dog) {
System.out.println("At the bad vet:");
dog.wag();
dog.cutTail(2); // cut the original dog's tail
dog.wag();
}
}
class Dog {
int tailLength;
public Dog(int originalLength) {
this.tailLength = originalLength;
}
public void cutTail (int newLength) {
this.tailLength = newLength;
}
public void wag() {
System.out.println("Wagging my " +tailLength +" inch tail");
}
}
Output:
At the owner's home:
Wagging my 12 inch tail
At the good vet:
Wagging my 12 inch tail
Wagging my 6 inch tail
With the owner again:)
Wagging my 12 inch tail
At the bad vet:
Wagging my 12 inch tail
Wagging my 2 inch tail
With the owner again(:
Wagging my 2 inch tail
Run Code Online (Sandbox Code Playgroud)
首先让我们了解 Java 中的内存分配:堆栈和堆是 JVM 为不同目的分配的内存的一部分。堆栈内存在创建时预先分配给线程,因此一个线程无法访问其他线程的堆栈。但是 Heap 可用于程序中的所有线程。
对于一个线程,Stack 存储所有本地数据、程序元数据、原始类型数据和对象引用。并且,堆负责实际对象的存储。
Book book = new Book("Effective Java");
Run Code Online (Sandbox Code Playgroud)
在上面的例子中,引用变量是“book”,它存储在堆栈中。new operator -> new Book("Effective Java") 创建的实例存储在 Heap 中。引用变量“book”具有在堆中分配的对象的地址。假设地址是 1001。
考虑传递原始数据类型,即 int、float、double 等。
public class PrimitiveTypeExample {
public static void main(string[] args) {
int num = 10;
System.out.println("Value before calling method: " + num);
printNum(num);
System.out.println("Value after calling method: " + num);
}
public static void printNum(int num){
num = num + 10;
System.out.println("Value inside printNum method: " + num);
}
}
Run Code Online (Sandbox Code Playgroud)
输出为:调用方法前的值:10 方法内部的值:20 调用方法后的值:10
整数编号 = 10; -> 这会在正在运行的线程的堆栈中为“int”分配内存,因为它是一种原始类型。现在,当调用 printNum(..) 时,会在同一个线程中创建一个私有堆栈。将“num”传递给此方法时,会在方法堆栈帧中创建“num”的副本。数量=数量+10;-> 这会增加 10 并修改方法堆栈帧中的 int 变量。因此,方法栈帧外的原始 num 保持不变。
考虑将自定义类的对象作为参数传递的示例。
在上面的例子中,ref变量“book”驻留在执行程序的线程栈中,当程序执行new Book()时,在Heap空间中创建了类Book的对象。Heap 中的这个内存位置由“book”引用。当“book”作为方法参数传递时,在同一线程堆栈内的方法的私有堆栈帧中创建“book”的副本。因此,复制的引用变量指向 Heap 中类“Book”的同一个对象。
方法堆栈帧中的引用变量为同一对象设置了一个新值。因此,当原始引用变量“book”获取其值时会反映出来。请注意,在传递引用变量的情况下,如果在被调用的方法中再次初始化它,它将指向新的内存位置,任何操作都不会影响 Heap 中的先前对象。
因此,当任何东西作为方法参数传递时,它始终是 Stack 实体——原始变量或引用变量。我们从不传递存储在堆中的东西。因此,在Java中,我们总是在堆栈中传递值,并且是按值传递。
我会用另一种方式说:
在java中,引用被传递(但不是对象),并且这些引用是按值传递的(引用本身被复制,因此您有2个引用,并且您无法控制方法中的第一个引用)。
只是说:按值传递对于初学者来说可能不够清楚。例如,在Python中,同样的情况,但有文章描述了他们调用它pass-by-reference,仅使用原因引用。
Java 总是按值传递,而不是按引用传递
首先,我们需要了解什么是值传递和引用传递。
按值传递意味着您正在内存中复制传入的实际参数的值。这是实际参数内容的副本。
通过引用传递(也称为通过地址传递)是指存储了实参地址的副本。
有时 Java 会给人一种通过引用传递的错觉。让我们通过下面的例子来看看它是如何工作的:
public class PassByValue {
public static void main(String[] args) {
Test t = new Test();
t.name = "initialvalue";
new PassByValue().changeValue(t);
System.out.println(t.name);
}
public void changeValue(Test f) {
f.name = "changevalue";
}
}
class Test {
String name;
}
Run Code Online (Sandbox Code Playgroud)
这个程序的输出是:
changevalue
Let's understand step by step:
Run Code Online (Sandbox Code Playgroud)
测试 t = 新测试();众所周知,它将在堆中创建一个对象并将引用值返回给 t。例如,假设 t 的值为 0x100234(我们不知道实际的 JVM 内部值,这只是一个示例)。
第一张图
new PassByValue().changeValue(t);
Run Code Online (Sandbox Code Playgroud)
将引用 t 传递给函数时,它不会直接传递对象 test 的实际引用值,而是会创建 t 的副本,然后将其传递给函数。由于它是按值传递的,因此它传递的是变量的副本,而不是它的实际引用。由于我们说 t 的值为 0x100234,因此 t 和 f 将具有相同的值,因此它们将指向同一个对象。
第二幅图
如果您使用引用 f 更改函数中的任何内容,它将修改对象的现有内容。这就是我们得到输出 changevalue 的原因,该值在函数中更新。
为了更清楚地理解这一点,请考虑以下示例:
public class PassByValue {
public static void main(String[] args) {
Test t = new Test();
t.name = "initialvalue";
new PassByValue().changeRefence(t);
System.out.println(t.name);
}
public void changeRefence(Test f) {
f = null;
}
}
class Test {
String name;
}
Run Code Online (Sandbox Code Playgroud)
这会抛出 NullPointerException 吗?不,因为它只传递引用的副本。在按引用传递的情况下,它可能会抛出 NullPointerException,如下所示:
第三张图
希望这会有所帮助。
为简单起见,
其pass reference by value:
public static void main(String[] args) {
Dog aDog = new Dog("Max");
Dog oldDog = aDog;
// we pass the object to foo
foo(aDog);
// aDog variable is still pointing to the "Max" dog when foo(...) returns
aDog.getName().equals("Max"); // true
aDog.getName().equals("Fifi"); // false
aDog == oldDog; // true
}
public static void foo(Dog d) {
d.getName().equals("Max"); // true
// change d inside of foo() to point to a new Dog instance "Fifi"
d = new Dog("Fifi");
d.getName().equals("Fifi"); // true
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1886445 次 |
| 最近记录: |