Java是“引用传递”还是“值传递”?

我一直认为Java使用引用传递

但是,我看到一篇博客文章声称Java使用按值传递

我不认为我理解他们所做的区别。

解释是什么?

2534801 次浏览

Java总是值传递,没有例外,曾经

那么,为什么任何人都会对此感到困惑,并认为Java是引用传递,或者认为他们有一个Java充当引用传递的例子?关键点是Java从未任何情况下提供了对物体本身值的直接访问。对对象的唯一访问是通过参考访问该对象。因为Java对象是通过引用总是访问的,而不是直接访问的,所以通常将字段和变量和方法参数称为对象,而学究地说它们只是对对象的引用混乱源于命名法的这种(严格来说是不正确的)变化。

所以,当调用一个方法时

  • 对于基元参数(intlong等),值传递为基元的的实际价值(例如3)。
  • 对于对象,值传递为对对象的引用的值。

因此,如果您有doSomething(foo)public void doSomething(Foo foo) { .. },则两个Fos复制了指向相同对象的参考文献

自然地,通过值传递对对象的引用看起来非常像(并且在实践中无法区分)通过引用传递对象。

Java按值传递引用。

因此,您无法更改传入的引用。

基本上,重新分配Object参数不会影响参数,例如:

private static void foo(Object bar) {bar = null;}
public static void main(String[] args) {String baz = "Hah!";foo(baz);System.out.println(baz);}

将打印出"Hah!"而不是null。这之所以有效是因为barbaz值的副本,它只是对"Hah!"的引用。如果它是实际的引用本身,那么foo将重新定义baznull

Java通过值传递对对象的引用。

术语“按值传递”和“按引用传递”在计算机科学中具有特殊的精确定义含义。这些含义与许多人第一次听到这些术语时的直觉不同。本次讨论中的大部分困惑似乎来自这一事实。

术语“按值传递”和“按引用传递”是在谈论变量。按值传递意味着变量的被传递给函数/方法。按引用传递意味着该变量的参考被传递给函数。后者为函数提供了一种更改变量内容的方法。

根据这些定义,Java总是按值传递。不幸的是,当我们处理包含对象的变量时,我们实际上是在处理称为参考文献的对象句柄,它也是按值传递的。这个术语和语义学很容易混淆许多初学者。

它是这样的:

public static void main(String[] args) {Dog aDog = new Dog("Max");Dog oldDog = aDog;
// we pass the object to foofoo(aDog);// aDog variable is still pointing to the "Max" dog when foo(...) returnsaDog.getName().equals("Max"); // trueaDog.getName().equals("Fifi"); // falseaDog == oldDog; // true}
public static void foo(Dog d) {d.getName().equals("Max"); // true// change d inside of foo() to point to a new Dog instance "Fifi"d = new Dog("Fifi");d.getName().equals("Fifi"); // true}

在上面的示例中,aDog.getName()仍然会返回"Max"main中的值aDog在函数foo中没有改变,Dog"Fifi"作为对象引用按值传递。如果它是通过引用传递的,那么main中的aDog.getName()将在调用foo后返回"Fifi"

同样:

public static void main(String[] args) {Dog aDog = new Dog("Max");Dog oldDog = aDog;
foo(aDog);// when foo(...) returns, the name of the dog has been changed to "Fifi"aDog.getName().equals("Fifi"); // true// but it is still the same dog:aDog == oldDog; // true}
public static void foo(Dog d) {d.getName().equals("Max"); // true// this changes the name of d to be "Fifi"d.setName("Fifi");}

在上面的例子中,Fifi是调用foo(aDog)后的狗的名字,因为对象的名字设置在foo(...)里面。food执行的任何操作,出于所有实际目的,它们都在aDog上执行,但没有可以更改变量aDog本身的值。

有关引用传递和值传递的更多信息,请参阅以下答案:https://stackoverflow.com/a/430958/6005228。这更彻底地解释了两者背后的语义学和历史,也解释了为什么Java和许多其他现代语言在某些情况下似乎都可以做到这一点。

长话短说,Java对象有一些非常奇特的属性。

一般来说,Java有直接通过值传递的原始类型(intboolchardouble等)。然后Java有对象(从java.lang.Object派生的所有东西)。对象实际上总是通过引用处理的(引用是你不能触摸的指针)。这意味着实际上,对象是通过引用传递的,因为引用通常不感兴趣。然而,这确实意味着你不能改变指向哪个对象,因为引用本身是通过值传递的。

这听起来很奇怪吗?让我们考虑一下C如何实现引用传递和值传递。在C中,默认约定是值传递。void foo(int x)逐个值传递一个int。void foo(int *x)是一个不想要int a的函数,而是一个指向int的指针:foo(&a)。可以将其与&运算符一起使用来传递变量地址。

把这个带到C++,我们就有了引用。引用基本上(在这种情况下)是隐藏等式指针部分的语法糖:void foo(int &x)foo(a)调用,编译器本身知道它是一个引用,而非引用a的地址应该被传递。Java,所有引用对象的变量实际上都是引用类型的,实际上强制大多数意图和目的按引用调用,而没有C++等提供的细粒度控制(和复杂性)。

区别,或者也许只是我记得的方式,因为我曾经和最初的海报一样的印象是:Java总是值传递。Java中的所有对象(在Java,除了原语之外的任何东西)都是引用。这些引用按值传递。

正如很多人之前提到的,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;}}

打印:

之前:a=2, b=3
后:a=2,b=3

发生这种情况是因为iA和iB是新的局部引用变量,它们与传递的引用具有相同的值(它们分别指向a和b)。因此,尝试更改iA或iB的引用只会在局部范围内更改,而不会在此方法之外。

我一直认为它是“通过复制传递”。它是值的副本,无论是原始还是引用。如果它是原始的,它是作为值的位的副本,如果它是对象,它是引用的副本。

public class PassByCopy{public static void changeName(Dog d){d.name = "Fido";}public static void main(String[] args){Dog d = new Dog("Maxx");System.out.println("name= "+ d.name);changeName(d);System.out.println("name= "+ d.name);}}class Dog{public String name;public Dog(String s){this.name = s;}}

java PassByCopy的输出:

name=Maxx
name=Fido

原始包装类和字符串是不可变的,因此使用这些类型的任何示例都不会与其他类型/对象相同。

我为任何编程语言这里创建了一个专门讨论此类问题的线程。

Java也被提及.以下是简短的摘要:

  • Java通过值传递参数
  • “by value”是Java中将参数传递给方法的唯一方法
  • 使用作为参数给出的对象的方法将改变对象作为引用指向原始对象(如果方法本身会改变一些值)

我刚刚注意到你引用了我的文章

Java规范说Java中的所有内容都是按值传递的,Java中没有“按引用传递”。

理解这一点的关键是

Dog myDog;

没有一只狗;它实际上是指针一只狗。在Java中使用术语“引用”是非常误导的,也是造成这里大部分混乱的原因。他们所说的“引用”更像我们在大多数其他语言中所说的“指针”。

这意味着,当你有

Dog myDog = new Dog("Rover");foo(myDog);

您实际上是将创建的Dog对象的详细地址传递给foo方法。

(我说本质上是因为Java指针/引用不是直接地址,但这样想是最容易的。

假设Dog对象驻留在内存地址42。这意味着我们将42传递给方法。

如果方法定义为

public void foo(Dog someDog) {someDog.setName("Max");     // AAAsomeDog = new Dog("Fifi");  // BBBsomeDog.setName("Rowlf");   // CCC}

让我们看看发生了什么。

  • 参数someDog设置为值42
  • 在“AAA”行
    • someDog后面跟着它指向的Dog(地址42处的Dog对象)
    • 0号(地址42的那个)被要求改名为Max
  • 在BBB行
    • 一个新的Dog被创建了。假设他在地址74
    • 我们将参数someDog分配给74
  • 在"CCC"行
    • 在它指向的Dog(地址74处的Dog对象)之后,会跟随一些狗。
    • 0号(地址74的那个)被要求改名为Rowlf
  • 然后,我们返回

现在让我们考虑一下方法之外会发生什么:

myDog改变了吗?

这是钥匙。

请记住myDog指针,而不是实际的Dog,答案是NO.myDog仍然具有值42;它仍然指向原始的Dog(但请注意,由于行“AAA”,它的名称现在是“Max”-仍然是相同的狗;myDog的值没有改变。

它是完全有效的遵循一个地址,并改变它的结尾;这不会改变变量,然而。

Java的工作原理与C完全相同。您可以分配一个指针,将指针传递给方法,按照方法中的指针并更改指向的数据。但是,调用者不会看到您对指针指向的位置所做的任何更改。(在具有引用传递语义学的语言中,方法函数可以更改指针,调用者会看到该更改。)

在C++,Ada,Pascal和其他支持引用传递的语言中,您实际上可以更改传递的变量。

如果Java有引用传递语义学,我们上面定义的foo方法在BBB线上分配someDog时会改变myDog指向的位置。

将引用参数视为传入变量的别名。当分配该别名时,传入的变量也是如此。

更新

评论中的讨论需要一些澄清…

在C中,你可以写

void swap(int *x, int *y) {int t = *x;*x = *y;*y = t;}
int x = 1;int y = 2;swap(&x, &y);

这不是C中的特例。两种语言都使用按值传递语义学。这里调用站点正在创建额外的数据结构来帮助函数访问和操作数据。

该函数被传递指向数据的指针,并遵循这些指针来访问和修改该数据。

在Java中,调用者设置辅助结构的类似方法可能是:

void swap(int[] x, int[] y) {int temp = x[0];x[0] = y[0];y[0] = temp;}
int[] x = {1};int[] y = {2};swap(x, y);

(或者,如果您希望两个示例都演示另一种语言没有的功能,请创建一个可变的IntWrapper类来代替数组)

在这些情况下,C和Java都是模拟引用传递,它们仍然传递值(指向int或数组的指针),并在被调用的函数内跟随这些指针来操作数据。

引用传递是关于函数声明/定义以及它如何处理其参数的。引用语义学适用于对该函数的调用,调用站点只需要传递变量,不需要额外的数据结构。

这些模拟需要调用站点和函数协作。毫无疑问,它很有用,但它仍然是按值传递的。

为了显示对比,比较以下C++Java片段:

C++:注意:错误的代码-内存泄漏!但它证明了这一点。

void cppMethod(int val, int &ref, Dog obj, Dog &objRef, Dog *objPtr, Dog *&objPtrRef){val = 7; // Modifies the copyref = 7; // Modifies the original variableobj.SetName("obj"); // Modifies the copy of Dog passedobjRef.SetName("objRef"); // Modifies the original Dog passedobjPtr->SetName("objPtr"); // Modifies the original Dog pointed to// by the copy of the pointer passed.objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer,// leaving the original object alone.objPtrRef->SetName("objRefPtr"); // Modifies the original Dog pointed to// by the original pointer passed.objPtrRef = new Dog("newObjPtrRef"); // Modifies the original pointer passed}
int main(){int a = 0;int b = 0;Dog d0 = Dog("d0");Dog d1 = Dog("d1");Dog *d2 = new Dog("d2");Dog *d3 = new Dog("d3");cppMethod(a, b, d0, d1, d2, d3);// a is still set to 0// b is now set to 7// d0 still have name "d0"// d1 now has name "objRef"// d2 now has name "objPtr"// d3 now has name "newObjPtrRef"}

Java,

public static void javaMethod(int val, Dog objPtr){val = 7; // Modifies the copyobjPtr.SetName("objPtr") // Modifies the original Dog pointed to// by the copy of the pointer passed.objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer,// leaving the original object alone.}
public static void main(){int a = 0;Dog d0 = new Dog("d0");javaMethod(a, d0);// a is still set to 0// d0 now has name "objPtr"}

Java只有两种类型的传递:对于内置类型按值传递,对于对象类型按指针的值传递。

这有点难以理解,但Java总是复制值-重点是,通常值是引用。因此,您最终会得到相同的对象而不会考虑它…

问题的关键在于“引用传递”中的参考与Java中的参考完全不同。

通常在Java参考表示引用一个对象。但是编程语言理论中的技术术语引用传递/值谈论的是引用保存变量的存储单元,这是完全不同的东西。

您永远不能在Java中引用传递,其中一个显而易见的方法是当您想从方法调用中返回多个值时。考虑C++中的以下代码:

void getValues(int& arg1, int& arg2) {arg1 = 1;arg2 = 2;}void caller() {int x;int y;getValues(x, y);cout << "Result: " << x << " " << y << endl;}

有时你想在Java中使用相同的模式,但你不能;至少不能直接。相反,你可以这样做:

void getValues(int[] arg1, int[] arg2) {arg1[0] = 1;arg2[0] = 2;}void caller() {int[] x = new int[1];int[] y = new int[1];getValues(x, y);System.out.println("Result: " + x[0] + " " + y[0]);}

正如在前面的答案中解释的那样,在Java你将指向数组的指针作为值传递给getValues。这就足够了,因为该方法然后修改数组元素,并且按照惯例,你期望元素0包含返回值。显然,你可以通过其他方式做到这一点,例如构建代码以使这不是必要的,或者构建一个可以包含返回值或允许设置它的类。但是上面C++中提供的简单模式在Java中不可用。

据我所知,Java只知道按值调用。这意味着对于原始数据类型,您将使用副本,而对于对象,您将使用对对象的引用的副本。然而,我认为有一些陷阱;例如,这是行不通的:

public static void swap(StringBuffer s1, StringBuffer s2) {StringBuffer temp = s1;s1 = s2;s2 = temp;}

public static void main(String[] args) {StringBuffer s1 = new StringBuffer("Hello");StringBuffer s2 = new StringBuffer("World");swap(s1, s2);System.out.println(s1);System.out.println(s2);}

这将填充Hello World而不是World Hello,因为在交换函数中,您使用的副本对main中的引用没有影响。但是如果您的对象不是不可变的,您可以更改它,例如:

public static void appendWorld(StringBuffer s1) {s1.append(" World");}
public static void main(String[] args) {StringBuffer s = new StringBuffer("Hello");appendWorld(s);System.out.println(s);}

这将在命令行上填充Hello World。如果您将StringBuffer更改为String,它将只生成Hello,因为String是不可变的。例如:

public static void appendWorld(String s){s = s+" World";}
public static void main(String[] args) {String s = new String("Hello");appendWorld(s);System.out.println(s);}

但是,您可以像这样为String制作一个包装器,这将使其能够与Strings一起使用:

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);}

编辑:我相信这也是在“添加”两个String时使用StringBuffer的原因,因为您可以修改原始对象,而您无法使用像String这样的不可变对象。

对一些帖子进行了一些更正。

C不支持引用传递。它总是值传递。C++支持引用传递,但不是默认值,而且非常危险。

Java中的值是什么并不重要:对象的基元或地址(大致),它总是按值传递。

如果一个Java对象的“行为”就像它通过引用传递一样,那是一个可变的属性,与传递机制完全无关。

我不知道为什么这是如此令人困惑,也许是因为这么多Java“程序员”没有经过正式培训,因此不明白内存中到底发生了什么?

Java按值复制引用。因此,如果您将其更改为其他内容(例如,使用new),则引用不会在方法外部更改。对于本机类型,它始终是值传递的。

看看这段代码。这段代码不会抛出NullPointerException…它会打印“Vinay”

public class Main {public static void main(String[] args) {String temp = "Vinay";print(temp);System.err.println(temp);}
private static void print(String temp) {temp = null;}}

如果Java引用传递,那么它应该抛出NullPointerException,因为引用设置为Null。

我不敢相信还没有人提到Barbara Liskov。当她在1974年设计CLU时,她遇到了同样的术语问题,她发明了术语共享呼叫(也称为对象共享调用对象调用)来解决“通过值调用,其中值是引用”的特定情况。

这将给你一些关于Java如何真正工作的见解,以至于在你下一次关于Java通过引用传递或通过值传递的讨论中,你只会微笑:-)

第一步,请从脑海中抹去以“p”开头的单词_______”,特别是如果你来自其他编程语言。Java和“p”不能写在同一本书、论坛甚至txt中。

第二步请记住,当您将Object传递给方法时,您传递的是Object引用,而不是Object本身。

  • 学生:主人,这是否意味着Java是按引用传递的?
  • 大师:蝗虫,不。

现在想想Object的引用/变量的作用/是什么:

  1. 变量包含告诉JVM如何到达内存中引用的对象(堆)的位。
  2. 当将参数传递给方法您不是传递引用变量,而是传递引用变量中位的副本时。类似这样的东西:3bad086a。3bad086a代表了一种获取传递对象的方法。
  3. 所以你只是传递3bad086a,它是引用的值。
  4. 您传递的是引用的值,而不是引用本身(而不是对象)。
  5. 此值实际上是复制并给定给方法

在下面(请不要尝试编译/执行此…):

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 nice6. static void changeName(Person anotherReferenceToTheSamePersonObject) {7.     anotherReferenceToTheSamePersonObject.setName("Jerry");8. }

会发生什么?

  • 变量是在第1行创建的,它在开头为null。
  • 在第2行创建一个新的Person对象,存储在内存中,变量被赋予对Person对象的引用。即它的地址。假设3bad086a。
  • 保存Object地址的变量被传递给第#3行的函数。
  • 在第4行,你可以听到寂静的声音
  • 查看第5行的评论
  • 创建了一个方法局部变量-对同一个人对象的另一个引用-,然后在第6行出现了魔法:
    • 变量/引用被逐位复制并传递给函数内部的对同一个人对象的另一个引用
    • 没有创建Person的新实例。
    • ”和“对同一个人对象的另一个引用”都包含相同的值3bad086a。
    • 不要尝试这样做,但人==antherRe的SameHomeObject将是true。
    • 两个变量都有引用的相同副本,它们都引用同一个Person对象,堆上的同一个对象,而不是一个副本。

一张图片胜过千言万语:

按值传递

请注意,ANTHERLECENCETOTHAMEIGERObject箭头指向对象,而不是指向变量人!

如果你没有得到它,那么请相信我,并记住最好说Java值传递。好吧,引用传递值。哦,好吧,更好的是变量值的复制传递!;)

现在请随意讨厌我,但请注意,在谈论方法参数时,给定这个传递原始数据类型和对象之间没有区别

您总是传递引用值的位的副本!

  • 如果它是原始数据类型,这些位将包含原始数据类型本身的值。
  • 如果它是一个Object,则位将包含地址的值,该地址告诉JVM如何到达Object。

Java是按值传递的,因为在方法内部,你可以随心所欲地修改引用的对象,但无论你怎么努力,你都无法修改传递的变量,该变量将继续引用(不是p_______)相同的对象!


上面的ChangeName函数将永远无法修改传递引用的实际内容(位值)。换句话说,ChangeName不能使Person人引用另一个对象。


当然,你可以缩短它,只是说Java是按值传递!

一切都是按值传递的。基元和对象引用。但是对象可以更改,如果它们的接口允许的话。

当您将对象传递给方法时,您正在传递一个引用,并且该对象可以通过方法实现进行修改。

void bithday(Person p) {p.age++;}

对象本身的引用按值传递:您可以重新分配参数,但更改不会反映回来:

void renameToJon(Person p) {p = new Person("Jon"); // this will not work}
jack = new Person("Jack");renameToJon(jack);sysout(jack); // jack is unchanged

实际上,“p”是引用(指向对象的指针),不能更改。

基本类型按值传递。对象的引用也可以被视为基本类型。

总结一下,一切都是通过值传递的。

这真的非常非常简单:

对于基本类型的变量(例如。intbooleanchar等…),当您将其名称用于方法参数时,您传递的是其中包含的值(5true'c')。此值会被“复制”,即使在方法调用后,变量也会保留其值。

对于引用类型的变量(例如。StringObject等…),当您将其名称用于方法参数时,您传递的是其中包含的值(“指向”对象的引用值)。这个参考值被“复制”,即使在方法调用之后,变量也会保留其值。引用变量保持“指向”同一个对象。

无论哪种方式,你总是按值传递东西。


比较一下C++你可以有一个方法来获取int&,或者在C#中你可以获取ref int(尽管在这种情况下,在将变量的名称传递给方法时,你也必须使用ref修饰符)。

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");}
}

我将分步骤解释这一点:

  1. 声明一个类型为Foo的名为f的引用,并为其分配一个具有属性"f"的类型为Foo的新对象。

    Foo f = new Foo("f");

    输入图片描述

  2. 从方法端,声明了一个名称为a的类型Foo的引用,并最初分配了null

    public static void changeReference(Foo a)

    输入图片描述

  3. 当您调用方法changeReference时,引用a将被分配作为参数传递的对象。

    changeReference(f);

    输入图片描述

  4. 声明一个类型为Foo的名为b的引用,并为其分配一个具有属性"b"的类型为Foo的新对象。

    Foo b = new Foo("b");

    输入图片描述

  5. a = b对属性为"b"的对象的引用a没有f进行了新的赋值。

    输入图片描述

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

    输入图片描述

  7. c.setAttribute("c");将更改引用c指向它的对象的属性,并且引用f指向它的对象是同一个对象。

    输入图片描述

我希望你现在明白在Java中传递对象作为参数是如何工作的:)

不,这不是引用传递。

Java根据Java语言规范进行值传递:

当调用方法或构造函数时(第15.12节),在执行方法或构造函数主体之前,实际参数表达式的值初始化新创建的参数变量,每个声明的类型。出现在DeclaratorId中的标识符可以用作方法或构造函数主体中的简单名称来引用形式参数

Java是通过常量引用传递,其中传递引用的副本,这意味着它基本上是一个值传递。如果类是可变的,您可以更改引用的内容,但不能更改引用本身。换句话说,地址不能更改,因为它是通过值传递的,但地址指向的内容可以更改。对于不可变类,引用的内容也不能更改。

Java总是按值传递,参数是变量传递的副本,所有对象都是使用引用定义的,引用是存储对象在内存中的内存地址的变量。

检查注释以了解执行中发生的事情;跟随数字,因为它们显示了执行的流程。

class Example{public static void test (Cat ref){// 3 - <ref> is a copy of the reference <a>// both currently reference GrumpySystem.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> referencetest (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());}}

为了增加更多的内容,我想我应该在这个主题上包括SCJP学习指南部分。这是为了通过Sun/Oracle对Java行为的测试而编写的指南,所以这是一个很好的讨论来源。

将变量传递给方法(目标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. }

当我们运行这个类时,我们可以看到修改()方法确实能够修改在第4行创建的原始(也是唯一的)Dimension对象。

C:\Java Projects\Reference>java ReferenceTestBefore modify() d.height = 10dim = 11After modify() d.height = 11

请注意,当第4行的Dimension对象被传递给修改()方法时,在方法内部发生的对对象的任何更改都将被传递给其引用的对象。在前面的例子中,引用变量d和dim都指向同一个对象。

Java使用按值传递语义吗?

如果Java通过传递引用变量来传递对象,这是否意味着Java对对象使用引用传递?不完全是,尽管您会经常听到和阅读到它。Java实际上是对单个VM中运行的所有变量的按值传递。按值传递意味着按变量值传递。这意味着,按变量复制传递!(又有复制这个词了!)

传递原始变量或引用变量没有区别,你总是传递变量中位的副本。因此,对于原始变量,您传递的是表示值的位的副本。例如,如果您传递值为3的int变量,则传递的是表示3的位的副本。然后被调用的方法会获得自己的值副本,以随心所欲地处理它。

如果你传递的是一个对象引用变量,你传递的是一个表示对一个对象的引用的位的副本。然后被调用的方法会得到它自己的引用变量的副本,可以随心所欲地使用它。但是因为两个相同的引用变量引用的是完全相同的对象,如果被调用的方法修改了对象(例如通过调用setter方法),调用者将看到调用者原始变量引用的对象也被更改了。在下一节中,我们将看看当我们谈论原语时图片是如何变化的。

按值传递的底线:被调用的方法不能改变调用者的变量,尽管对于对象引用变量,被调用的方法可以改变变量引用的对象。改变变量和改变对象有什么区别?

        void bar() {Foo f = new Foo();doStuff(f);}void doStuff(Foo g) {g.setName("Boo");g = new Foo();}

重新分配g不会重新分配f!在bar()方法结束时,创建了两个Foo对象,一个由局部变量f引用,一个由局部变量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);}}

在这个简单的程序中,变量a被传递给一个名为修改()的方法,它将变量增加1。结果输出如下所示:

  Before modify() a = 1number = 2After modify() a = 1

请注意,a在传递给方法后没有改变。请记住,它是传递给方法的a的副本。当原始变量传递给方法时,它是按值传递的,这意味着按变量中的位的副本传递。

Java只有值传递。

public void test() {MyClass obj = null;init(obj);//After calling init method, obj still points to null//this is because obj is passed as value and not as reference.}private void init(MyClass objVar) {objVar = new MyClass();}

最短答案:)

  • Java具有按值传递(和按值传递引用)。
  • C#也有引用传递

在C#中,这是通过“out”和“ref”关键字完成的。

按引用传递:变量以方法内部的重新分配甚至在方法外部也会反映出来。的方式传递

下面是引用传递的一个例子(C#)。此功能在java中不存在。

class Example{static void InitArray(out int[] arr){arr = new int[5] { 1, 2, 3, 4, 5 };}
static void Main(){int[] someArray;InitArray(out someArray);
// This is true !boolean isTrue = (someArray[0] == 1);}}

另见:MSDN库(C#):通过ref和out传递数组

另见:MSDN库(C#):按值和引用传递

按值传递的底线:被调用的方法不能更改调用者的变量,尽管对于对象引用变量,被调用的方法可以更改对象引用的变量。更改变量有什么区别和改变对象?对于对象引用,这意味着被调用的方法不能重新分配调用者的原始引用变量并使其引用不同的对象,或空。

我从一本关于Java认证的书中获取了这段代码和解释,并做了一些小的修改。我认为这是一个很好地说明了对象的值传递。在下面的代码中,重新赋值g不会重新赋值f!在bar()方法结束时,两个Foo对象已创建,一个由局部变量f引用,一个由局部变量f引用局部(参数)变量g。

因为doStuff()方法有一个引用变量的副本,所以它有一种方法可以获取到原始Foo对象,例如调用setName()方法。但是,doStuff()方法没有办法到达f引用变量。因此doStuff()可以更改f引用的对象内的值to,但doStuff()不能更改f的实际内容(位模式)也就是说,doStuff()可以改变f引用的对象的状态,但它不能让f指向另一个对象!

package test.abc;
public class TestObject {
/*** @param args*/public static void main(String[] args) {bar();}
static void bar() {Foo f = new Foo();System.out.println("Object reference for f: " + f);f.setName("James");doStuff(f);System.out.println(f.getName());//Can change the state of an object variable in f, but can't change the object reference for f.//You still have 2 foo objects.System.out.println("Object reference for f: " + f);}
static void doStuff(Foo g) {g.setName("Boo");g = new Foo();System.out.println("Object reference for g: " + g);}}

package test.abc;
public class Foo {public String name = "";
public String getName() {return name;}
public void setName(String name) {this.name = name;}
}

请注意,下面的控制台输出中的对象引用没有更改:

控制台输出:

f:test.abc.Foo@62f72617的对象引用

g:test.abc.Foo@4fe5e2c3的对象引用

嘘对象引用f:test.abc.Foo@62f72617

我觉得争论“按引用传递与按值传递”并不是很有帮助。

如果你说,“Java是通过什么(引用/值)”,无论哪种情况,你都没有提供一个完整的答案。这里有一些额外的信息,希望有助于理解内存中发生的事情。

堆栈/堆速成课程在我们到达Java实现之前:价值以一种有序的方式在堆栈中上下移动,就像自助餐厅里的一堆盘子一样。堆中的内存(也称为动态内存)是杂乱无章的。JVM只是尽可能地找到空间,并将其释放出来,因为不再需要使用它的变量。

好的。首先,本地原语放在堆栈上。所以这段代码:

int x = 3;float y = 101.1f;boolean amIAwesome = true;

结果是:

堆栈上的原语

当你声明和实例化一个对象时。实际的对象会进入堆栈。堆栈上有什么?堆栈上对象的地址。C++程序员会称之为指针,但是一些Java开发人员反对“指针”这个词。不管怎样。只要知道对象的地址在堆栈上。

像这样:

int problems = 99;String name = "Jay-Z";

一个b*7ch不是一个!

数组是一个对象,所以它也在堆上。数组中的对象呢?它们有自己的堆空间,每个对象的地址都在数组中。

JButton[] marxBros = new JButton[3];marxBros[0] = new JButton("Groucho");marxBros[1] = new JButton("Zeppo");marxBros[2] = new JButton("Harpo");

马克思兄弟

那么,当你调用一个方法时,会传入什么呢?如果你传入一个对象,你实际上传入的是对象的地址。有人可能会说地址的“值”,也有人说它只是对对象的引用。这就是“引用”和“值”支持者之间圣战的起源。你称之为什么并不重要,因为你明白传入的是对象的地址。

private static void shout(String name){System.out.println("There goes " + name + "!");}
public static void main(String[] args){String hisName = "John J. Jingleheimerschmitz";String myName = hisName;shout(myName);}

一个String被创建并在堆中为其分配空间,字符串的地址存储在堆栈上并给出标识符hisName,因为第二个String的地址与第一个相同,没有创建新的String,也没有分配新的堆空间,而是在堆栈上创建了一个新的标识符。然后我们调用shout():创建一个新的堆栈帧,并创建一个新的标识符name,并为已经存在的String分配地址。

la da di da da da da

所以,价值,参考?你说"土豆"。

Java按值传递参数,但对于对象变量,这些值本质上是对对象的引用。由于数组是对象,以下示例代码显示了差异。

public static void dummyIncrease(int[] x, int y){x[0]++;y++;}public static void main(String[] args){int[] arr = {3, 4, 5};int b = 1;dummyIncrease(arr, b);// arr[0] is 4, but b is still 1}
main()arr +---+       +---+---+---+| # | ----> | 3 | 4 | 5 |+---+       +---+---+---+b   +---+             ^| 1 |             |+---+             ||dummyIncrease()         |x   +---+             || # | ------------++---+y   +---+| 1 |+---+

Java总是值传递而不是引用传递

首先,我们需要了解什么是值传递和引用传递。

按值传递意味着您正在内存中复制传入的实际参数的值。这是实际参数内容的副本

引用传递(也称为地址传递)意味着存储实际参数地址的副本

有时Java会给人一种引用传递的错觉。让我们通过使用下面的示例来看看它是如何工作的:

public class PassByValue {public static void main(String[] args) {Test t = new Test();t.name = "initialvalue";new PassByValue().changeValue(t);System.out.println(t.name);}    
public void changeValue(Test f) {f.name = "changevalue";}}
class Test {String name;}

该程序的输出是:

changevalue

让我们一步一步来理解:

Test t = new Test();

众所周知,它将在堆中创建一个对象并将引用值返回给t。例如,假设t的值是0x100234(我们不知道实际的JVM内部值,这只是一个例子)。

第一幅插图

new PassByValue().changeValue(t);

当将引用t传递给函数时,它不会直接传递对象test的实际引用值,但它会创建t的副本,然后将其传递给函数。由于它是按值传递,它传递的是变量的副本而不是它的实际引用。由于我们说t的值是0x100234,t和f都将具有相同的值,因此它们将指向同一个对象。

第二幅插图

如果您使用引用f更改函数中的任何内容,它将修改对象的现有内容。这就是为什么我们得到输出changevalue,它在函数中更新。

为了更清楚地理解这一点,请考虑以下示例:

public class PassByValue {public static void main(String[] args) {Test t = new Test();t.name = "initialvalue";new PassByValue().changeRefence(t);System.out.println(t.name);}    
public void changeRefence(Test f) {f = null;}}
class Test {String name;}

这会抛出NullPointerException吗?不,因为它只传递引用的副本。在通过引用传递的情况下,它可能抛出NullPointerException,如下所示:

第三幅插图

希望这会有帮助。

Java编程语言仅按值传递参数,即:您不能从内部更改调用方法中的参数值被调用的方法。


但是,当对象实例作为参数传递给方法时,参数的值不是对象本身,而是对对象的引用对象。您可以在调用的方法中更改对象的内容,但不是对象引用。


对许多人来说,这看起来像是通过引用传递,并且在行为上,它具有但是,有两个原因这是不正确的

  • 首先,改变事物的能力传递给方法仅适用于对象,而不是原始值。

  • 第二,实际与对象类型的变量关联的值是对客体,而不是客体本身。这是其他方法,如果清楚地理解,完全支持这一点Java编程语言按值传递参数。


The following code example illustrates this point:1 public class PassTest {23   // Methods to change the current values4   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   }1314 public static void main(String args[]) {15     MyDate date;16     int val;1718     // Assign the int19     val = 11;20     // Try to change it21     changeInt(val);22     // What is the current value?23     System.out.println("Int value is: " + val);2425 // Assign the date26     date = new MyDate(22, 7, 1964);27     // Try to change it28     changeObjectRef(date);29     // What is the current value?30 System.out.println("MyDate: " + date);3132 // Now change the day attribute33     // through the object reference34     changeObjectAttr(date);35     // What is the current value?36 System.out.println("MyDate: " + date);37   }38 }

This code outputs the following:java PassTestInt value is: 11MyDate: 22-7-1964MyDate: 4-7-1964The MyDate object is not changed by the changeObjectRef method;however, the changeObjectAttr method changes the day attribute of theMyDate object.

在java中,一切都是引用,所以当你有这样的东西时:Point pnt1 = new Point(0,0);Java执行以下操作:

  1. 创建新的Point对象
  2. 在先前创建的Point对象上创建新的Point引用并将该引用初始化为点(指)
  3. 从这里,通过Point对象生命,您将通过pnt1访问该对象引用。所以我们可以说在Java你通过它的引用来操作对象。

输入图片描述

Java不通过引用传递方法参数;它通过值传递它们。我将使用本网站的示例:

public static void tricky(Point arg1, Point arg2) {arg1.x = 100;arg1.y = 100;Point temp = arg1;arg1 = arg2;arg2 = temp;}public static void main(String [] args) {Point pnt1 = new Point(0,0);Point pnt2 = new Point(0,0);System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y);System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);System.out.println(" ");tricky(pnt1,pnt2);System.out.println("X1: " + pnt1.x + " Y1:" + pnt1.y);System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);}

程序的流程:

Point pnt1 = new Point(0,0);Point pnt2 = new Point(0,0);

创建两个不同的Point对象,并关联两个不同的引用。在此处输入图片描述

System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y);System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);System.out.println(" ");

预期的产出将是:

X1: 0     Y1: 0X2: 0     Y2: 0

在这条线上,“按值传递”进入了游戏…

tricky(pnt1,pnt2);           public void tricky(Point arg1, Point arg2);

引用pnt1pnt2是棘手方法的pnt21,这意味着现在你的引用pnt1pnt2copies名为arg1arg2。所以pnt1arg1pnt22指向同一个对象。(pnt2arg2也是如此)在此处输入图片描述

tricky方法中:

 arg1.x = 100;arg1.y = 100;

输入图片描述

接下来是tricky方法

Point temp = arg1;arg1 = arg2;arg2 = temp;

在这里,您首先创建新的temp Point引用,它将放在与arg1引用相同的位置。然后您将引用arg1移动到到与arg2引用相同的位置。最后arg2temp一样放在同一个地方。

输入图片描述

从这里开始,tricky方法的范围消失了,您无法再访问引用:arg1arg2temp但重要的是,当这些引用“在生活中”时,你对它们所做的一切都会永久地影响它们指向的对象。

所以在执行方法tricky之后,当你返回到main时,你会遇到这种情况:在此处输入图片描述

所以现在,程序的完全执行将是:

X1: 0         Y1: 0X2: 0         Y2: 0X1: 100       Y1: 100X2: 0         Y2: 0

Java通过值传递对对象的引用。

因此,如果对引用参数指向的Object进行任何修改,它将反映回原始对象。

但是,如果引用参数指向另一个Object,则原始引用将指向原始Object。

引用在表示时始终是一个值,无论您使用什么语言。

获得一个开箱即用的视图,让我们看看Assembly或一些低级内存管理。在CPU级别,如果任何参考被写入内存或CPU寄存器之一,它立即成为。(这就是为什么指针是一个很好的定义。它是一个值,同时有一个目的)。

内存中的数据有一个位置,在该位置有一个值(字节、单词等)。在汇编中,我们有一个方便的解决方案,将姓名赋予某个位置(又名变量),但是在编译代码时,汇编器只需将姓名替换为指定位置,就像您的浏览器将域名替换为IP地址一样。

从技术上讲,在任何语言中传递对任何东西的引用而不表示它是不可能的(当它立即成为一个值时)。

假设我们有一个变量Foo,它的位置在内存中的第47个字节,它的是5。我们有另一个变量Ref2Foo在内存中的第223个字节,它的值将是47。这个Ref2Foo可能是一个技术变量,不是由程序显式创建的。如果你只看5和47而没有任何其他信息,你会看到只有两个价值观。如果你使用它们作为参考,那么要达到5,我们必须旅行:

(Name)[Location] -> [Value at the Location]---------------------(Ref2Foo)[223]  -> 47(Foo)[47]       -> 5

这就是跳转表的工作方式。

如果我们想用Foo的值调用方法/函数/过程,有几种可能的方法将变量传递给方法,具体取决于语言及其几种方法调用模式:

  1. 5被复制到其中一个CPU寄存器(即EAX)。
  2. 5获取PUSHd到堆栈。
  3. 47被复制到其中一个CPU寄存器
  4. 47 PUSHd到堆栈。
  5. 223被复制到CPU寄存器之一。
  6. 223获取PUSHd到堆栈。

在创建了一个值(现有值的复制)以上的每种情况下,现在由接收方法来处理它。当你在方法中写“Foo”时,它要么从EAX读出,要么自动取消引用,或者双重取消引用,这个过程取决于语言的工作方式和/或Foo的类型决定。这对开发人员是隐藏的,直到她绕过取消引用过程。所以参考在表示时是,因为引用是一个必须处理的值(在语言级别)。

现在我们已经将Foo传递给方法:

  • 在情况1.和2.中,如果您更改Foo(Foo = 9),它只会影响本地范围,因为您拥有Value的副本。从方法内部,我们甚至无法确定原始Foo在内存中的位置。
  • 在情况3.和4.如果你使用默认语言结构并更改Foo(Foo = 11),它可以全局更改Foo(取决于语言,即Java或类似Pascal的procedure findMin(x, y, z: integer;var m: integer);)。但是,如果语言允许你绕过取消引用过程,你可以更改47,例如更改为49。此时,如果你阅读了Foo,Foo似乎已经被更改,因为你已经将procedure findMin(x, y, z: integer;0更改为它。如果你要在方法(Foo = 12)中修改这个Foo,你可能会FUBAR程序的执行(又名。段错误),因为你将写入到与预期不同的内存,你甚至可以修改注定要容纳可执行程序的区域,写入它将修改正在运行的代码(Foo现在不在47)。但是Foo的值47并没有全局变化,只有方法内部的值,因为47也是方法的副本。
  • 在情况5.和6.如果你在方法内部修改223,它会产生与3.或4.(一个指针,指向一个现在坏的值,它再次被用作指针)相同的混乱,但这仍然是一个局部问题,因为223是复制。然而,如果你能够取消引用Ref2Foo(即223),到达并修改指向的值47,比如说,到49,它将影响FooRef2Foo0,因为在这种情况下,方法得到了223的副本,但引用的47只存在一次,将其更改为49将导致每个Ref2Foo双取消引用到一个错误的值。

挑剔无关紧要的细节,即使是进行引用传递的语言也会将值传递给函数,但这些函数知道它们必须使用它来取消引用。这种将引用传递为值的做法只是对程序员隐藏,因为它实际上毫无用处,术语只有引用传递

严格的按值传递也是无用的,这意味着每次我们以数组为参数调用方法时都必须复制一个100Mbyte的数组,因此Java不能严格地按值传递。每种语言都会传递一个对这个巨大数组的引用(作为一个值),并且如果该数组可以在方法内部本地更改,则采用写时复制机制,或者允许方法(就像Java一样)全局修改数组(从调用者的角度),并且一些语言允许修改引用本身的值。

因此,简而言之,用Java自己的术语,Java是按值传递,其中可以是:实际价值,表示参考

@Scott Stanchfield先生写了一个很好的答案。这是你需要验证的类这是什么意思:

public class Dog {
String dog ;static int x_static;int y_not_static;
public String getName(){return this.dog;}
public Dog(String dog){this.dog = dog;}
public void setName(String name){this.dog = name;}
public static void foo(Dog someDog){x_static = 1;// y_not_static = 2;  // not possible !!someDog.setName("Max");     // AAAsomeDog = new Dog("Fifi");  // BBBsomeDog.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在方法调用中使用了什么

当调用方法或构造函数时(§15.12),实际参数表达式初始化新创建的参数变量,每个声明的类型,在执行主体之前方法或构造函数。

因此,它将参数的值分配(或绑定)给相应的参数变量。

论证的价值是什么?

让我们考虑引用类型,Java虚拟机规格状态

有三种引用类型:类类型、数组类型、它们的值是对动态创建的类实例、数组或类实例或数组分别实现接口。

Java语言规范也表示

引用值(通常只是引用)是指向这些对象的指针,以及一个特殊的空引用,它不引用任何对象。

参数(某些引用类型)的值是指向对象的指针。请注意,变量、具有引用类型返回类型的方法调用和实例创建表达式(new ...)都解析为引用类型值。

所以

public void method (String param) {}...String variable = new String("ref");method(variable);method(variable.toString());method(new String("ref"));

都将对String实例的引用的值绑定到方法新创建的参数param。这正是按值传递的定义所描述的。因此,Java是按值传递的

您可以跟随引用来调用方法或访问被引用对象的字段这一事实与对话完全无关。引用传递的定义是

这通常意味着函数可以修改(即分配给)变量用作参数-它的调用者将看到的东西。

Java,修改变量意味着重新分配它。Java,如果您在方法中重新分配变量,调用者将不会注意到它。修改变量引用的对象是一个完全不同的概念。


原值也在Java虚拟机规范这里中定义。类型的值是相应的整数或浮点值,适当编码(8、16、32、64等位)。

理解它在两个步骤:

您不能更改对对象本身的引用,但您可以使用此传递的参数作为对对象的引用。

如果您想更改引用后面的值,您只会在堆栈上声明一个同名为'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}

在所有答案中,我们看到Java按值传递,或者更确切地说是@Gevorg他写道:“传递-复制-变量-值”,这是我们应该一直牢记的想法。

我专注于帮助我理解这个想法的例子,这是对以前答案的补充。

在Java你总是通过复制传递参数;也就是说,你总是在函数内部创建一个值的新实例。但是有一些行为会让你认为你是通过引用传递的。

  • 通过复制传递:当一个变量传递给一个方法/函数时,会产生一个副本(有时我们会听到当你传递原语时,你正在复制)。

  • 通过引用传递:当一个变量被传递给方法/函数时,方法/函数中的代码会对原始变量进行操作(你仍然通过复制传递,但是对复杂对象内部值的引用是变量两个版本的一部分,包括原始版本和函数内部的版本。复制复杂对象本身,但保留内部引用)

通过复制/通过值传递的示例

来自[参考文献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)

来自[参考文献2]的示例

很好地显示了机制最多看5分钟

(通过引用传递)按变量值的副本传递

来自[参考文献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)

复制复杂对象本身,但保留内部引用。

来自[参考文献3]的示例

package com.pritesh.programs;
class Rectangle {int length;int width;
Rectangle(int l, int b) {length = l;width = b;}
void area(Rectangle r1) {int areaOfRectangle = r1.length * r1.width;System.out.println("Area of Rectangle : "+ areaOfRectangle);}}
class RectangleDemo {public static void main(String args[]) {Rectangle r1 = new Rectangle(10, 20);r1.area(r1);}}

矩形的面积为200,长度=10,宽度=20

最后一件事我想分享的是讲座的这一刻:内存分配我发现这对理解按值传递的Java非常有帮助,或者更确切地说,“传递-复制-变量-值”,正如@Gevorg所写的那样。

  1. 参考1Lynda.com
  2. REF 2
  3. REF 3

Java中有一个解决方法供参考。让我通过这个例子来解释:

public class Yo {public static void foo(int x){System.out.println(x); //out 2x = x+2;System.out.println(x); // out 4}public static void foo(int[] x){System.out.println(x[0]); //1x[0] = x[0]+2;System.out.println(x[0]); //3}public static void main(String[] args) {int t = 2;foo(t);System.out.println(t); // out 2 (t did not change in foo)
int[] tab = new int[]{1};foo(tab);System.out.println(tab[0]); // out 3 (tab[0] did change in foo)}}

我希望这有帮助!

简单的程序

import java.io.*;class Aclass{public int a;}public class test{public static void foo_obj(Aclass obj){obj.a=5;}public static void foo_int(int a){a=3;}public static void main(String args[]){//test passing an objectAclass ob = new Aclass();ob.a=0;foo_obj(ob);System.out.println(ob.a);//prints 5
//test passing an integerint i=0;foo_int(i);System.out.println(i);//prints 0}}

从C/C++程序员的角度来看,java使用值传递,因此对于原始数据类型(int、char等),函数中的更改不会反映在调用函数中。但是当您传递一个对象并在函数中更改其数据成员或调用可以更改对象状态的成员函数时,调用函数将获得更改。

Java通过价值传递一切!

//通过传入名称和年龄创建一个对象:

PersonClass variable1 = new PersonClass("Mary", 32);
PersonClass variable2;

//变量2和变量1现在都引用同一个对象

variable2 = variable1;

PersonClass variable3 = new PersonClass("Andre", 45);

//变量1现在指向变量3

variable1 = variable3;

//这个的输出是什么?

System.out.println(variable2);System.out.println(variable1);
Mary 32Andre 45

如果你能理解这个例子,我们就完成了。否则,请访问此网页进行详细说明:

网页

Java通过值和值只有传递参数。

长话短说:

对于来自C#的人:没有“out”参数。

对于来自PASCAL的人:没有“var”参数

这意味着您不能从对象本身更改引用,但您始终可以更改对象的属性。

一种解决方法是使用StringBuilder参数而不是String。您可以随时使用数组!

让我试着用四个例子来解释我的理解。Java是按值传递,而不是按引用传递

/**

按值传递

在Java中,所有参数都按值传递,即分配方法参数对调用者不可见。

*/

示例1:

public class PassByValueString {public static void main(String[] args) {new PassByValueString().caller();}
public void caller() {String value = "Nikhil";boolean valueflag = false;String output = method(value, valueflag);/** 'output' is insignificant in this example. we are more interested in* 'value' and 'valueflag'*/System.out.println("output : " + output);System.out.println("value : " + value);System.out.println("valueflag : " + valueflag);
}
public String method(String value, boolean valueflag) {value = "Anand";valueflag = true;return "output";}}

结果

output : outputvalue : Nikhilvalueflag : false

示例2:

/****通过值**/

public class PassByValueNewString {public static void main(String[] args) {new PassByValueNewString().caller();}
public void caller() {String value = new String("Nikhil");boolean valueflag = false;String output = method(value, valueflag);/** 'output' is insignificant in this example. we are more interested in* 'value' and 'valueflag'*/System.out.println("output : " + output);System.out.println("value : " + value);System.out.println("valueflag : " + valueflag);
}
public String method(String value, boolean valueflag) {value = "Anand";valueflag = true;return "output";}}

结果

output : outputvalue : Nikhilvalueflag : false

示例3:

/**这个“按值传递”有一种“按引用传递”的感觉

有些人说原始类型和'String'是'值传递'对象是“引用传递”。

但是从这个例子中,我们可以理解它实际上只是值传递,请记住,这里我们将引用作为值传递。即:引用按值传递。这就是为什么能够改变并且在局部范围之后仍然成立的原因。但是我们不能改变原始范围之外的实际引用。下一个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;}@Overridepublic String toString() {return "Student [id=" + id + ", name=" + name + "]";}}
public static void main(String[] args) {new PassByValueObjectCase1().caller();}
public void caller() {Student student = new Student(10, "Nikhil");String output = method(student);/** 'output' is insignificant in this example. we are more interested in* 'student'*/System.out.println("output : " + output);System.out.println("student : " + student);}
public String method(Student student) {student.setName("Anand");return "output";}}

结果

output : outputstudent : Student [id=10, name=Anand]

例4:

/**

除了示例3(PassByValueObjectCase1.java)中提到的内容外,我们不能更改原始范围之外的实际引用。

注意:我没有粘贴private class Student的代码。Student的类定义与示例3相同。

*/

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 seeStudent student = new Student(10, "Nikhil");String output = method(student);/** 'output' is insignificant in this example. we are more interested in* 'student'*/System.out.println("output : " + output);System.out.println("student : " + student); // Will it print Nikhil or Anand?}
public String method(Student student) {student = new Student(20, "Anand");return "output";}
}

结果

output : outputstudent : Student [id=10, name=Nikhil]

Java总是使用按值调用。这意味着该方法获取所有参数值的副本。

考虑以下三种情况:

1)尝试更改原始变量

public static void increment(int x) { x++; }
int a = 3;increment(a);

x将复制a的值并递增x,a保持不变

2)尝试更改对象的原始字段

public static void increment(Person p) { p.age++; }
Person pers = new Person(20); // age = 20increment(pers);

p将复制per的引用值并增加age字段,变量引用同一个对象,因此age会改变

3)尝试更改引用变量的引用值

public static void swap(Person p1, Person p2) {Person temp = p1;p1 = p2;p2 = temp;}
Person pers1 = new Person(10);Person pers2 = new Person(20);swap(pers1, pers2);

调用交换p1后,p2从pers1和pers2复制引用值,与值交换,因此pers1和pers2保持不变

所以。您只能更改方法中的对象字段,将引用值的副本传递给此对象。

这么长的答案。让我给一个简单的:

  • Java总是按值传递一切
  • 这意味着引用也是按值传递的

简而言之,您不能修改传递的任何参数的值,但您可以调用方法或更改传递的对象引用的属性。

我做了这个小图表,展示了数据是如何被创建和传递的

数据创建和传递方式示意图

注意:原始值作为值传递,对该值的第一个引用是方法的参数

这意味着:

  • 您可以更改函数myObject里面的值
  • 但是你不能在函数内部改变myObject引用的内容,因为point不是myObject
  • 请记住,pointmyObject都是参考文献,不同的参考文献,然而,这些引用在同一个new Point(0,0)

围绕这个问题的很多困惑来自于Java试图重新定义“按值传递”和“按引用传递”的含义。重要的是要理解这些是行业术语,离开该上下文无法正确理解。它们旨在帮助您编码并且有价值理解,所以让我们首先了解它们的含义。

可以找到两者的良好描述这里

按值传递函数收到的值是调用者正在使用的对象的副本。它对函数来说完全是唯一的,你对该对象所做的任何事情都只会在函数中看到。

引用传递函数接收到的值是对调用者正在使用的对象的引用。调用者将看到函数对值引用的对象所做的任何事情,并且从那时起它将处理这些更改。

从这些定义中可以清楚地看出,引用按值传递的事实是无关紧要的。如果我们接受这个定义,那么这些术语就变得毫无意义,所有语言都只是按值传递。

无论你如何传递引用,它都只能按值传递。这不是重点。重点是你向函数传递了对自己对象的引用,而不是它的副本。你可以丢弃收到的引用这一事实无关紧要。同样,如果我们接受了这个定义,这些术语变得毫无意义,每个人都总是按值传递。

不,C++的特殊“引用传递”语法不是引用传递的唯一定义。它纯粹是一种方便的语法,旨在使您在传递指针后不需要使用指针语法。它仍然在传递指针,编译器只是对您隐藏了这一事实。它还在传递指针BY VALUE,编译器只是对您隐藏了这一点。

因此,通过这种理解,我们可以查看Java并看到它实际上具有两者。所有Java基本类型总是值传递,因为您收到调用者对象的副本并且不能修改它们的副本。所有Java引用类型总是引用传递,因为您收到对调用者对象的引用并且可以直接修改它们的对象。

您不能修改调用者的引用这一事实与引用传递无关,在每种支持引用传递的语言中都是如此。

有一个简单的方法来理解这一点。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;}

结果如何?

119999

因此,bar()为传入的“引用”分配了一个新值后,它实际上更改了从main本身传入的值,解释了main print 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);}
}

它说:

11991

Voilà,在main中传递给bar的Foo的引用仍然保持不变!

这个例子清楚地表明,当我们说“引用传递”时,java与C++不一样。本质上,java将“引用”作为“值”传递给函数,这意味着java是值传递。

在Java,方法参数都是按值传递的:

Java参数为全部按值传递(方法使用时复制值或引用):

对于原始类型,Java行为很简单:该值被复制到原始类型的另一个实例中。

对于对象,这是相同的:对象变量是使用“new”关键字创建的引用(仅包含Object的详细地址而不是原始值的mem存储桶),并像原始类型一样复制。

行为可能与原始类型不同:因为复制的对象变量包含相同的地址(到相同的对象)。对象的内容/成员可能仍然在方法中被修改,然后在方法之外访问,给人一种错觉,即(包含)对象本身是通过引用传递的。

“字符串”对象似乎是一个很好的反例,城市传说说“对象通过引用传递”:

实际上,使用方法,您将永远无法更新作为参数传递的String的值:

字符串对象,由声明为最终的数组保存无法修改的字符。只有对象的地址可以被另一个使用“new”替换。使用“new”来更新变量,不会让Object从外部访问,因为变量最初是按值传递并复制的。

Java通过引用操作对象,所有对象变量都是引用。但是,Java不通过引用传递方法参数;它通过值传递它们。

以badSwap()方法为例:

    public void badSwap(int var1, intvar2{ int temp = var1; var1 = var2; var2 =temp; }

当badSwap()返回时,作为参数传递的变量仍将保留其原始值。如果我们将参数类型从int更改为Object,该方法也将失败,因为Java也按值传递对象引用。现在,这是它变得棘手的地方:

public void tricky(Point arg1, Point   arg2){ arg1.x = 100; arg1.y = 100; Point temp = arg1; arg1 = arg2; arg2 = temp; }public static void main(String [] args) {
Point pnt1 = new Point(0,0); Point pnt2= new Point(0,0); System.out.println("X:" + pnt1.x + " Y: " +pnt1.y);
System.out.println("X: " + pnt2.x + " Y:" +pnt2.y); System.out.println(" ");
tricky(pnt1,pnt2);System.out.println("X: " + pnt1.x + " Y:" + pnt1.y);
System.out.println("X: " + pnt2.x + " Y: " +pnt2.y); }

如果我们执行这个main()方法,我们会看到以下输出:

X: 0 Y: 0 X: 0 Y: 0 X: 100 Y: 100 X: 0 Y: 0

该方法成功地改变了pnt1的值,即使它是按值传递的;然而,pnt1和pnt2的交换失败了!这是混淆的主要根源。在medain()方法中,pnt1和pnt2只不过是对象引用。当您将pnt1和pnt2传递给tricky()方法时,Java像任何其他参数一样按值传递引用。这意味着传递给该方法的引用实际上是原始引用的副本。下面的图1显示了Java将对象传递给方法后指向同一对象的两个引用。

Java按值复制和传递引用,而不是对象。因此,方法操作将改变对象,因为引用指向原始对象。但是由于引用是副本,交换将失败。如图2所示,方法引用交换,但不引用原始引用。不幸的是,在方法调用后,你只剩下未交换的原始引用。为了在方法调用之外成功交换,我们需要交换原始引用,而不是副本。

检查语言是否支持引用传递的一个简单测试就是简单地编写一个传统的交换。你能在Java中编写传统的交换(a, b)方法/函数吗?

传统的交换方法或函数接受两个参数并交换它们,以便传递给函数的变量在函数之外发生变化。它的基本结构看起来像

(非Java)基本交换函数结构

swap(Type arg1, Type arg2) {Type temp = arg1;arg1 = arg2;arg2 = temp;}

如果您可以用您的语言编写这样的方法/函数,则调用

Type var1 = ...;Type var2 = ...;swap(var1,var2);

实际上切换变量var1和var2的值,语言支持按引用传递。但是Java不允许这样的事情发生,因为它只支持传递值,而不支持指针或引用。

主要的基础知识必须是引用的,

当对象引用传递给方法时,引用本身使用按值调用传递。但是,由于值是传递引用一个对象,该值的副本仍将引用由其相应参数引用的相同对象。

Java:初学者指南,第六版,赫伯特·希尔德特

似乎在java中一切都是按值调用的,因为我试图通过以下程序理解

S级

class S{String name="alam";public void setName(String n){this.name=n;}}

类样本

    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

哈利

因为我们定义了类S,实例变量名为值taleev,所以对于我们从它初始化的所有对象都将具有值为taleev的名称变量,但是如果我们更改任何对象的名称值,那么它只会更改类(Object)的副本的名称,而不是每个类的名称,因此当我们做System.out.println(s.name)时,它只打印taleev,我们无法更改我们最初定义的名称值,我们正在更改的值是对象的值而不是实例变量值,因此一旦我们定义了实例变量,我们就无法更改它

所以我认为这就是java处理而不是参考文献的方式

原始变量的内存分配可以用这个来理解

Java,毫无疑问,是“值传递”。此外,由于Java(主要)是面向对象的,并且对象使用引用,因此很容易混淆并将其视为“引用传递”

按值传递意味着将值传递给方法,如果方法更改了传递的值,则实际实体不会更改。另一方面,按引用传递意味着将引用传递给方法,如果方法更改了它,则传递的对象也会更改。

在Java,通常当我们将对象传递给方法时,我们基本上将对象的引用作为值传递,因为这就是Java的工作方式;它与引用和地址一起工作,就堆中的Object而言。

但是要测试它是否真的是值传递或引用传递,您可以使用原始类型和引用:

@Testpublic void sampleTest(){int i = 5;incrementBy100(i);System.out.println("passed ==> "+ i);Integer j = new Integer(5);incrementBy100(j);System.out.println("passed ==> "+ j);}/*** @param i*/private void incrementBy100(int i) {i += 100;System.out.println("incremented = "+ i);}

输出是:

incremented = 105passed ==> 5incremented = 105passed ==> 5

因此,在这两种情况下,方法内部发生的任何事情都不会改变真正的Object,因为传递的是该对象的值,而不是对对象本身的引用。

但是当您将自定义对象传递给方法以及该方法并更改它时,它也会更改真正的对象,因为即使您传递了对象,您也将其引用作为值传递给了方法。让我们再试一个例子:

@Testpublic 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;}
@Overridepublic String toString() {StringBuilder builder = new StringBuilder();builder.append("Person [age=");builder.append(age);builder.append(", name=");builder.append(name);builder.append("]");return builder.toString();}
}

在这种情况下,输出是:

Person [age=24, name=John]Person [age=45, name=Tom]

我试图简化上面的例子,只保留问题的本质。让我把它作为一个容易记住和正确应用的故事来介绍。故事是这样的:你有一只宠物狗,吉米,它的尾巴有12英寸长。你离开它与兽医几个星期,而你在国外旅行。

兽医不喜欢吉米的长尾巴,所以他想把它切成两半。但作为一名优秀的兽医,他知道自己没有权利残害别人的狗。所以他首先做了一个狗的克隆(带有新的关键词),并切断了克隆的尾巴。当狗终于回到你身边时,它有了原来的12英寸尾巴。快乐的结局!

下次你旅行的时候,你带着狗,不知不觉地,一个邪恶的兽医。他也是一个讨厌长尾的人,所以他把它削减到可怜的2英寸。但他这样对你亲爱的吉米,不是它的克隆。当你回来的时候,你惊讶地看到吉米可怜地摇着一个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 clonedog.cutTail(6);    // cut the clone's taildog.wag();}
public static void badVet (Dog dog) {System.out.println("At the bad vet:");dog.wag();dog.cutTail(2);   // cut the original dog's taildog.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 tailAt the good vet:Wagging my 12 inch tailWagging my 6 inch tailWith the owner again:)Wagging my 12 inch tailAt the bad vet:Wagging my 12 inch tailWagging my 2 inch tailWith the owner again(:Wagging my 2 inch tail

与其他一些语言不同,Java不允许您在按值传递和按引用传递之间进行选择。

所有参数都按值传递。

一个方法调用可以将两个types of values传递给一个方法

  • 基元值的副本(例如,int和Double类型的值)
  • 对象引用的副本。

Objects themselves cannot be passed to methods。当方法修改原始类型参数时,对参数的更改对调用方法中的原始参数值没有影响。

引用类型参数也是如此。如果您修改引用类型参数以使其引用另一个对象,则只有参数引用新对象-存储在调用者变量中的引用仍然引用原始对象。

参考文献:Java™如何编程(早期对象),第十版

Java是一个值传递(堆栈内存)

它是如何运作的

  • 让我们首先了解java存储原始数据类型和对象数据类型的位置。

  • 原始数据类型本身和对象引用存储在堆栈中。对象本身存储在堆中。

  • 这意味着,堆栈内存存储原始数据类型以及对象的地址。

  • 并且您始终传递引用值的位的副本。

  • 如果它是原始数据类型,那么这些复制的位包含原始数据类型本身的值,这就是为什么当我们在方法内部更改参数的值时,它不会反映外部的更改。

  • 如果它是一个像foo foo=新的foo()这样的对象数据类型,那么在这种情况下,对象地址的副本就像文件快捷方式一样传递,假设我们在C:\桌面有一个文本文件abc.txt,假设我们对同一个文件进行快捷方式并将其放在C:\桌面\abc-快捷方式中,所以当你从C:\abc.txt访问文件并写入堆栈溢出并关闭文件时,再次从快捷方式打开文件,然后写入'是程序员学习的最大在线社区',然后总文件更改将是“Stack Overflow是程序员学习的最大在线社区”,这意味着从哪里打开文件并不重要,每次我们访问同一个文件,这里我们可以假设Foo是一个文件,并假设foo存储在123hd7h(原始地址如C:\abc.txt))地址和abc.txt1(像C:\桌面\abc-快捷方式这样的复制地址,实际上包含了里面文件的原始地址)。所以为了更好地理解,让快捷方式文件和感觉。

Java值传递。

这个线程上已经有很好的答案了。不知何故,我从来都不清楚关于原始数据类型和对象的值传递/引用。因此,我用下面的代码测试了它,以提高我的满意度和清晰度;可能会帮助寻求类似清晰度的人:

class Test    {
public static void main (String[] args) throws java.lang.Exception{// Primitive typeSystem.out.println("Primitve:");int a = 5;primitiveFunc(a);System.out.println("Three: " + a);    //5
//ObjectSystem.out.println("Object:");DummyObject dummyObject = new DummyObject();System.out.println("One: " + dummyObject.getObj());    //555objectFunc(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);    //5b = 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指向同一个虚拟对象。

这个线程(https://stackoverflow.com/a/12429953/4233180)中的一个答案中的插图可以帮助获得更深入的理解。

编程语言中最大Java困惑之一是Java是按值传递还是通过引用传递

首先,我们应该了解什么是值传递或引用传递。

按值传递:方法参数值被复制到另一个变量,然后复制的对象被传递,这就是为什么它被称为值传递。

通过参考:对实际参数的别名或引用被传递给方法,这就是为什么它被称为引用传递。

假设我们有一个像下面这样的类气球。

public class Balloon {
private String color;
public Balloon(){}
public Balloon(String c){this.color=c;}
public String getColor() {return color;}
public void setColor(String color) {this.color = color;}}

我们有一个简单的程序,用一个泛型方法来交换两个对象,类如下所示。

public class Test {
public static void main(String[] args) {
Balloon red = new Balloon("Red"); //memory reference 50Balloon 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=100balloon.setColor("Red"); //baloon=100balloon = new Balloon("Green"); //baloon=200balloon.setColor("Blue"); //baloon = 200}
//Generic swap methodpublic static void swap(Object o1, Object o2){Object temp = o1;o1=o2;o2=temp;}}

当我们执行上述程序时,我们得到以下输出。

red color=Redblue color=Blueblue color=Red

如果您查看输出的前两行,很明显交换方法不起作用。这是因为Java是按值传递的,这个交换()方法测试可以与任何编程语言一起使用,以检查它是值传递还是引用传递。

让我们一步一步地分析程序执行。

Balloon red = new Balloon("Red");Balloon blue = new Balloon("Blue");

当我们使用new运算符创建类的实例时,实例被创建,变量包含保存对象的内存的引用位置。对于我们的例子,让我们假设“红色”指向50,“蓝色”指向100,这是两个Balloon对象的内存位置。

现在,当我们调用交换()方法时,会创建两个新变量o1和o2,它们分别指向50和100。

所以下面的代码片段解释了交换()方法执行中发生的事情。

public static void swap(Object o1, Object o2){ //o1=50, o2=100Object temp = o1; //temp=50, o1=50, o2=100o1=o2; //temp=50, o1=100, o2=100o2=temp; //temp=50, o1=100, o2=50} //method terminated

请注意,我们正在更改o1和o2的值,但它们是“红色”和“蓝色”参考位置的副本,因此实际上,“红色”和“蓝色”的值以及输出没有变化。

如果你已经了解到这一点,你就很容易理解混淆的原因。由于变量只是对对象的引用,我们感到困惑的是,我们正在传递引用,所以Java是通过引用传递的。然而,我们正在传递引用的副本,因此它是值传递的。我希望现在它消除了所有的疑虑。

现在让我们分析foo()方法执行。

private static void foo(Balloon balloon) { //baloon=100balloon.setColor("Red"); //baloon=100balloon = new Balloon("Green"); //baloon=200balloon.setColor("Blue"); //baloon = 200}

当我们调用一个方法时,第一行是重要的,该方法是在引用位置的Object上调用的。此时,气球指向100,因此它的颜色变为红色。

在下一行中,气球引用更改为200,并且执行的任何进一步方法都发生在内存位置200的对象上,并且对内存位置100的对象没有任何影响。这解释了我们程序输出的第三行打印蓝色=红色。

我希望上面的解释能消除所有的疑虑,只要记住变量是引用或指针,它的副本传递给方法,所以Java总是按值传递。当你了解堆和堆栈内存以及不同对象和引用的存储位置时,你会更清楚。

Java严格按值传递

当我说值传递时,它意味着每当调用者调用被调用者参数(即:要传递给另一个函数的数据)被复制并放置在形式参数(被调用者用于接收输入的局部变量)中。Java仅在值传递环境中从一个函数到另一个函数进行数据通信。

重要的一点是要知道即使是C语言也是严格按值传递的:
即:数据从调用者复制到被调用者,更重要的是被调用者执行的操作在同一个内存位置我们传递给它们的是我们从(&)运算符获得的该位置的地址,并且形式参数中使用的标识符被声明为指针变量(*),我们可以使用它进入内存位置以访问其中的数据。

因此,这里的形式参数只不过是该位置的别名。在该位置上所做的任何修改在变量(标识该位置)的范围还活着的地方都是可见的。

在Java中,没有指针的概念(即:没有什么叫做指针变量),尽管我们可以将引用变量视为技术上的指针在Java中我们将其称为句柄。我们在Java中将指向地址的指针称为句柄的原因是指针变量不仅能够执行单个解引用,而且能够执行多个解引用例如:P中的int *p;表示p指向一个整数C中的int **p;表示p是指向整数的指针我们在Java中没有这个功能,所以说它是一个句柄是绝对正确的,技术上是合法的,C中也有指针算术的规则,它允许对指针进行算术运算,并对其进行约束。

在C中,我们将这种传递地址并用指针变量接收它们的机制称为引用传递,因为我们传递它们的地址并将它们作为形式参数中的指针变量接收,但在编译器级别,地址被复制到指针变量中(因为这里的数据即使是它的数据也是地址),因此我们可以100%确定C是严格按值传递的(因为我们只传递数据)

(如果我们直接在C中传递数据,我们称之为值传递。

在Java中,当我们做同样的事情时,我们用句柄来做;因为它们不像在(如上所述)中那样被称为指针变量,即使我们传递引用,我们也不能说它的引用传递,因为我们没有在Java中使用指针变量收集它。

Java严格使用值传递机制

数据通过传递参数在函数之间共享。现在,有2种传递参数的方式:

  • 通过引用传递:调用者和被调用者使用相同的变量作为参数。

  • 按值传递:调用者和被调用者有两个具有相同值的独立变量。

Java使用值传递

  • 当传递原始数据时,它复制原始数据类型的值。
  • 传递对象时,它复制对象的地址并传递给被调用者方法变量。

Java在存储变量时遵循以下规则:

  • 本地变量(如原语和对象引用)是在Stack内存上创建的。
  • 对象在堆内存上创建。

使用原始数据类型的示例:

public class PassByValuePrimitive {public static void main(String[] args) {int i=5;System.out.println(i);  //prints 5change(i);System.out.println(i);  //prints 5}    
    
private static void change(int i) {System.out.println(i);  //prints 5i=10;System.out.println(i); //prints 10        
}}

使用对象的示例:

public class PassByValueObject {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("prem");list.add("raj");new PassByValueObject().change(list);System.out.println(list); // prints [prem, raj, ram]        
}    
    
private  void change(List list) {System.out.println(list.get(0)); // premlist.add("ram");list=null;System.out.println(list.add("bheem")); //gets NullPointerException}}

这是回答这个问题的最好方法。

首先,我们必须明白,在Java,参数传递行为

public void foo(Object param){// some code in foo...}
public void bar(){Object obj = new Object();
foo(obj);}

和…

public void bar(){Object obj = new Object();
Object param = obj;
// some code in foo...}

不考虑堆栈位置,这与本讨论无关。

所以,事实上,我们在Java中寻找的是变量赋值的工作原理。我在的文档中找到了它:

你会遇到的最常见的运算符之一是简单的赋值操作符"="[…]它赋予价值在其右侧到其左侧的操作数:

int节奏=0;
int速度=0;
int齿轮=1;

此运算符也可用于对象分配对象引用[…]

很明显,这个运算符以两种不同的方式起作用:赋值和分配引用。最后,当它是一个对象时……第一,当它不是一个对象时,也就是说,当它是一个基元时。但是,我们能理解Java的函数参数可以是按值传递引用传递吗?

真相就在代码中。让我们试试:

public class AssignmentEvaluation{static public class MyInteger{public int value = 0;}
static public void main(String[] args){System.out.println("Assignment operator evaluation using two MyInteger objects named height and width\n");
MyInteger height = new MyInteger();MyInteger width  = new MyInteger();
System.out.println("[1] Assign distinct integers to height and width values");
height.value = 9;width.value  = 1;
System.out.println("->  height is " + height.value + " and width is " + width.value + ", we are different things! \n");
System.out.println("[2] Assign to height's value the width's value");
height.value = width.value;
System.out.println("->  height is " + height.value + " and width is " + width.value + ", are we the same thing now? \n");
System.out.println("[3] Assign to height's value an integer other than width's value");
height.value = 9;
System.out.println("->  height is " + height.value + " and width is " + width.value + ", we are different things yet! \n");
System.out.println("[4] Assign to height the width object");
height = width;
System.out.println("->  height is " + height.value + " and width is " + width.value + ", are we the same thing now? \n");
System.out.println("[5] Assign to height's value an integer other than width's value");
height.value = 9;
System.out.println("->  height is " + height.value + " and width is " + width.value + ", we are the same thing now! \n");
System.out.println("[6] Assign to height a new MyInteger and an integer other than width's value");
height = new MyInteger();height.value = 1;
System.out.println("->  height is " + height.value + " and width is " + width.value + ", we are different things again! \n");}}

这是我运行的输出:

Assignment operator evaluation using two MyInteger objects named height and width
[1] Assign distinct integers to height and width values->  height is 9 and width is 1, we are different things!
[2] Assign to height's value the width's value->  height is 1 and width is 1, are we the same thing now?
[3] Assign to height's value an integer other than width's value->  height is 9 and width is 1, we are different things yet!
[4] Assign to height the width object->  height is 1 and width is 1, are we the same thing now?
[5] Assign to height's value an integer other than width's value->  height is 9 and width is 9, we are the same thing now!
[6] Assign to height a new MyInteger and an integer other than width's value->  height is 1 and width is 9, we are different things again!

[2]中,我们有不同的对象,并将一个变量的值分配给另一个变量。但是在[3]中分配一个新值后,对象有不同的值,这意味着在[2]中分配的值是原始变量的副本,通常称为按值传递,否则,[3]中打印的值应该相同。

[4]中,我们仍然有不同的对象,并将一个对象分配给另一个对象。在[5]中分配一个新值后,对象具有相同的值,这意味着在[4]中分配的对象不是另一个对象的副本,应该称为引用传递。但是,如果我们仔细看[6],我们不能确定没有复制…???

我们不能这么确定,因为在[6]中,对象是相同的,然后我们给其中一个分配了一个新对象,之后,对象有了不同的值!如果它们是一样的,现在怎么能区分呢?它们在这里也应该是一样的!????

我们需要记住的文档才能理解发生了什么:

此运算符也可用于对象以分配对象引用

所以我们的两个变量存储了引用……我们的变量在[4]之后有相同的引用,在[6]之后有不同的引用……如果这是可能的,这意味着对象的赋值是通过复制对象的引用来完成的,否则,如果不是引用的副本,[6]中变量的打印值应该是相同的。所以对象(引用),就像原语一样,通过赋值被复制到变量中,人们通常称之为按值传递。这是Java中唯一的传递

PT 1:房地产列表

有一个蓝色的120平方英尺的“小房子”目前停在1234 Main St,前面有一个修剪整齐的草坪和花坛。

一家当地公司的房地产经纪人被雇佣并被告知要为那所房子保留一份清单。

让我们称那个房地产经纪人为“鲍勃”。嗨,鲍勃。

Bob用网络摄像头更新他的房源,他称之为tinyHouseAt1234Main,可以让他实时注意到实际房子的任何变化。他还记录了有多少人询问过房源。鲍勃的整数viewTally今天是42。

每当有人想要关于主街1234号蓝色小房子的信息时,他们就会问鲍勃。

鲍勃查阅了他的清单tinyHouseAt1234Main,并告诉他们所有关于它的信息——颜色、漂亮的草坪、阁楼床和堆肥厕所等等。然后他将他们的询问添加到他的清单viewTally中。不过,他没有告诉他们真实的实际地址,因为鲍勃的公司专门从事可以随时移动的小房子。现在的数字是43。

在另一家公司,房地产经纪人可能会明确表示他们的上市“指向”主街1234号的房子,在旁边用一个小*表示,因为他们主要处理很少移动的房子(尽管可能有这样做的原因)。

当然,鲍勃不会亲自把0号房子放在卡车上,直接展示给客户看——这是不切实际的,也是荒谬的资源浪费。传递一份完整的理货单是一回事,但一直传递整个房子是昂贵和荒谬的。

(旁白:鲍勃的公司也不会在每次有人询问时3D打印上市房屋的新的和独特的副本。这就是新贵,类似于基于网络的公司及其衍生公司所做的-这既昂贵又缓慢,人们经常把这两家公司弄混,但无论如何他们都很受欢迎)。

在其他一些更靠近海边的老公司,像鲍勃这样的房地产经纪人甚至可能不存在来管理房源。客户可能会咨询罗乐德斯“安妮”(简称0)以获取房屋的直接地址。客户不是像鲍勃那样从房源中读取引用的房屋详细信息,而是从安妮(0)那里获得房屋地址,直接去主街1234号,有时不知道他们会在那里找到什么。

有一天,Bob的公司开始提供一项新的自动化服务,该服务需要客户感兴趣的房屋的列表。

好吧,拥有该信息的人是Bob,所以客户让Bob调用服务并向其发送列表的副本。

jobKillingAutomatedListingService(Listing tinyHouseAt1234Main, int viewTally) Bob送来…

该服务最终将此列表称为houseToLookAt,但实际上它收到的是Bob列表的精确副本,其中包含完全相同的VALUEs,指的是1234 Main St.

这项新服务也有自己的内部统计,记录有多少人查看了列表。该服务出于专业礼貌接受Bob的统计,但它并不真正关心,无论如何都会用自己的本地副本完全覆盖它。今天的统计是1,而Bob的仍然是43。

房地产公司称之为“按值传递”,因为Bob传递了他的viewTally和他的清单tinyHouseAt1234Main的当前值。他实际上并没有传递整个物理房子,因为这是不切实际的。他也没有像Annie(&)那样传递真实的物理地址。

但他正在传递一份的价值的副本,他对房子的引用。在某些方面似乎是一个愚蠢的迂腐的区别,但这就是他的公司的运作方式…………………………………………………………………………………

事情变得混乱和危险的地方…

新的自动化服务,不像其他一些时尚的金融和科学公司那样完全以功能和数学为导向,可能会产生不可预见的副作用。

一旦给定一个列表对象,它允许客户使用远程无人机机器人舰队实际上重新粉刷主街1234号的真正房子!它允许客户控制机器人推土机实际挖出花坛!这太疯狂了!!!

该服务还允许客户完全将houseToLookAt重定向到另一个地址的其他房子,而不涉及Bob或他的列表。突然之间,他们可能会看到4321 Elm St.,这与Bob的列表没有任何联系(谢天谢地,他们不能再造成损害)。

鲍勃在他的实时网络摄像头上观看这一切。他辞去了他唯一的工作责任的苦差事,他告诉客户新的丑陋的油漆工作&突然缺乏抑制吸引力。他的清单仍然是1234主街,毕竟。新服务的houseToLookAt无法改变这一点。鲍勃一如既往地准确而尽职尽责地报告他的tinyHouseAt1234Main的细节,直到他被解雇或房子被一无所有完全摧毁。

实际上,该服务对其Bob原始列表的houseToLookAt副本唯一不能做的就是将地址从1234 Main St.更改为其他地址,或者更改为虚无,或者更改为一些随机类型的对象,如鸭嘴兽。Bob的列表仍然总是指向1234 Main St,无论它仍然有什么价值。他像往常一样传递其当前值。

将列表传递给新的自动化服务的这种奇怪的副作用让那些询问它如何工作的人感到困惑。真的,远程控制机器人改变1234 Main房子状态的能力与实际上物理上去那里并造成严重破坏的能力有什么区别?因为安妮给了你地址?

如果你通常关心的是列表中房子的被复制和传递,这似乎是一种挑剔的语义争论,对吧?

我的意思是,如果你的业务是实际拿起房子并将它们物理移动到其他地址(不像移动或微型房屋,这是平台的预期功能),或者你正在访问,重命名,像某种低级的上帝扮演的疯子一样洗牌整个社区,那么也许你会更关心传递这些特定的地址引用,而不仅仅是房子细节的最新值的副本……

与其他一些语言不同,Java不允许您在按值传递和按引用传递之间进行选择-所有参数都是按值传递的。方法调用可以将两种类型的值传递给方法-原始值的副本(例如,int和Double的值)和对对象的引用的副本。

当方法修改基元类型参数时,对参数的更改对调用方法中的原始参数值没有影响。

当涉及到对象时,对象本身不能传递给方法。所以我们传递对象的引用(地址)。我们可以使用此引用操作原始对象。

Java如何创建和存储对象:当我们创建一个对象时,我们将对象的地址存储在引用变量中。让我们分析以下语句。

Account account1 = new Account();

“帐户帐户1”是引用变量的类型和名称,“=”是赋值操作符,“new”要求系统提供所需的空间量。关键字new右侧创建对象的构造函数由关键字new隐式调用。创建对象的地址(right值的结果,它是一个名为“类实例创建表达式”的表达式)使用赋值运算符分配给左值(它是一个具有指定名称和类型的引用变量)。

尽管对象的引用是按值传递的,但方法仍然可以通过使用对象引用的副本调用其公共方法来与被引用对象交互。由于存储在参数中的引用是作为参数传递的引用的副本,因此被调用方法中的参数和调用方法中的参数引用内存中的同一个对象。

传递对数组的引用,而不是数组对象本身,出于性能原因是有意义的。因为Java中的所有内容都是按值传递的,如果传递了数组对象,将传递每个元素的副本。对于大型数组,这会浪费时间和消耗为元素的副本提供大量存储空间。

在下面的图片中,你可以看到我们在main方法中有两个引用变量(在C/C++中称为指针,我认为这个术语更容易理解这个特性。)原始变量和引用变量保存在堆栈内存中(下图左侧)。array1和array2分别引用变量“point”(正如C/C++程序员所说)或对a和b数组的引用,它们是堆内存中的对象(这些引用变量保存的值是对象的地址)(下图右侧)。

按值传递示例1

如果我们将array1引用变量的值作为参数传递给反向数组方法,则会在方法中创建一个引用变量,并且该引用变量开始指向同一个数组(a)。

public class Test{public static void reverseArray(int[] array1){// ...}
public static void main(String[] args){int[] array1 = { 1, 10, -7 };int[] array2 = { 5, -190, 0 };
reverseArray(array1);}}

按值传递示例2

所以,如果我们说

array1[0] = 5;

在反向数组方法中,它将在数组a中进行更改。

我们有另一个引用变量在反向数组方法(array2)中指向数组c。如果我们说

array1 = array2;

然后,引用变量array1将停止指向数组a并开始指向数组c(第二张图像中的虚线)。

如果我们将引用变量array2的值作为方法的返回值返回,并将此值分配给main方法中的引用变量array1,main中的array1将开始指向数组c。

所以,让我们立刻写下我们现在所做的所有事情。

public class Test{public static int[] reverseArray(int[] array1){int[] array2 = { -7, 0, -1 };
array1[0] = 5; // array a becomes 5, 10, -7
array1 = array2; /* array1 of reverseArray startspointing to c instead of a (not shown in image below) */return array2;}
public static void main(String[] args){int[] array1 = { 1, 10, -7 };int[] array2 = { 5, -190, 0 };
array1 = reverseArray(array1); /* array1 ofmain starts pointing to c instead of a */}}

在此处输入图片描述

而现在反向数组方法结束了,它的引用变量(array1和array2)消失了。这意味着我们现在只有主方法array1和array2中分别指向c和b数组的两个引用变量。没有引用变量指向对象(数组)a。所以它有资格获得垃圾回收机制。

您还可以将main中array2的值分配给array1。array1将开始指向b。

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 variableSystem.out.println("after add " + data);}
public static void main(String args[]) {Example op = new Example();System.out.println("before change " + op.data);op.change(500);System.out.println("after change " + op.data);}}

输出:

before change 50after add 600after change 50

正如Michael在评论中所说:

对象仍然按值传递,即使对它们的操作表现得像引用传递。考虑void changePerson(Person person){ person = new Person(); },调用者对人对象的引用将保持不变。对象本身按值传递,但它们的成员可能会受到更改的影响。要成为真正的引用传递,我们必须能够将参数重新分配给一个新对象,并让更改反映在调用者中。

Java通过值传递基本类型,通过引用传递类类型

现在,人们喜欢无休止地争论“引用传递”是否是描述Java等人实际做的事情的正确方法。

  1. 传递对象不会复制该对象。
  2. 传递给函数的对象可以让函数修改其成员。
  3. 传递给函数的原始值不能被函数修改。复制。

在我的书中,这叫做参考传递。

-brianbi-引用传递哪些编程语言?

我想通过分享一个非常简单的例子(将编译)来做出一点贡献,它对比了c++中的按引用传递和Java中的按值传递的行为。

有几点:

  1. 术语“引用”有两个不同的含义。在Java它只是指一个指针,但在“按引用传递”的上下文中,它指的是传入的原始变量的句柄。
  2. Java是按值传递.Java是C的后裔(和其他语言一样)。在C之前,一些(但不是全部)早期语言,如FORTRAN和COBOL支持PBR,但C不支持。PBR允许这些其他语言对子例程内传递的变量进行更改。为了完成同样的事情(即更改函数内变量的值),C程序员将指向变量的指针传递给函数。受C启发的语言,如Java,借用了这一想法,并继续像C一样将指针传递给方法,只是Java将其指针称为引用。同样,这是“引用”一词与“通过引用”的不同用法。
  3. C++允许按引用传递通过使用“&”字符声明引用参数(恰好与C和C++中用于指示“变量地址”的字符相同)。例如,如果我们通过引用传入指针,参数和参数不仅仅指向同一个对象。相反,它们是同一个变量。如果一个被设置为不同的地址或null,另一个也会如此。
  4. 在下面的C++示例中,我将指针传递给以null结尾的字符串引用。在下面的Java示例中,我将Java引用按值传递给String(同样,与指向String的指针相同)。请注意注释中的输出。

C++引用传递示例:

using namespace std;#include <iostream>
void change (char *&str){   // the '&' makes this a reference parameterstr = NULL;}
int main(){char *str = "not Null";change(str);cout<<"str is " << str;      // ==>str is <null>}

Java通过值示例传递“Java引用”

public class ValueDemo{    
public void change (String str){str = null;}
public static void main(String []args){ValueDemo vd = new ValueDemo();String str = "not null";vd.change(str);System.out.println("str is " + str);    // ==> str is not null!!// Note that if "str" was// passed-by-reference, it// WOULD BE NULL after the// call to change().}}

编辑

几个人写的评论似乎表明他们没有看我的例子,或者他们没有得到c++的例子。不确定断开在哪里,但猜测c++例子是不清楚的。我在PASCAL中发布同样的例子,因为我认为PASCAL中的引用传递看起来更清晰,但我可能错了。我可能只是让人们更困惑;我希望不是。

在pascal中,通过引用传递的参数称为“var参数”。在下面的过程setToNil中,请注意参数“ptr”前面的关键字“var”。当指针传递给此过程时,它将被传递引用。请注意行为:当此过程将ptr设置为nil时(这是PASCAL表示NULL),它将参数设置为nil-您不能在Java中这样做。

program passByRefDemo;typeiptr = ^integer;varptr: iptr;   
procedure setToNil(var ptr : iptr);beginptr := nil;end;
beginnew(ptr);ptr^ := 10;setToNil(ptr);if (ptr = nil) thenwriteln('ptr seems to be nil');     { ptr should be nil, so this line will run. }end.

编辑2

摘自KenArnold的《Java编程语言》詹姆斯·高斯林(发明Java的人)和DavidHolmes,第2章第2.6.5节

方法的所有参数都“按值”传递。换句话说,方法中参数变量的值是调用者的副本指定为参数。

他继续对物体提出同样的观点…

您应该注意,当参数是对象引用时,它是对象引用-不是对象本身-是通过值传递

在同一部分的末尾,他做了一个更广泛的声明,关于java只是值传递,从不引用传递。

Java编程语言不通过引用传递对象;它按值传递对象引用。因为相同的两个副本引用指的是同一个实际对象,通过一个对象进行的更改引用变量通过另一个可见。只有一个参数传递模式-值传递-这有助于保持东西很简单

本书的这一部分很好地解释了Java中的参数传递,以及引用传递和值传递之间的区别,这是Java的创建者写的。

我认为这两个模型之间的差异非常微妙,除非你在实际使用引用传递的地方做过编程,否则很容易忽略两个模型的不同之处。

我希望这能解决争论,但可能不会。

编辑3

我可能有点迷恋这篇文章。可能是因为我觉得Java的制造者无意中传播了虚假信息。如果不是用“引用”这个词作为指针,他们用了别的东西,说你可以说,“Java通过值而不是引用传递dingleberry”,没有人会感到困惑。

这就是为什么只有Java开发人员对此有异议。他们看到“引用”这个词并认为他们确切地知道这意味着什么,所以他们甚至懒得考虑相反的论点。

不管怎么说,我注意到一个较旧的帖子中的一个评论,它做了一个气球的类比,我真的很喜欢。以至于我决定把一些剪贴画粘在一起,制作一组卡通来说明这一点。

按值传递引用--对引用的更改不会反映在调用者的作用域中,但对对象的更改会反映在调用者的作用域中。这是因为引用被复制了,但原始和副本都引用了同一个对象。按值传递对象引用

引用传递-没有引用的副本。单个引用由调用者和被调用的函数共享。对引用或对象数据的任何更改都反映在调用者的范围内。引用传递

编辑4

我看到过关于这个主题的帖子,它们描述了Java中参数传递的低级实现,我认为这很棒并且非常有帮助,因为它使抽象的想法变得具体。然而,对我来说,这个问题更多的是关于语言规范中描述的行为,而不是关于行为的技术实现。这是Java语言规范,第8.4.1节的练习:

当调用方法或构造函数时(§15.12),实际参数表达式初始化新创建的参数变量,每个声明的类型,在执行主体之前方法或构造函数。出现在DeclaratorId可以用作方法主体中的简单名称,或者构造函数引用形式参数。

这意味着,java在执行方法之前会创建传递参数的副本。像大多数在大学学习编译器的人一样,我使用了《龙书》,这是编译器的第一本书。它在第1章中对“按值调用”和“按引用调用”有很好的描述。按值调用的描述与Java规范完全匹配。

当我在90年代研究编译器时,我使用的是1986年的第一版,它比Java早了大约9或10年。然而,我只是偶然发现了2007年的第二版副本它实际上提到了Java!第1.6.6节标有“参数传递机制”,很好地描述了参数传递。这是“按值调用”标题下的摘录,其中提到Java:

在按值调用中,计算实际参数(如果它是一个表达式)或复制(如果它是变量)。该值放置在属于相应形式参数的位置此方法用于C和Java,是一种常见的方法C++中的选项,以及大多数其他语言中的选项。

长话短说:

  1. 非原语:Java传递参考文献的价值
  2. 原语:只是价值。

结束。

(2)太简单了。现在如果你想思考(1)意味着什么,想象你有一个类Apple:

class Apple {private double weight;public Apple(double weight) {this.weight = weight;}// getters and setters ...
}

然后,当您将此类的实例传递给main方法时:

class Main {public static void main(String[] args) {Apple apple = new Apple(3.14);transmogrify(apple);System.out.println(apple.getWeight()+ " the goose drank wine...";
}
private static void transmogrify(Apple apple) {// does something with apple ...apple.setWeight(apple.getWeight()+0.55);}}

哦…但你可能知道,当你做这样的事情时,你对会发生什么感兴趣:

class Main {public static void main(String[] args) {Apple apple = new Apple(3.14);transmogrify(apple);System.out.println("Who ate my: "+apple.getWeight()); // will it still be 3.14?
}
private static void transmogrify(Apple apple) {// assign a new apple to the reference passed...apple = new Apple(2.71);}

}

Java通过值传递参数,没有在Java中传递引用的选项。

但是在编译器绑定层,它使用内部不向用户公开的引用。

这是必不可少的,因为它节省了大量内存并提高了速度。

public class Test {
static class Dog {String name;
@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + ((name == null) ? 0 : name.hashCode());return result;}
@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;Dog other = (Dog) obj;if (name == null) {if (other.name != null)return false;} else if (!name.equals(other.name))return false;return true;}
public String getName() {return name;}
public void setName(String nb) {this.name = nb;}
Dog(String sd) {this.name = sd;}}/**** @param args*/public static void main(String[] args) {Dog aDog = new Dog("Max");
// we pass the object to foofoo(aDog);Dog oldDog = aDog;
System.out.println(" 1: " + aDog.getName().equals("Max")); // falseSystem.out.println(" 2 " + aDog.getName().equals("huahua")); // falseSystem.out.println(" 3 " + aDog.getName().equals("moron")); // trueSystem.out.println(" 4 " + " " + (aDog == oldDog)); // true
// part2Dog aDog1 = new Dog("Max");
foo(aDog1, 5);Dog oldDog1 = aDog;
System.out.println(" 5 : " + aDog1.getName().equals("huahua")); // trueSystem.out.println(" part2 : " + (aDog1 == oldDog1)); // false
Dog oldDog2 = foo(aDog1, 5, 6);System.out.println(" 6 " + (aDog1 == oldDog2)); // trueSystem.out.println(" 7 " + (aDog1 == oldDog)); // falseSystem.out.println(" 8 " + (aDog == oldDog2)); // false}
/**** @param d*/public static void foo(Dog d) {System.out.println(d.getName().equals("Max")); // true
d.setName("moron");
d = new Dog("huahua");System.out.println(" -:-  " + d.getName().equals("huahua")); // true}
/**** @param d* @param a*/public static void foo(Dog d, int a) {d.getName().equals("Max"); // true
d.setName("huahua");}
/**** @param d* @param a* @param b* @return*/public static Dog foo(Dog d, int a, int b) {d.getName().equals("Max"); // trued.setName("huahua");return d;}}

示例代码演示了更改对不同函数对象的影响。

首先让我们了解Java中的内存分配:堆栈和堆是JVM为不同目的分配的内存的一部分。堆栈内存是预先分配给线程的,当它被创建时,因此,一个线程不能访问其他线程的堆栈。但是堆对程序中的所有线程都可用。

对于线程,堆栈存储所有本地数据、程序元数据、原始类型数据和对象引用。堆负责存储实际对象。

Book book = new Book("Effective Java");

在上面的示例中,引用变量是存储在堆栈中的“book”。new运算符->new Book(“有效Java”)创建的实例存储在Heap中。ref变量“book”具有在Heap中分配的对象的地址。假设地址是1001。

在此处输入图片描述

考虑传递一个基本数据类型,即int、浮点数、双精度等。

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);}}

输出为:调用方法前的值:10printNum方法内的值:20调用方法后的值:10

int num=10;->这会在正在运行的线程的Stack中为“int”分配内存,因为它是一种原始类型。现在当调用printNum(…)时,会在同一线程中创建一个私有堆栈。当“num”传递给此方法时,会在方法堆栈框架中创建“num”的副本。num=num+10;->这会增加10并修改方法堆栈框架中的int变量。因此,方法栈框架之外的原始num保持不变。

考虑将自定义类的对象作为参数传递的示例。

在此处输入图片描述

在上面的例子中,ref变量“book”驻留在执行程序的线程堆栈中,当程序执行new Book()时,类Book的对象在Heap空间中创建。Heap中的这个内存位置被“book”引用。当“book”作为方法参数传递时,“book”的副本会在同一线程堆栈内的方法私有堆栈框架中创建。因此,复制的引用变量指向Heap中类“Book”的同一个对象。

在此处输入图片描述

方法栈帧中的引用变量为同一个对象设置了一个新值。因此,当原始ref变量“book”获取其值时,它会反映出来。请注意,在传递引用变量的情况下,如果它在调用方法中再次初始化,则它会指向新的内存位置,并且任何操作都不会影响堆中的前一个对象。

因此,当任何东西作为方法参数传递时,它总是Stack实体-原始变量或引用变量。我们从不传递存储在Heap中的东西。因此,在Java,我们总是传递堆栈中的值,它是值传递。

我认为这个简单的解释可能会帮助你理解,因为我想理解同样的事情,当我正在努力解决这个问题时。

当您将原始数据传递给函数调用时,它的内容将被复制到函数的参数中,当您传递对象时,它的引用将被复制到函数的参数中。说到对象,您不能更改调用函数中复制的引用-参数变量引用

考虑这个简单的例子,String是java中的一个对象,当您更改字符串的内容时,引用变量现在将指向一些新的引用,因为String对象在java中是不可变的。

String name="Mehrose";  // name referencing to 100
ChangeContenet(String name){name="Michael"; // refernce has changed to 1001
}System.out.print(name);  //displays Mehrose

相当简单,因为正如我提到的,您不允许更改调用函数中复制的引用。但问题是当您传递String/Object数组时的数组。让我们看看。

String names[]={"Mehrose","Michael"};
changeContent(String[] names){names[0]="Rose";names[1]="Janet"
}
System.out.println(Arrays.toString(names)); //displays [Rose,Janet]

正如我们所说,我们无法更改函数调用中复制的引用,我们也看到了单个String对象的情况。原因是名称[]变量引用200,名称[0]引用205,依此类推。你看,我们没有更改名称[]引用,它仍然指向函数调用后的旧相同引用,但现在名称[0]和名称[1]引用已经更改。我们仍然坚持我们的定义,即我们不能更改引用变量的引用,所以我们没有。

同样的事情发生在当你传递一个学生对象给一个方法,你仍然可以改变学生的名字或其他属性,重点是我们没有改变实际的学生对象,而是改变了它的内容

你不能这么做

Student student1= new Student("Mehrose");
changeContent(Student Obj){obj= new Student("Michael") //invalidobj.setName("Michael")  //valid
}

答:Java通过引用操作对象,所有对象变量都是引用。但是,Java不通过引用传递方法参数;它通过值传递它们。

以badSwap()方法为例:

public void badSwap(int var1, int var2){int temp = var1;var1 = var2;var2 = temp;}

当badSwap()返回时,作为参数传递的变量仍将保留其原始值。如果我们将参数类型从int更改为Object,该方法也将失败,因为Java也按值传递对象引用。现在,这是它变得棘手的地方:

public void tricky(Point arg1, Point arg2){arg1.x = 100;arg1.y = 100;Point temp = arg1;arg1 = arg2;arg2 = temp;}public static void main(String [] args){Point pnt1 = new Point(0,0);Point pnt2 = new Point(0,0);System.out.println("X: " + pnt1.x + " Y: " +pnt1.y);System.out.println("X: " + pnt2.x + " Y: " +pnt2.y);System.out.println(" ");tricky(pnt1,pnt2);System.out.println("X: " + pnt1.x + " Y:" + pnt1.y);System.out.println("X: " + pnt2.x + " Y: " +pnt2.y);}

如果我们执行这个main()方法,我们会看到以下输出:

X: 0 Y: 0X: 0 Y: 0X: 100 Y: 100X: 0 Y: 0

该方法成功地改变了pnt1的值,即使它是按值传递的;然而,pnt1和pnt2的交换失败了!这是混淆的主要根源。在main()方法中,pnt1和pnt2只不过是对象引用。当您将pnt1和pnt2传递给tricky()方法时,Java像任何其他参数一样按值传递引用。这意味着传递给该方法的引用实际上是原始引用的副本。下面的图1显示了Java将对象传递给方法后指向同一对象的两个引用。

输入图片描述
图1.传递给方法后,对象将至少有两个引用

Java复制并传递引用的值,而不是对象。因此,方法操作将改变对象,因为引用指向原始对象。但由于引用是副本,交换将失败。

我看到所有的答案都包含相同的:值传递。然而,最近Brian Goetz对项目瓦尔哈拉的更新实际上给出了不同的答案:

实际上,关于Java对象是按值传递还是按引用传递是一个常见的“问题”,并且答案是“都不是”:对象引用按值传递。

你可以在这里阅读更多:瓦尔哈拉州。第2节:语言模型

编辑: Brian Goetz是Java语言架构师,领导Project Valhala和Project Amber等项目。

编辑-2020-12-08:更新瓦尔哈拉州

我会用另一种方式说:

在java中,引用被传递(但不是对象),这些引用是按值传递的(引用本身被复制,结果你有2个引用,你在方法中的第一个引用下没有控制权)。

对于初学者来说,仅仅说:按值传递可能不够清楚。例如在Python中相同的情况,但有文章描述他们称之为pass-by-reference,仅使用原因引用。

Java总是按值传递参数。
Java中的所有对象引用都是按值传递的。这意味着值的副本将被传递给方法。但诀窍是传递值的副本也会改变对象的真实值。

请参考下面的例子。

public class ObjectReferenceExample {
public static void main(String... doYourBest) {Student student = new Student();transformIntoHomer(student);System.out.println(student.name);}
static void transformIntoDuleepa(Student student) {student.name = "Duleepa";}}class Student {String name;}

在这种情况下,它将是Duleepa!原因是Java对象变量只是指向内存堆中真实对象的引用。因此,即使Java通过值将参数传递给方法,如果变量指向对象引用,实际对象也会发生变化。

Java总是值传递而不是引用传递

首先,我们需要了解什么是值传递和引用传递。

按值传递表示您正在内存中复制传入的实际参数的值。这是实际参数内容的副本。

引用传递(也称为按地址传递)表示存储实际参数地址的副本。

有时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;}

该程序的输出是:

changevalueLet's understand step by step:

测试t=new Test();众所周知,它将在堆中创建一个对象并将引用值返回给t。例如,假设t的值是0x100234(我们不知道实际的JVM内部值,这只是一个例子)。

第一幅插图

new PassByValue().changeValue(t);

当将引用t传递给函数时,它不会直接传递对象test的实际引用值,但它会创建t的副本,然后将其传递给函数。由于它是按值传递的,它传递的是变量的副本而不是它的实际引用。由于我们说t的值是0x100234,t和f都将具有相同的值,因此它们将指向相同的对象。

第二幅插图

如果您使用引用f更改函数中的任何内容,它将修改对象的现有内容。这就是为什么我们得到了输出改变值,它在函数中更新。

为了更清楚地理解这一点,请考虑以下示例:

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,如下所示:

第三幅插图

希望这会有帮助。

不要重复,但对于那些在阅读了许多答案后可能仍然感到困惑的人来说:

  • Java中的pass by value是C++中的不平等pass by value,虽然听起来像这样,这可能就是为什么会有混淆

分解它:

  • pass by value在C++意味着传递对象的值(如果对象),文字上是对象的副本
  • Java中的pass by value意味着传递对象的地址值(如果对象),而不是像C++那样传递对象的“值”(副本)
  • 通过pass by value在Java,对函数内部的对象(例如myObj.setName("new"))进行操作,对函数外部的对象进行有影响;通过pass by value在C++,对函数外部的对象进行效果。
  • 然而,到C++的pass by reference,对函数确实中的对象进行操作会对外部的对象产生影响!类似于Java的pass by value只是相似,不一样),不是吗?…人们总是重复“Java中没有引用传递”,=>轰,混乱开始…

所以,朋友们,一切都只是关于术语定义的差异(跨语言),你只需要知道它是如何工作的,就是这样(虽然有时候有点困惑怎么叫我承认)!

如果你想把它变成一个简单的句子来理解和记忆,最简单的答案是:

Java总是使用新引用传递值

(因此可以修改原始对象但不能访问原始引用)

这里有一个更精确的定义:

  • 按值传递/调用:形式参数就像一个局部变量的功能范围,它评估到实际参数的时刻函数调用。
  • 通过/调用引用:形式化参数只是实数的别名值,它在函数范围内的任何变化都可以有边在代码的任何其他部分之外的效果。

因此,在C/C++中,您可以创建一个函数来交换使用引用传递的两个值:

void swap(int& a, int& b){int tmp = a;a = b;b = tmp;}

你可以看到它对a和b有一个唯一的引用,所以我们没有副本,tmp只保存唯一的引用。

java中相同的函数没有副作用,参数传递就像上面的代码一样,没有引用。

虽然java使用指针/引用,但参数不是唯一的指针,在每个属性中,它们被复制而不是像C/C++一样分配

只有两个版本:

  • 您可以传递值即(4,5)
  • 您可以传递一个地址,即0xF43A

Java将原始值作为值传递,将对象作为地址传递。那些说“地址也是值”的人并没有区分两者。那些关注交换函数效果的人关注的是传递完成后会发生什么。

在C++您可以执行以下操作:

Point p = Point(4,5);

这在堆栈上保留8个字节并在其中存储(4,5)。

Point *x = &p;

这在堆栈上保留4个字节并在其中存储0xF43A。

Point &y = p;

这在堆栈上保留4个字节并在其中存储0xF43A。

  1. 我想每个人都会同意,如果f的定义是f(Point p),则对f(p)的调用是按值传递的。在这种情况下,额外保留8个字节并将(4,5)复制到其中。当f更改p时,原始内容保证在f返回时保持不变。

  2. 我想每个人都会同意,如果f的定义是f(Point&p),则对f(p)的调用是引用传递。在这种情况下,将保留额外的4个字节并将0xF43A复制到其中。当f更改p时,当f返回时,原始内容将被更改。

  3. 如果f的定义是f(Point*p),则对f(&p)的调用也是通过引用传递的。在这种情况下,将保留额外的4个字节并将0xF43A复制到其中。当f更改*p时,原始内容保证在f返回时被更改。

  4. 如果f的定义是f(Point*p),则对f(x)的调用也是通过引用传递的。在这种情况下,将保留额外的4个字节并将0xF43A复制到其中。当f更改*p时,当f返回时,原始内容必须更改。

  5. 如果f的定义是f(Point&p),则对f(y)的调用也是通过引用传递的。在这种情况下,将保留额外的4个字节并将0xF43A复制到其中。当f更改p时,原始内容在f返回时保证需要更改。

当然传递完成后会发生什么不同,但这只是一种语言结构。在指针的情况下,你必须使用->来访问成员,在引用的情况下,你必须使用…如果你想交换原始的值,那么你可以做tmp=a; a=b; b=tmp;在引用的情况下,tmp=*a;*b=tmp;*a=tmp作为指针。在Java你会做:tmp.set(a);a.set(b);b.set(tmp)。专注于赋值语句语句是愚蠢的。如果你写一点代码,你可以在Java做同样的事情。

所以Java通过值传递原语,通过引用传递对象。Java复制值来实现这一点,C++也是如此。

为了完整性:

Point p = new Point(4,5);

这在堆栈上保留4个字节并在其中存储0xF43A,在堆上保留8个字节并在其中存储(4,5)。

如果你想像这样交换内存位置

void swap(int& a, int& b) {int *tmp = &a;&a = &b;&b = tmp;}

然后你会发现你遇到了硬件的限制。

这里的每一个答案都是通过引用从其他语言中获取传递指针,并展示如何在Java中做到这一点是不可能的。不管出于什么原因,没有人试图展示如何从其他语言中实现按值传递对象。

这段代码显示了如何完成这样的事情:

public class Test{private static void needValue(SomeObject so) throws CloneNotSupportedException{SomeObject internalObject = so.clone();so=null;        
// now we can edit internalObject safely.internalObject.set(999);}public static void main(String[] args){SomeObject o = new SomeObject(5);System.out.println(o);try{needValue(o);}catch(CloneNotSupportedException e){System.out.println("Apparently we cannot clone this");}System.out.println(o);}}
public class SomeObject implements Cloneable{private int val;public SomeObject(int val){this.val = val;}public void set(int val){this.val = val;}public SomeObject clone(){return new SomeObject(val);}public String toString(){return Integer.toString(val);}}

这里我们有一个函数needValue,它所做的是立即创建一个对象的克隆,它需要在对象本身的类中实现,并且类需要标记为Cloneable。在那之后设置sonull并不重要,但我在这里这样做是为了表明我们不会在那之后使用该引用。

很可能Java没有引用传递语义学,但将语言称为“价值传递”是一厢情愿的想法。

为了简单和冗长pass reference by value

public static void main(String[] args) {Dog aDog = new Dog("Max");Dog oldDog = aDog;
// we pass the object to foofoo(aDog);// aDog variable is still pointing to the "Max" dog when foo(...) returnsaDog.getName().equals("Max"); // trueaDog.getName().equals("Fifi"); // falseaDog == 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}

Java使用按值传递,但无论您使用的是原始类型还是引用类型,效果都不同。

当您将原始类型作为参数传递给方法时,它会获取原始类型的副本,并且方法块内的任何更改都不会更改原始变量。

当您将引用类型作为参数传递给方法时,它仍然会获得一个副本,但它是对对象(换句话说,您将获得对象所在堆中内存地址的副本)的引用的副本,因此方法块内对象的任何更改都将影响块外的原始对象。

猜猜,基于不准确的语言,普通的正典是错误的

编程语言的作者无权重命名既定的编程概念。

原始Java类型byte, char, short, int, long float, double肯定是按值传递的。

所有其他类型都是Objects:对象成员和参数技术上是参考

因此,这些“引用”是“按值”传递的,但堆栈上没有对象构造。对象成员(或数组中的元素)的任何更改都适用于相同的原始Object;这种引用精确地满足传递给任何C方言中某个函数的实例指针的逻辑,我们过去称之为通过引用传递对象

特别是我们确实有这个java.lang.NullPointerException,这在纯粹的按值概念中毫无意义

在进行了详尽的讨论之后,我认为是时候将所有严肃的结果放在一个片段中了。

/**** @author Sam Ginrich** All Rights Reserved!**/public class JavaIsPassByValue{
static class SomeClass{int someValue;
public SomeClass(int someValue){this.someValue = someValue;}}
static void passReferenceByValue(SomeClass someObject){if (someObject == null){throw new NullPointerException("This Object Reference was passed by Value,\r\n   that's why you don't get a value from it.");}someObject.someValue = 49;}
public static void main(String[] args){SomeClass someObject = new SomeClass(27);System.out.println("Here is the original value: " + someObject.someValue);
passReferenceByValue(someObject);System.out.println("\nAs ´Java is pass by value´,\r\n   everything without exception is passed by value\r\n   and so an object's attribute cannot change: "+ someObject.someValue);
System.out.println();passReferenceByValue(null);}

}

从输出中可以很容易地看出,在Java一切都是按值传递的,如此简单!

Here is the original value: 27
As ´Java is pass by value´,everything without exception is passed by valueand so an object´s attribute cannot change: 49
'Exception in thread "main" java.lang.NullPointerException: This Object Reference was passed by value,that´s why you don´t get a value from it.at JavaIsPassByValue.passReferenceByValue(JavaIsPassByValue.java:26)at JavaIsPassByValue.main(JavaIsPassByValue.java:43)

“我是一名初级Java开发人员,我想知道Java是使用按值调用还是按引用调用?”

对此没有通用的答案,也不可能有,因为它是一个假二分法。我们有2个不同的术语(按值调用/按引用调用),但在将数据传递给方法时,至少有3种(!)不同的处理所述数据的方式:

  1. 我们的数据被复制并将副本提供给一个方法。对副本的更改不会传播到外部。(想想Java或C++或C#中的int。)
  2. 我们的数据的指针(内存地址)被提供给方法。对我们数据的更改确实会传播到外部。我们也可以指向一些新实例,使我们的原始数据悬而未决。(想想C++中的指针。)
  3. 类似于#2,只是我们只能更改原始数据,但不能更改指针指向的内容。(想想Java中作为参数传递的实例。)

在OO世界中,引用调用按价值调用,#2是引用调用是没有争议的。然而,由于我们只有三个选项的两个术语,由于选项#3,这两个术语之间没有明确的界限。

“这对我来说意味着什么?”

这意味着在Java范围内,你可以合理地假设#3是在按值调用(或更口头的体操术语,按值调用引用)下假设的。

然而,在更广泛的OO世界中,这意味着您必须要求按值调用和按引用调用之间的明确划分,特别是另一方如何分类#3。

“但我读到JLS将其定义为按值调用!”

这就是为什么你在与Java开发人员打交道时的最初假设应该是上述的。然而,JLS在Java之外的权力要小得多。

“为什么Java开发人员坚持自己的术语?”

我不能推测,但我认为公平地指出,#2(显然是引用调用)存在一些潜在问题,正如暗示的那样,导致引用调用在任何地方都没有最好的声誉。

“有办法解决这个烂摊子吗?”

3个选项,只有2个无处不在的名称。显而易见的出口是第三个术语,它与按值调用和按引用调用一样广泛使用(这比按值调用引用更不混乱,也许是共享呼叫)。在那之前,你需要假设或,如上所述。

最终,我们称之为什么并不重要,只要我们相互理解,没有混淆。

Java是按值传递还是引用?证明它。

Java严格执行按值传递。使用按值传递参数不会影响/改变原始变量。在下面的程序中,我们用一些值初始化了一个名为'x'的变量,并使用按值传递技术来演示变量的值如何保持不变。

代码:

public class Main{public static void main(String[] args){//Original value of 'x' will remain unchanged// in case of call-by-value   
int x = 5;System.out.println( "Value of x before call-by-value: " + x);// 5
processData(x);System.out.println("Value of x after call-by-value: " + x);// 5}public static void processData(int x){x=x+10;}}

现在来谈谈意思-x的值在主函数中没有改变-这称为值传递。

如果值在main函数中发生变化,则称为引用传递。

值传递和引用传递的区别

什么是价值传递?

在值传递中,函数参数的值被复制到内存的另一个位置。当访问或修改函数内的变量时,它只访问副本。因此,对原始值没有影响。

什么是引用传递?

在引用传递中,内存地址被传递给该函数。换句话说,函数可以访问实际变量。

按值传递和按引用传递的区别

定义

值传递是指将函数参数值复制到另一个变量的机制,而引用传递是指将实际参数传递给函数的机制。因此,这是值传递和引用传递之间的主要区别。

变化

在值传递中,函数内部所做的更改不会反映在原始值中。另一方面,在引用传递中,函数内部所做的更改会反映在原始值中。因此,这是值传递和引用传递之间的另一个区别。

实际参数

此外,值传递会复制实际参数。但是,在引用传递中,实际参数的地址传递给函数。