提问



我一直认为Java是传递参考


但是,我已经看过一些博客文章(例如,这个博客)声称它不是。[203]


我不认为我理解他们正在制造的区别。


解释是什么?

最佳参考


Java总是按值传递。不幸的是,他们决定将对象的位置称为引用。当我们传递一个对象的值时,我们将引用传递给它。这对初学者来说很困惑。


它是这样的:


public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    // we pass the object to foo
    foo(aDog);
    // aDog variable is still pointing to the "Max" dog when foo(...) returns
    aDog.getName().equals("Max"); // true
    aDog.getName().equals("Fifi"); // false 
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // change d inside of foo() to point to a new Dog instance "Fifi"
    d = new Dog("Fifi");
    d.getName().equals("Fifi"); // true
}


在上面的例子中aDog.getName()仍将返回"Max"。函数foo中的值aDog不会随Dog "Fifi"改变,因为对象参考值是按值传递的。如果通过引用传递,那么main中的aDog.getName()将在调用foo后返回"Fifi"


同样:


public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // true
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // this changes the name of d to be "Fifi"
    d.setName("Fifi");
}


在上面的例子中,Fifi是调用foo(aDog)后的狗的名字,因为对象的名字是在foo(...)内设置的。 food上执行的任何操作都是这样的,出于所有实际目的,它们在aDog本身上执行(除非d改变为指向不同[[Dog例如d = new Dog("Boxer"))。

其它参考1


我刚刚注意到你引用了我的文章。[204]


Java规范说Java中的所有内容都是按值传递的。在Java中没有pass-by-reference这样的东西。


理解这一点的关键是类似的东西


Dog myDog;


是不是狗;它实际上是狗的指针。


这意味着,就在你拥有的时候


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


你基本上将创建的Dog对象的地址传递给foo方法。


(我说的主要是因为Java指针不是直接地址,但最容易想到它们)


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


如果方法被定义为


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


让我们来看看发生了什么。



  • 参数someDog设置为值42

  • 在AAA行


    • someDog跟随Dog指向(地址42处的Dog对象)

    • 要求Dog(地址42的那个)将他的名字改为Max


  • 在BBB行


    • 创建了新Dog。让我们说他在地址74

    • 我们将参数someDog分配给74


  • 在线CCC


    • someDog跟随Dog指向(地址74处的Dog对象)

    • 要求Dog(地址为74的那个)将他的名字改为Rowlf


  • 然后,我们返回



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


myDog改变了吗?


关键是。


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


跟随地址并改变其末尾的内容是完全有效的;但是,这不会改变变量。


Java的工作方式与C完全相同。您可以分配指针,将指针传递给方法,按照方法中的指针操作并更改指向的数据。但是,您无法更改指针指向的位置。


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


如果Java具有pass-by-reference语义,那么我们在上面定义的foo方法在myDog指向[[BB]]上的someDog时指向的位置会发生变化。


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

其它参考2


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的引用,并将其赋值为Foo类型的新对象,其属性为"f"


    Foo f = new Foo("f");
    




  2. 从方法方面,声明类型Foo的名称为a的引用,并且它最初分配给null


    public static void changeReference(Foo a)
    




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


    changeReference(f);
    




  4. 声明类型为Foo的名为b的引用,并将其赋值为Foo类型的新对象,其属性为"b"


    Foo b = new Foo("b");
    




  5. a = b将引用a NOT f重新分配给其属性为"b"的对象。







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




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






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

其它参考3


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


第一步请从脑海中删除以p_ _ _ _ _ _ _开头的单词,特别是如果您来自其他编程语言。 Java和p不能写在同一本书,论坛,甚至txt中。


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



  • 学生:硕士,这是否意味着Java是传递参考?

  • Master :Grasshopper,No。



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



  1. 变量包含告诉JVM如何到达内存中引用的对象的位(Heap)。

  2. 将参数传递给方法时,您不传递引用变量,而是传递引用变量中的位副本。像这样:3bad086a。 3bad086a代表了一种获取传递对象的方法。

  3. 所以你只是传递3bad086a它是参考的价值。

  4. 您正在传递引用的值而不是引用本身(而不是对象)。

  5. 此值实际上是COPIED并提供给方法



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


1. Person person;
2. person = new Person("Tom");
3. changeName(person);
4.
5. //I didn't use Person person below as an argument to be nice
6. static void changeName(Person anotherReferenceToTheSamePersonObject) {
7.     anotherReferenceToTheSamePersonObject.setName("Jerry");
8. }


怎么了?



  • 变量 person 是在第1行创建的,并且在开头是null。

  • 在第2行创建一个新的Person对象,存储在内存中,变量 person 被赋予对Person对象的引用。那就是它的地址。让我们说3bad086a。

  • 保存Object对象的变量 person 将传递给第3行的函数。

  • 第4行你可以听到沉默的声音

  • 检查第5行
  • 上的评论
  • 创建一个方法局部变量 - anotherReferenceToTheSamePersonObject - 然后在#6行中产生魔力:


    • 变量/引用 person 被逐位复制并传递给函数内的 anotherReferenceToTheSamePersonObject 。

    • 没有创建Person的新实例。

    • person 和 anotherReferenceToTheSamePersonObject 都拥有相同的3bad086a值。

    • 不要试试这个,但是人== anotherReferenceToTheSamePersonObject会是真的。

    • 两个变量都具有引用的IDENTICAL COPIES,它们都引用相同的Person对象,堆上的SAME对象而不是COPY。




一张图片胜过千言万语:





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


如果你没有得到它,那么请相信我,并记住最好说 Java是按价值传递。那么,通过参考值传递。哦,更好的是 传递副本的变量值! ;)


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


您总是传递参考值的位副本!



  • 如果它是原始数据类型,这些位将包含原始数据类型本身的值。

  • 如果它是一个Object,那么这些位将包含告诉JVM如何到达Object的地址值。




  Java是按值传递的,因为在一个方法中你可以根据需要修改引用的对象,但无论你怎么努力,你都将永远无法修改将继续引用的传递变量(不是p _ _ _ _ _ _ _)同样的对象无论如何!







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






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

其它参考4


Java总是按值传递,没有例外,永远


那么如何让任何人都对此感到困惑,并相信Java是通过引用传递的,或者认为他们有一个Java作为传递参考的例子?关键是Java 从不在任何环境中提供对对象本身的值的直接访问。对对象的唯一访问是通过引用到该对象。因为Java对象总是通过引用而不是直接访问,所以通常将字段和变量和方法参数称为对象 ,当他们只是对象的引用时。 这种混淆源于这种(严格来说,不正确的)命名法的变化。


所以,在调用方法时



  • 对于原始参数(intlong等),传递值原语的实际值(例如,3)。

  • 对于对象,pass by value是对象的引用值。



因此,如果您有doSomething(foo)public void doSomething(Foo foo) { .. },则两个Foos已复制指向相同对象的引用。


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

其它参考5


Java按值传递引用。


所以你不能改变传入的引用。

其它参考6


我觉得争论传递引用与传递价值并不是非常有用。


如果你说Java是通过任何东西(参考/价值),在任何一种情况下,你都不会提供一个完整的答案。这里有一些额外的信息,希望有助于理解记忆中发生的事情。 。


在我们进入Java实现之前,堆栈/堆上的崩溃过程:
价值观以有序的方式在堆栈中上下移动,就像在自助餐厅的一堆盘子一样。
堆中的内存(也称为动态内存)是杂乱无章的。 JVM只是在任何地方找到空间,并释放它,因为不再需要使用它的变量。


好的。首先,本地原语进入堆栈。所以这段代码:


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


结果如下:





声明和实例化对象时。实际的对象在堆上。什么在堆栈上?堆上对象的地址。 C ++程序员会将此称为指针,但是一些Java开发人员反对指针这个词。随你。只要知道对象的地址就在堆栈上。


像这样:


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





数组是一个对象,所以它也在堆上。那阵列中的对象怎么样?它们获得自己的堆空间,每个对象的地址都在数组内部。


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





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


private static void shout(String name){
    System.out.println("There goes " + name + "!");
}

public static void main(String[] args){
    String hisName = "John J. Jingleheimerschmitz";
    String myName = hisName;
    shout(myName);
}


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





那么,价值,参考?你说土豆。

其它参考7


为了显示对比,比较以下C ++和Java片段:[206] [207]


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


void cppMethod(int val, int &ref, Dog obj, Dog &objRef, Dog *objPtr, Dog *&objPtrRef)
{
    val = 7; // Modifies the copy
    ref = 7; // Modifies the original variable
    obj.SetName("obj"); // Modifies the copy of Dog passed
    objRef.SetName("objRef"); // Modifies the original Dog passed
    objPtr->SetName("objPtr"); // Modifies the original Dog pointed to 
                               // by the copy of the pointer passed.
    objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                   // leaving the original object alone.
    objPtrRef->SetName("objRefPtr"); // Modifies the original Dog pointed to 
                                    // by the original pointer passed. 
    objPtrRef = new Dog("newObjPtrRef"); // Modifies the original pointer passed
}

int main()
{
    int a = 0;
    int b = 0;
    Dog d0 = Dog("d0");
    Dog d1 = Dog("d1");
    Dog *d2 = new Dog("d2");
    Dog *d3 = new Dog("d3");
    cppMethod(a, b, d0, d1, d2, d3);
    // a is still set to 0
    // b is now set to 7
    // d0 still have name "d0"
    // d1 now has name "objRef"
    // d2 now has name "objPtr"
    // d3 now has name "newObjPtrRef"
}


在Java中


public static void javaMethod(int val, Dog objPtr)
{
   val = 7; // Modifies the copy
   objPtr.SetName("objPtr") // Modifies the original Dog pointed to 
                            // by the copy of the pointer passed.
   objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                  // leaving the original object alone.
}

public static void main()
{
    int a = 0;
    Dog d0 = new Dog("d0");
    javaMethod(a, d0);
    // a is still set to 0
    // d0 now has name "objPtr"
}


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

其它参考8


Java按值传递对象的引用。

其它参考9


我不相信没有人提到Barbara Liskov。当她在1974年设计CLU时,她遇到了同样的术语问题,她通过共享发明了一词(也称为电话对象共享和按对象调用)对于值为值为引用的值调用的特定情况。

其它参考10


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


private 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

其它参考11


问题的关键在于通过引用传递这个词中的 reference 这个词意味着与Java中 reference 这个词的通常含义完全不同。


通常在Java reference 中表示对对象的引用。但是,来自编程语言理论的技术术语通过引用/值传递的是关于对包含变量的存储器单元的引用,这是完全不同的。

其它参考12


在java中,一切都是引用,所以当你有类似的东西:
    Point pnt1 = new Point(0,0); Java确实如下:



  1. 创建新的Point对象

  2. 创建新的Point引用,并在先前创建的Point对象上初始化对 point(参考)的引用。

  3. 从这里开始,通过Point对象生命,您将通过pnt1访问该对象
     参考。所以我们可以说在Java中你通过它的引用操作对象。






Java不会通过引用传递方法参数;它会按值传递它们。我将使用此站点中的示例:[208]


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


程序流程:


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


使用两个不同的引用关联创建两个不同的Point对象。



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


正如预期的输出将是:


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


在这一行中,按值传递进入游戏......



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


引用pnt1pnt2 通过值传递给棘手的方法,这意味着现在您的引用pnt1pnt2具有copies]]命名arg1arg2。所以pnt1arg1 指向到同一个对象。 (pnt2arg2相同)



tricky方法中:


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





接下来是tricky方法


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


在这里,您首先创建新的temp点引用,它将指向在arg1引用的同一位置。然后将参考arg1移动到点到arg2参考的相同位置。
最后arg2将指向到temp之类的相同位置。





从这里tricky方法的范围消失了,你不再能够访问参考文献:arg1arg2temp但重要的是当你在生活中使用这些引用时所做的一切将永久地影响它们指向的对象。


所以在执行方法tricky之后,当你返回main时,你会遇到这种情况:



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


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

其它参考13


无论您使用何种语言,引用始终是表示的值。



获取框外视图,让我们看一下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推到堆栈

  5. 223被复制到其中一个CPU寄存器。

  6. 223获取PUSHd到堆栈。



在高于某个值的每种情况下 - 现有值的副本 - 已经创建,现在由接收方法来处理它。当你在方法中写Foo时,它要么从EAX中读出,要么自动取消引用,或者双重解引用,这个过程取决于语言的工作方式和/或Foo的类型指示。这是开发人员隐藏的,直到她绕过解除引用过程。因此,引用在表示时是值,因为引用是必须处理的值(在语言级别)。


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



  • 在情况1.和2.如果你改变Foo(Foo = 9)它只影响本地范围,因为你有一个价值的副本。从方法内部我们甚至无法确定原始Foo位于内存的哪个位置。

  • 在情况3.和4.如果你使用默认语言结构并改变Foo(Foo = 11),它可以全局改变Foo(取决于语言,即Java或类似Pascal的procedure findMin(x, y, z: integer;]] var m : integer);。但是如果语言允许你规避取消引用过程,你可以改变47,比如说49。那时候Foo如果你阅读它似乎已被改变,因为你已经将本地指针更改为它。如果你要在方法中修改这个Foo(Foo = 12)你可能会FUBAR执行程序(又名.segfault),因为你将写入一个不同于预期的内存,你甚至可以修改一个注定要保存可执行程序的区域,并写入它将修改运行代码(Foo现在不在47]])。但是47的Foo值没有全局变化,只有方法内部的变化,因为47也是方法的副本。

  • 在第5和第6例中。如果在方法中修改223,它会产生与3.或4中相同的混乱。(一个指向现在不好的值的指针,再次用作指针)但这仍然是一个本地问题,因为223是复制。但是,如果你能够解除引用Ref2Foo(即223),达到并修改指向值47,比如说49,它会影响Foo 全局,因为在这种情况下,方法得到223的副本,但引用的47只存在一次,而将其改为49将导致Ref2Foo Ref2Foo]]双重引用错误的值。



对无关紧要的细节进行挑剔,即使是通过引用传递的语言也会将值传递给函数,但这些函数知道它们必须将它用于解除引用目的。这个传递参考值只是程序员隐藏的,因为它实际上是无用的,术语只是传递引用。


严格的按值传递也没用,这意味着每次调用一个以数组为参数的方法时都必须复制一个100 MB的数组,因此Java无法严格传递-值。每种语言都会传递对这个巨大数组的引用(作为一个值),并且如果该数组可以在方法内部进行本地更改,或者允许该方法(如Java那样)全局修改数组,则采用写时复制机制(来自调用者的视图和一些语言允许修改引用本身的值。


所以简而言之,在Java自己的术语中,Java是按值传递,其中值可以是:实际值或者,表示引用

其它参考14


Java始终按值传递,而不是通过引用传递


首先,我们需要了解通过值传递的内容以及通过引用传递的内容。


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


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


有时Java可以给出通过引用传递的错觉。让我们看看它是如何工作的,使用下面的例子:


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

    public void changeValue(Test f) {
        f.name = "changevalue";
    }
}

class Test {
    String name;
}


该程序的输出是:



changevalue



让我们一步一步地理解:


Test t = new Test();


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





new PassByValue().changeValue(t);


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





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


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


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

    public void changeRefence(Test f) {
        f = null;
    }
}

class Test {
    String name;
}


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





希望这会有所帮助。

其它参考15


不,它不是通过引用传递。


根据Java语言规范,Java是按值传递的:



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


其它参考16


据我所知,Java只知道按值调用。这意味着对于原始数据类型,您将使用副本,对于对象,您将使用对象的引用副本。不过我觉得有一些陷阱;例如,这不起作用:


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


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


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


public static void appendWorld(StringBuffer s1) {
    s1.append(" World");
}

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


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


public static void appendWorld(String s){
    s = s+" World";
}

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


但是你可以为这样的String创建一个包装器,这样就可以将它与字符串一起使用:


class StringWrapper {
    public String value;

    public StringWrapper(String value) {
        this.value = value;
    }
}

public static void appendWorld(StringWrapper s){
    s.value = s.value +" World";
}

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


编辑:我相信这也是在添加两个字符串时使用StringBuffer的原因,因为你可以修改原始对象,你不能使用像String这样的不可变对象。

其它参考17


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


/**


通过价值


在Java中,所有参数都按值传递,即调用者无法看到赋值方法参数。


*/


示例1:


public class PassByValueString {
    public static void main(String[] args) {
        new PassByValueString().caller();
    }

    public void caller() {
        String value = "Nikhil";
        boolean valueflag = false;
        String output = method(value, valueflag);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'value' and 'valueflag'
         */
        System.out.println("output : " + output);
        System.out.println("value : " + value);
        System.out.println("valueflag : " + valueflag);

    }

    public String method(String value, boolean valueflag) {
        value = "Anand";
        valueflag = true;
        return "output";
    }
}


结果


output : output
value : Nikhil
valueflag : false


示例2:


/**
 *
 *通过价值
 *
 */


public class PassByValueNewString {
    public static void main(String[] args) {
        new PassByValueNewString().caller();
    }

    public void caller() {
        String value = new String("Nikhil");
        boolean valueflag = false;
        String output = method(value, valueflag);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'value' and 'valueflag'
         */
        System.out.println("output : " + output);
        System.out.println("value : " + value);
        System.out.println("valueflag : " + valueflag);

    }

    public String method(String value, boolean valueflag) {
        value = "Anand";
        valueflag = true;
        return "output";
    }
}


结果


output : output
value : Nikhil
valueflag : false


示例3:


/**
  这种通过价值传递有一种感觉通过参考


有人说原始类型和字符串是按值传递
  和对象是通过引用传递。


但是从这个例子中,我们可以理解它只是通过价值,
  请记住,这里我们将引用作为值传递。
  ie:引用按值传递。
  这就是为什么能够改变并且仍然适用于本地范围之后。
  但我们无法改变原始范围之外的实际参考。
  这意味着下一个PassByValueObjectCase2示例。


*/


public class PassByValueObjectCase1 {

    private class Student {
        int id;
        String name;
        public Student() {
        }
        public Student(int id, String name) {
            super();
            this.id = id;
            this.name = name;
        }
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return "Student [id=" + id + ", name=" + name + "]";
        }
    }

    public static void main(String[] args) {
        new PassByValueObjectCase1().caller();
    }

    public void caller() {
        Student student = new Student(10, "Nikhil");
        String output = method(student);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'student'
         */
        System.out.println("output : " + output);
        System.out.println("student : " + student);
    }

    public String method(Student student) {
        student.setName("Anand");
        return "output";
    }
}


结果


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


示例4:


/**


除了Example3(PassByValueObjectCase1.java)中提到的内容之外,我们无法更改原始范围之外的实际引用。


注意:我没有粘贴private class Student的代码。 Student​​]]的类定义与Example3相同。


*/


public class PassByValueObjectCase2 {

    public static void main(String[] args) {
        new PassByValueObjectCase2().caller();
    }

    public void caller() {
        // student has the actual reference to a Student object created
        // can we change this actual reference outside the local scope? Let's see
        Student student = new Student(10, "Nikhil");
        String output = method(student);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'student'
         */
        System.out.println("output : " + output);
        System.out.println("student : " + student); // Will it print Nikhil or Anand?
    }

    public String method(Student student) {
        student = new Student(20, "Anand");
        return "output";
    }

}


结果


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

其它参考18


您永远不能通过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提供。

其它参考19


我想我会提供这个答案,以便从规格中添加更多细节。


首先,通过参考传递与传递值之间有什么区别?



  通过引用传递意味着被调用的函数参数将是
  与调用者传递参数相同(不是值,而是身份
   - 变量本身)。

  
  按值传递意味着被调用的函数参数将是一个副本
  呼叫者通过了争论。



或者来自维基百科,关于传递参考的主题[211]



  在逐个参考评估中(也称为
  pass-by-reference),函数接收对a的隐式引用
  用作参数的变量,而不是其值的副本。这个
  通常意味着该功能可以修改(即分配给)
  用作参数的变量 - 调用者可以看到的东西。



关于传值的主题[212]



  在call-by-value中,参数表达式被计算,并且
  结果值绑定到函数[[...]]中的相应变量。
  如果函数或过程能够为其赋值
  参数,仅指定其本地副本[[...]]。



其次,我们需要知道Java在其方法调用中使用了什么。 Java语言规范声明[213]



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



因此它将参数的值赋予(或绑定)到相应的参数变量。


论证的价值是什么?


让我们考虑一下引用类型,Java虚拟机规范说明[214]



  有三种引用类型:类类型,数组类型,
  和接口类型。 他们的值是动态引用
  创建了类实例,数组或类实例或数组
  分别实现接口。




Java语言规范还声明[215]



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



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


所以


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


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


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



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



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





此处还在Java虚拟机规范中定义了原始值。该类型的值是相应的积分或浮点值,适当编码(8,16,32,64等位)。[216]

其它参考20


Java是按值调用的。


怎么运行的。



  • 您总是传递参考值的位副本!

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

  • 如果它是像 Foo foo=new Foo()这样的对象数据类型,那么在这种情况下,对象地址的副本会像文件快捷方式一样传递,假设我们有一个文本文件 C:\\ desktop > abc.txt ,假设我们创建了同一个文件的快捷方式并将其放在 C:\\ desktop \\ abc-shortcut 中,所以当您从 C:\\ desktop \\ abc.txt 访问该文件并写入Stack Overflow并关闭该文件并再次从快捷方式打开该文件然后您编写是程序员学习的最大在线社区然后总文件更改Stack Overflow是程序员学习的最大在线社区,这意味着它不重要在你打开文件的地方,每次我们访问同一个文件时,我们都可以假设 Foo 作为文件,并假设foo存储在 123hd7h (原始地址如 C:\\ desktop \\ abc.txt )广告连衣裙和 234jdid (复制地址如 C:\\ desktop \\ abc-shortcut 其中实际包含文件的原始地址)..
    所以为了更好地理解快捷方式文件和感觉...


其它参考21


区别,或者也许只是我记忆中的方式,因为我曾经与原始海报的印象相同:Java总是按价值传递。 Java中的所有对象(在Java中,除了基元之外的任何东西)都是引用。这些引用按值传递。

其它参考22


正如许多人之前提到的那样,Java总是按值传递[217]


这是另一个帮助您理解差异的示例(经典交换示例):[218]


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的引用只会在本地范围内更改,而不会在此方法之外。

其它参考23


我一直认为它是通过副本。它是值的原始或引用的副本。如果它是原语,则它是作为值的位的副本,如果它是Object,则它是引用的副本。


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


java PassByCopy的输出:



  name=Maxx

  name=Fido



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

其它参考24


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

其它参考25


在Java中,只传递引用并按值传递:


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


在原始类型的情况下,Java行为很简单:
该值将复制到基本类型的另一个实例中。


对于Objects,这是相同的:
对象变量是仅包含使用new关键字创建的Object的地址的指针(存储桶),并且像基本类型一样被复制。


行为可能与原始类型不同:因为复制的对象变量包含相同的地址(对于同一个对象)
对象的内容/成员可能仍然在方法中被修改,然后在外部访问,从而产生了(包含)对象本身通过引用传递的错觉。


字符串对象似乎是城市传说中的强>完美反例,表示对象通过引用传递:


实际上,在一个永远不可能的方法中,更新作为参数传递的String的值:


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

其它参考26


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


还提到了Java。以下是简短摘要:



  • Java按值传递参数

  • by value是java将参数传递给方法的唯一方法

  • 使用作为参数给出的对象的方法将改变
    对象作为引用指向
    原始物体。 (如果说
    方法本身改变了一些值)


其它参考27


一些帖子的一些更正。


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


Java中的值是什么并不重要:对象的原始或地址(粗略),它总是按值传递。


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


我不确定为什么会这么混乱,也许是因为很多Java程序员没有经过正式培训,因此不明白内存中究竟发生了什么?

其它参考28


简而言之,Java对象具有一些非常特殊的属性。[221]


通常,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 ++提供的细粒度控制(和复杂性)。

其它参考29


Java按VALUE传递参数,按值仅传递


长话短说:



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

  
  来自PASCAL的人:没有var参数



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


解决方法是使用StringBuilder参数代替String。你总是可以使用数组!