Java是“通过引用传递”还是“传递价值”?

时间:2008-09-02 20:14:29

标签: java methods parameter-passing pass-by-reference pass-by-value

我一直以为Java 传递参考

但是,我看过几篇博文(例如,this blog)声称它不是。

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

解释是什么?

95 个答案:

答案 0 :(得分:5298)

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

它是这样的:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

    // we pass the object to foo
    foo(aDog);
    // aDog variable is still pointing to the "Max" dog when foo(...) returns
    aDog.getName().equals("Max"); // true
    aDog.getName().equals("Fifi"); // false
    aDog == oldDog; // true
}

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

在上面的示例中,aDog.getName()仍会返回"Max"。函数aDogmain内的值foo未使用Dog "Fifi"更改,因为对象引用按值传递。如果通过引用传递,则aDog.getName()中的main会在调用"Fifi"后返回foo

同样地:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // true
    // but it is still the same dog:
    aDog == oldDog; // true
}

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

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

答案 1 :(得分:2844)

我刚注意到你引用了my article

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具有传递引用语义,那么我们在上面定义的foo方法在myDog在BBB上分配someDog时指向的位置会发生变化。

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

答案 2 :(得分:1573)

Java始终按值而不是通过引用传递参数。


让我通过example解释一下:

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. 声明名为f的{​​{1}}类型的引用,并为其指定一个类型为Foo的新对象,其属性为Foo

    "f"

    enter image description here

  2. 从方法方面,声明了名为Foo f = new Foo("f"); 的{​​{1}}类型的引用,并且它最初被分配为Foo

    a

    enter image description here

  3. 在调用方法null时,将为引用public static void changeReference(Foo a) 分配作为参数传递的对象。

    changeReference

    enter image description here

  4. 声明名为a的{​​{1}}类型的引用,并为其指定一个类型为changeReference(f); 的新对象,其属性为b

    Foo

    enter image description here

  5. Foo对其属性为"b"的对象的引用Foo b = new Foo("b"); a = b进行了新的分配

    enter image description here


  6. 当您调用a方法时,会创建一个引用f,并为该对象分配属性"b"

    enter image description here

  7. modifyReference(Foo c)会更改引用c的对象的属性指向它,并且它与引用"f"指向它的对象相同。

    enter image description here

  8. 我希望您现在了解如何将对象作为参数传递在Java中:)

答案 3 :(得分:696)

这将为您提供一些有关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并提供给方法
  6. 以下(请不要尝试编译/执行此...):

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

    会发生什么?

    • 变量 person 在第1行创建,开头为空。
    • 在第2行创建一个新的Person对象,存储在内存中,并为变量 person 提供对Person对象的引用。那就是它的地址。比如说3bad086a。
    • 保存Object对象的变量 person 将传递给第3行的函数。
    • 在第4行,你可以听到沉默的声音
    • 检查第5行
    • 上的评论
    • 创建一个方法局部变量 - anotherReferenceToTheSamePersonObject - 然后在#6行中产生魔力:
      • 变量/引用 person 被逐位复制并传递给函数内的 anotherReferenceToTheSamePersonObject
      • 没有创建Person的新实例。
      • person ”和“ anotherReferenceToTheSamePersonObject ”都拥有相同的3bad086a值。
      • 不要尝试这个,但是人== anotherReferenceToTheSamePersonObject会是真的。
      • 两个变量都具有引用的IDENTICAL COPIES,它们都引用相同的Person对象,堆上的SAME对象而不是COPY。

    一张图片胜过千言万语:

    Pass by Value

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

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

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

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

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

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


      

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


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

答案 4 :(得分:620)

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

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

所以,在调用方法时

  • 对于原始参数(intlong等),按值传递原语的实际值(例如,3)。
  • 对于对象,pass by value是对象的引用值。

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

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

答案 5 :(得分:300)

Java按值传递引用。

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

答案 6 :(得分:217)

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

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

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

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

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

结果如下:

primitives on the stack

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

像这样:

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

a b*7ch aint one!

数组是一个对象,因此它也会在堆上运行。那么阵列中的对象呢?他们获得了自己的堆空间,每个对象的地址都在数组内部。

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

marx brothers

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

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

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

答案 7 :(得分:176)

只是为了显示对比度,请比较以下C++Java摘要:

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

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

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

在Java中,

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

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

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

答案 8 :(得分:158)

Java按值传递对象的引用。

答案 9 :(得分:153)

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

private void foo(Object bar) {
    bar = null;
}

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

将打印出"Hah!"而不是null。这样做的原因是因为barbaz值的副本,它只是对"Hah!"的引用。如果它本身就是实际参考,那么foo会将baz重新定义为null

答案 10 :(得分:146)

我无法相信没人提到Barbara Liskov。当她在1974年设计CLU时,她遇到了同样的术语问题,并通过共享(也称为通过对象共享调用)发明了调用这一术语。按对象调用)针对“按价值调用,其中值为参考”的特定情况。

答案 11 :(得分:109)

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

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

答案 12 :(得分:80)

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

  1. 创建新的Point对象
  2. 创建新的Point引用,并在先前创建的Point对象上初始化对 point(参考)的引用。
  3. 从这里开始,通过Point对象生命,您将通过pnt1访问该对象  参考。所以我们可以说在Java中你通过它的引用来操纵对象。
  4. enter image description here

    Java不通过引用传递方法参数;它按值传递它们。我将使用this site中的示例:

    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对象,并将两个不同的引用关联起来。 enter image description here

    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有他们的名为copiesarg1的{​​{1}}。arg2pnt1 指向同一个对象。 (arg1pnt2相同) enter image description here

    arg2方法中:

    tricky

    enter image description here

    arg1.x = 100; arg1.y = 100; 方法

    中的下一步
    tricky

    在这里,您首先要创建新的Point temp = arg1; arg1 = arg2; arg2 = temp; 点引用,它将指向temp引用的同一个地方。然后将参考arg1移动到arg1参考的同一个地方。 最后arg2指向arg2等同一个地方。

    enter image description here

    此处temp方法的范围已消失,您无法再访问引用:trickyarg1arg2但重要的一点是,当你在“生活中”使用这些引用时所做的一切将永久地影响它们指向的对象。

    因此,在执行方法temp后,当您返回tricky时,您遇到以下情况: enter image description here

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

    main

答案 13 :(得分:76)

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内部值,这只是一个例子)。

first illustration

new PassByValue().changeValue(t);

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

second illustration

如果使用引用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,如下所示:

third illustration

希望这会有所帮助。

答案 14 :(得分:68)

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

获取框外视图,让我们看看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 to the stack。
  5. 223被复制到其中一个CPU寄存器。
  6. 223获得PUSHd到堆栈。
  7. 在高于某个值的每种情况下 - 现有值的副本 - 已经创建,现在由接收方法来处理它。当你写" Foo"在方法内部,它要么从EAX中读出,要么自动取消引用,或者双重解引用,这个过程取决于语言的工作方式和/或Foo的类型。这是开发人员隐藏的,直到她绕过解除引用过程。因此,引用在表示时是,因为引用是必须处理的值(在语言级别)。

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

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

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

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

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

答案 15 :(得分:58)

Java是按值调用

工作原理

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

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

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

答案 16 :(得分:52)

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

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

  

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

答案 17 :(得分:52)

据我所知,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,因为在swap函数中你使用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这样的不可变对象。

答案 18 :(得分:48)

让我试着在四个例子的帮助下解释我的理解。 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:

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

有些人说原始类型和&#39; String&#39;是通过价值&#39;   和对象通过引用传递。

但是从这个例子中,我们可以理解它只是通过价值,   请记住,这里我们将引用作为值传递。   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)中提到的内容之外,我们无法更改原始范围之外的实际引用。&#34;

注意:我没有粘贴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]

答案 19 :(得分:46)

您永远不能通过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中不可用。

答案 20 :(得分:45)

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

首先,What's the difference between passing by reference vs. passing by value?

  

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

     

按值传递表示被调用函数的参数将是副本   来电者的通过论证。

或者来自维基百科,on the subject of pass-by-reference

  

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

on the subject of pass-by-value

  

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

其次,我们需要知道Java在其方法调用中使用了什么。 Java Language Specification

  

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

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

参数的价值是什么?

让我们考虑参考类型,Java Virtual Machine Specification状态

  

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

Java Language Specification也说明了

  

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

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

所以

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

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

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

  

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

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


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

答案 21 :(得分:36)

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

答案 22 :(得分:34)

许多人之前提到过,Java is always pass-by-value

这是另一个帮助您了解差异的示例(the classic swap example):

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 :(得分:32)

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

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

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

对于Objects,这是相同的: 对象变量是仅包含对象的地址的指针(桶),它是使用&#34; new&#34;关键字,并像原始类型一样被复制。

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

&#34;字符串&#34;对象似乎是城市传说的完美反例,说&#34;对象通过引用传递&#34;:

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

一个String对象,通过一个无法修改的声明 final 的数组来保存字符。 只有对象的地址可能被另一个使用&#34; new&#34;替换。 使用&#34; new&#34;要更新变量,不要让对象从外部访问,因为变量最初是按值传递并复制的。

答案 24 :(得分:32)

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 :(得分:30)

我一直认为它是“通过副本”。它是值的原始或引用的副本。如果它是原语,则它是作为值的位的副本,如果它是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

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

答案 26 :(得分:25)

我为任何编程语言here创建了一个致力于这类问题的线程。

Java is also mentioned。以下是简短摘要:

  • Java按值传递参数
  • “by value”是java将参数传递给方法的唯一方法
  • 使用作为参数给出的对象的方法将改变 对象作为引用指向 原始物体。 (如果说 方法本身改变了一些值)

答案 27 :(得分:24)

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

答案 28 :(得分:24)

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

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

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

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

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

答案 29 :(得分:22)

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

长话短说:

  

来自C#的人:没有&#34; out&#34;参数

     

来自PASCAL的人:没有&#34; var&#34;参数

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

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

答案 30 :(得分:20)

在所有答案中,我们看到Java传递值或者更确切地说是@Gevorg 写道:“传递副本的变量值”这是我们应该始终牢记的想法。

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

从[1]在Java中,你总是通过副本传递参数;那就是你总是在函数内部创建一个新的值实例。但是有一些行为可以让你认为你是通过参考传递的。

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

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

按副本/按值传递

的示例

来自[ref 1]

的示例
void incrementValue(int inFunction){
  inFunction ++;
  System.out.println("In function: " + inFunction);
}

int original = 10;
System.out.print("Original before: " + original);
incrementValue(original);
System.out.println("Original after: " + original);

We see in the console:
 > Original before: 10
 > In Function: 11
 > Original after: 10 (NO CHANGE)

来自[ref 2]

的示例
  

很好地展示了这种机制   watch max 5 min

(通过引用传递)pass-by-copy-of-variable-value

来自[ref 1] 的示例 (记住数组是一个对象)

void incrementValu(int[] inFuncion){
  inFunction[0]++;
  System.out.println("In Function: " + inFunction[0]);
}

int[] arOriginal = {10, 20, 30};
System.out.println("Original before: " + arOriginal[0]);
incrementValue(arOriginal[]);
System.out.println("Original before: " + arOriginal[0]);

We see in the console:
  >Original before: 10
  >In Function: 11
  >Original before: 11 (CHANGE)

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

来自[ref 3]

的示例
package com.pritesh.programs;

class Rectangle {
  int length;
  int width;

  Rectangle(int l, int b) {
    length = l;
    width = b;
  }

  void area(Rectangle r1) {
    int areaOfRectangle = r1.length * r1.width;
    System.out.println("Area of Rectangle : " 
                            + areaOfRectangle);
  }
}

class RectangleDemo {
  public static void main(String args[]) {
    Rectangle r1 = new Rectangle(10, 20);
    r1.area(r1);
  }
}

矩形区域为200,长度= 10,宽度= 20

最后一件事我想分享一下这个讲座的时刻: 的 Memory Allocation 我发现非常有助于理解Java传递的值,或者更确切地说是“传递给变量值的副本”,正如@Gevorg写的那样。

  1. REF 1 Lynda.com
  2. REF 2 Professor Mehran Sahami
  3. REF 3 c4learn

答案 31 :(得分:20)

这是回答问题的最佳方式...

首先,我们必须了解,在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中寻找的是变量赋值的工作原理。我在the docs找到了它:

  

您将遇到的最常见的运算符之一是简单赋值运算符“=”[...] 它将右侧的值分配给左侧的操作数:

     

int cadence = 0;
   int speed = 0;
   int gear = 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] 中对象是相同的,然后我们为其中一个分配了一个新对象,之后,这些对象具有不同的值!如果他们是相同的,他们现在如何区别?他们在这里也应该是一样的! ?????

我们需要记住the docs以了解正在发生的事情:

  

此运算符也可用于对象以指定对象引用

所以我们的两个变量是存储引用...我们的变量在 [4] 之后具有相同的引用,并且 [6] 之后的不同引用...如果这样的话事情是可能的,这意味着对象的分配是通过对象引用的副本完成的,否则,如果它不是引用的副本, [6] 中变量的打印值应该是相同。因此,对象(引用)就像基元一样,通过赋值复制到变量,人们通常称之为按值传递。这是Java中唯一的 pass-by -

答案 32 :(得分:18)

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

当方法修改基本类型参​​数时,对参数的更改不会影响调用方法中的原始参数值。

对于对象,对象本身不能传递给方法。所以我们传递在引用变量中保存的对象的地址。

Java如何创建和存储对象:当我们创建对象时,我们将对象的地址存储在引用变量中。 “扫描仪输入”是类型和参考变量,“=”是赋值运算符,“新”是从系统请求所需的空间量。创建对象的关键字new右侧的构造函数由关键字new隐式调用。使用assign运算符将创建的对象的地址(右变量的结果,即表达式)分配给左变量(具有指定名称和类型的引用变量)。 “new Account()”被称为“类实例创建表达式”。

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

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

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

Pass by value example 1

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

public class Test
{
    public static void main(String args)
    {
        int[] array1 = { 1, 10, -7 };

        reverseArray(array1);
    }

    public void reverseArray(Int[] array1)
    {
        // ...
    }
}

Pass by value example 2

所以,如果我们说

array1[0] = 5;

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

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

array1 = array2;

在reverseArray方法中,方法reverseArray中的引用变量array1将停止指向数组a并开始指向数组c(第二个图像中的虚线)。

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

return array2; // This code is in reverseArray method.

所以我们马上写下我们现在做的所有事情。

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

        array1 = reverseArray(array1); /* array1 of 
         main starts pointing to c instead of a */
    }

    public void reverseArray(Int[] array1)
    {
        int[] array2 = { -7, 0, -1 };

        array1[0] = 5; // array a becomes 5, 10, -7

        array1 = array2; /* array1 of reverseArray starts
          pointing to c instead of a (not shown in image below) */
        return array2;
    }
}

enter image description here

现在reverseArray方法已经结束了,它的引用变量(array1和array2)已经消失了。这意味着我们现在只有main方法array1和array2中的两个引用变量分别指向c和b数组。没有引用变量指向对象(数组)a。所以它有资格进行垃圾收集。

您还可以将main2中的array2值赋给array1。 array1将开始指向b。

答案 33 :(得分:18)

这真的很简单:

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

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

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


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

答案 34 :(得分:18)

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

答案 35 :(得分:17)

Java通过常量引用传递,其中传递了引用的副本,这意味着它基本上是按值传递。如果类是可变的,您可能会更改引用的内容,但您无法更改引用本身。换句话说,地址不能改变,因为它是通过值传递的,但地址指向的内容可以改变。在不可变类的情况下,引用的内容也不能改变。

答案 36 :(得分:17)

Java编程语言最大的困惑之一是Java是通过值传递还是通过参考传递

首先,我们应该理解通过值或通过引用传递的含义。

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

按引用传递:将实际参数的别名或引用传递给方法,这就是它被称为按引用传递的原因。

假设我们有一个类Balloon,如下所示。

public class Balloon {

    private String color;

    public Balloon(){}

    public Balloon(String c){
        this.color=c;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

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

public class Test {

    public static void main(String[] args) {

        Balloon red = new Balloon("Red"); //memory reference 50
        Balloon blue = new Balloon("Blue"); //memory reference 100

        swap(red, blue);
        System.out.println("red color="+red.getColor());
        System.out.println("blue color="+blue.getColor());

        foo(blue);
        System.out.println("blue color="+blue.getColor());

    }

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

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

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

red color=Red
blue color=Blue
blue color=Red

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

让我们逐步分析程序执行情况。

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

当我们使用new运算符创建类的实例时,将创建实例,并且变量包含保存对象的内存的引用位置。对于我们的示例,假设“red”指向50并且“blue”指向100,这些是两个Balloon对象的内存位置。

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

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

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

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

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

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

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

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

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

我希望上面的解释能够清除所有的疑问,只需记住变量是引用或指针,并将其副本传递给方法,因此Java总是按值传递。您将更清楚地了解堆内存和堆栈内存以及存储不同对象和引用的位置。

答案 37 :(得分:16)

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

考虑接下来的3种情况:

1)尝试更改原始变量

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

int a = 3;
increment(a);

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

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

public static void increment(Person p) { p.age++; }

Person pers = new Person(20); // age = 20
increment(pers);

p将复制pers的参考值并将增加age字段,变量引用同一个对象以便更改年龄

3)尝试更改参考变量的参考值

public static void swap(Person p1, Person p2) {
    Person temp = p1;
    p1 = p2;
    p2 = temp;
}

Person pers1 = new Person(10);
Person pers2 = new Person(20);
swap(pers1, pers2);

调用swap p1后,来自pers1和pers2的p2复制参考值与值交换,因此pers1和pers2保持不变

因此。您可以在方法中仅更改对象的字段,将参考值的副本传递给此对象。

答案 38 :(得分:16)

毫无疑问,Java肯定是“按值传递”。此外,由于Java(大部分)是面向对象的,并且对象使用引用,因此很容易混淆并认为它是“通过引用传递”

传递值表示您将值传递给方法,如果方法更改传递的值,则实体不会更改。另一方面,通过引用传递意味着将引用传递给方法,如果方法更改它,则传递的对象也会更改。

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

但要测试它是否真的按值传递或通过引用传递,您可以使用基本类型和引用:

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

输出结果为:

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

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

但是当你将一个自定义对象传递给一个方法并且该方法改变它时,它也会改变真实对象,因为即使你传递了这个对象,你也将它的引用作为一个值传递给方法。让我们尝试另一个例子:

@Test
public void sampleTest2(){
    Person person = new Person(24, "John");
    System.out.println(person);
    alterPerson(person);
    System.out.println(person);
}

/**
 * @param person
 */
private void alterPerson(Person person) {
    person.setAge(45);
    Person altered = person;
    altered.setName("Tom");
}

private static class Person{
    private int age;
    private String name; 

    public Person(int age, String name) {
        this.age=age;
        this.name =name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("Person [age=");
        builder.append(age);
        builder.append(", name=");
        builder.append(name);
        builder.append("]");
        return builder.toString();
    }

}

在这种情况下,输出为:

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

答案 39 :(得分:15)

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

  • Java总是按值传递所有内容
  • 表示引用也按值传递

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

答案 40 :(得分:13)

Java严格按值传递

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

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

因此,形式参数只不过是该位置的别名。对于该位置所做的任何修改都是可见的,其中变量的范围(标识该位置)是活着的。

在Java中,没有指针的概念(即:没有任何东西称为指针变量),虽然我们可以在java中将引用变量视为技术指针,但我们将其称为句柄。我们在java中将指向地址的指针称为句柄的原因是因为指针变量不仅可以执行单个解除引用而且可以执行多个解除引用 例如:P中的int *p;表示p指向整数 并且C中的int **p;表示p是指向整数的指针 我们在Java中没有这个工具,所以它绝对正确且在技术上合法地说它作为句柄,在C中也有指针算法的规则。它允许对具有约束的指针执行算术运算。

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

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

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

因此Java 严格使用按值传递机制

答案 41 :(得分:12)

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

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

    private static void print(String temp) {
        temp = null;
    }
}

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

答案 42 :(得分:12)

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

  

将对象引用传递给方法时,引用本身   通过使用按值调用传递。但是,因为价值   pass指的是一个对象,该值的副本仍将引用   相应的参数所引用的同一个对象。

Java:初学者指南,第六版,Herbert Schildt

答案 43 :(得分:11)

检查语言是否支持pass-by-reference的简单测试只是编写传统的swap。 你能用Java写一个传统的swap(a,b)方法/函数吗?

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

(非Java)基本交换功能结构

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

如果您可以用您的语言编写这样的方法/功能,那么调用

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

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

答案 44 :(得分:9)

我看到所有答案都包含相同的内容:按值传递。但是,Brian Goetz在Valhalla项目上的最新更新实际上以不同的方式回答了问题:

实际上,这是一个常见的“陷阱”问题,关于Java对象是通过值传递还是通过引用传递,而答案是“都不”:对象引用是通过值传递的。

您可以在此处了解更多信息:State of Valhalla. Section 2: Language Model

编辑: Brian Goetz是Java Language Architect,负责Project Valhalla和Project Amber等项目。

编辑2020-12-08 :更新了State of Valhalla

答案 45 :(得分:8)

Java通过值传递原始类型,并通过引用传递类类型

现在,人们喜欢无休止地争论“按引用传递”是否是描述Java等人的正确方法。确实可以。重点是:

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

在我的书中,这称为参考传递。

Brian Bi-Which programming languages are pass by reference?

答案 46 :(得分:8)

为了尝试增加更多内容,我想我已经包含了关于该主题的SCJP学习指南部分。这是来自针对Java行为的Sun / Oracle测试的指南,因此它是用于此讨论的良好来源。

  

将变量传递给方法(目标7.3)

     

7.3确定对象引用和原始值在传递给对参数执行赋值或其他修改操作的方法时的影响。

     

可以声明方法以获取基元和/或对象引用。您需要知道调用者的变量如何(或者如果)受到被调用方法的影响。传递给方法时,对象引用和原始变量之间的区别是巨大而重要的。要理解本节,您需要熟悉本章第一部分中介绍的作业部分。

     

传递对象引用变量

     

将对象变量传递给方法时,必须记住,您传递的是对象引用,而不是实际的对象本身。请记住,引用变量包含一些位(表示底层VM)到达内存中特定对象(在堆上)的方式。更重要的是,您必须记住,您甚至不能传递实际的引用变量,而是传递引用变量的副本。变量的副本意味着您获得该变量中位的副本,因此当您传递引用变量时,您将传递表示如何到达特定对象的位的副本。换句话说,调用者和被调用的方法现在都具有相同的引用副本,因此它们都将引用堆上相同的(非副本)对象。

     

对于此示例,我们将使用java.awt包中的Dimension类:

1. import java.awt.Dimension;
2. class ReferenceTest {
3.     public static void main (String [] args) {
4.         Dimension d = new Dimension(5,10);
5.         ReferenceTest rt = new ReferenceTest();
6.         System.out.println("Before modify() d.height = " + d.height);
7.         rt.modify(d);
8.         System.out.println("After modify() d.height = "
9.     }
10.
11.
12.
13.   }
14. }
  

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

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

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

     

Java是否使用按值传递语义?

     

如果Java通过传递引用变量来传递对象,那是否意味着Java使用对象的pass-by-reference?不完全是这样,尽管你经常会听到并读到它。 Java实际上是在单个VM中运行的所有变量的值传递。按值传递意味着传递变量值。这意味着,传递副本的变量! (那个词再次复制!)

     

如果你传递原语或引用变量没有区别,你总是传递变量中的位副本。因此,对于原始变量,您将传递表示该值的位的副本。例如,如果传递值为3的int变量,则传递表示3的位的副本。然后,被调用的方法获取其自己的值副本,并根据需要进行操作。< / p>      

如果您正在传递对象引用变量,那么您将传递表示对象引用的位的副本。然后,被调用的方法获得自己的引用变量副本,以便根据需要进行操作。但是因为两个相同的引用变量引用完全相同的对象,如果被调用的方法修改了对象(例如通过调用setter方法),调用者将看到调用者的原始变量所引用的对象也已被改变。在下一节中,我们将讨论当我们谈论基元时图片的变化。

     

传值的底线:被调用的方法无法更改调用者的变量,但对于对象引用变量,被调用的方法可以更改所引用变量的对象。更改变量和更改对象之间的区别是什么?对于对象引用,这意味着被调用的方法不能重新分配调用者的原始引用变量并使其引用不同的对象,或者为null。例如,在以下代码片段中,

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

重新分配g不会重新分配f!在bar()方法的末尾,创建了两个Foo对象,一个由局部变量f引用,另一个由引用   本地(参数)变量g。因为doStuff()方法具有引用变量的副本,所以它有一种方法可以获取原始的Foo对象,例如调用setName()方法。但是,doStuff()方法没有办法获取f引用变量。所以doStuff()可以改变f指向的对象内的值,但doStuff()不能改变f的实际内容(位模式)。换句话说,doStuff()可以改变f引用的对象的状态,但它不能使f引用不同的对象!

     

传递原始变量

     

让我们看一下将原始变量传递给方法时会发生什么:

class ReferenceTest {
    public static void main (String [] args) {
      int a = 1;
      ReferenceTest rt = new ReferenceTest();
      System.out.println("Before modify() a = " + a);
      rt.modify(a);
      System.out.println("After modify() a = " + a);
    }
    void modify(int number) {
      number = number + 1;
      System.out.println("number = " + number);
    }
}
  

在这个简单的程序中,变量a被传递给一个名为modify()的方法,   将变量递增1.结果输出如下:

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

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

答案 47 :(得分:7)

Java是按值传递的。

这个帖子已经有很好的答案了。不知何故,关于原始数据类型和对象,我从未明确传递值/引用。因此,我通过以下代码测试了我的满意度和清晰度;可能会帮助寻求类似清晰度的人:

class Test    {

public static void main (String[] args) throws java.lang.Exception
{
    // Primitive type
    System.out.println("Primitve:");
    int a = 5;
    primitiveFunc(a);
    System.out.println("Three: " + a);    //5

    //Object
    System.out.println("Object:");
    DummyObject dummyObject = new DummyObject();
    System.out.println("One: " + dummyObject.getObj());    //555
    objectFunc(dummyObject);
    System.out.println("Four: " + dummyObject.getObj());    //666 (555 if line in method uncommented.)

}

private static void primitiveFunc(int b)    {
    System.out.println("One: " + b);    //5
    b = 10;
    System.out.println("Two:" + b);    //10
}

private static void objectFunc(DummyObject b)   {
    System.out.println("Two: " + b.getObj());    //555
    //b = new DummyObject();
    b.setObj(666);
    System.out.println("Three:" + b.getObj());    //666
}

}

class DummyObject   {
    private int obj = 555;
    public int getObj() { return obj; }
    public void setObj(int num) { obj = num; }
}

如果取消注释行b = new DummyObject(),则之后进行的修改将在 new 对象上进行,即新的实例化。因此,它不会反映在调用方法的地方。但是,否则,这种变化反映为修改仅在&#34;参考&#34;对象,即-b指向相同的虚拟对象。

此主题(https://stackoverflow.com/a/12429953/4233180)中的一个答案中的插图有助于加深理解。

答案 48 :(得分:7)

这有点难以理解,但Java总是复制值 - 点是,通常值是参考。因此,你最终得到了同一个对象而不考虑它...

答案 49 :(得分:7)

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通过值将参数传递给方法,但如果变量指向对象引用,则实际对象也会更改。

答案 50 :(得分:7)

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

  • 通过值传递:调用者和被调用者有两个具有相同值的自变量。

  • Java使用pass by value
    • 传递原始数据时,它会复制原始数据类型的值。
    • 传递对象时,它会复制对象的地址并传递给被调用方法变量。

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

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


    private static void change(int i) {
        System.out.println(i);  //prints 5
        i=10;
        System.out.println(i); //prints 10

    }
}

使用对象的示例:

public class PassByValueObject {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("prem");
        list.add("raj");
        new PassByValueObject().change(list);
        System.out.println(list); // prints [prem, raj, ram]

    }


    private  void change(List list) {
        System.out.println(list.get(0)); // prem
        list.add("ram");
        list=null;
        System.out.println(list.add("bheem")); //gets NullPointerException
    }
}

答案 51 :(得分:7)

有一种非常简单的方法可以理解这一点。 让我们把C ++传递给参考文献。

#include <iostream>
using namespace std;

class Foo {
    private:
        int x;
    public:
        Foo(int val) {x = val;}
        void foo()
        {
            cout<<x<<endl;
        }
};

void bar(Foo& ref)
{
    ref.foo();
    ref = *(new Foo(99));
    ref.foo();
}

int main()
{
   Foo f = Foo(1);
   f.foo();
   bar(f);
   f.foo();

   return 0;
}

结果如何?

1
1
99
99

因此,在bar()为传入的“引用”分配了一个新值之后,它实际上改变了从main本身传入的那个,解释了从主打印99开始的最后一次f.foo()调用。

现在,让我们看看java的内容。

public class Ref {

    private static class Foo {
        private int x;

        private Foo(int x) {
            this.x = x;
        }

        private void foo() {
            System.out.println(x);
        }
    }

    private static void bar(Foo f) {
        f.foo();
        f = new Foo(99);
        f.foo();
    }

    public static void main(String[] args) {
        Foo f = new Foo(1);
        System.out.println(f.x);
        bar(f);
        System.out.println(f.x);
    }

}

它说:

1
1
99
1

Voilà,传递给酒吧的Foo的主要参考仍未改变!

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

答案 52 :(得分:7)

PT 1:房地产清单

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

聘请了一家本地公司的房地产经纪人并告知他们要保留该房屋的清单。

让我们称之为Realtor&#34; Bob。&#34;嗨鲍勃。

鲍勃保持他的名单tinyHouseAt1234Main的列表,最新的网络摄像头,让他能够实时记录实际房屋的任何变化。他还记录了有多少人询问上市情况。 鲍勃的房子整数viewTally今天是42岁。

每当有人想要了解1234 Main St的蓝色Tiny House时,他们会问鲍勃。

鲍勃查找他的清单tinyHouseAt1234Main并告诉他们所有关于它 - 颜色,漂亮的草坪,阁楼床和堆肥厕所等等。然后他将他们的询问添加到他的viewTally。但他并没有告诉他们真实的实际地址,因为Bob的公司专门研究可随时移动的Tiny Houses。这个数字现在是43。

在另一家公司,房地产经纪人可能会明确表示他们的上市&#34;积分&#34;到了1234 Main St的房子,旁边有一个*,因为它们主要处理很少移动的房屋(尽管可能有理由这样做)。鲍勃的公司并不打算这样做。

现在,鲍勃当然不会将实际的房子放在卡车上直接向客户展示 - 这将是不切实际的,也是荒谬的资源浪费。通过他的理货单的完整副本是一回事,但一直绕过整个房子是昂贵和荒谬的。

(除此之外:鲍勃的公司每次有人询问时都没有3D打印所列出的房屋的新的和唯一的副本。这就是新贵,同样命名为网络 - 基于公司和它的衍生公司 - 这是昂贵和缓慢的,人们经常让这两家公司感到困惑,但他们反正很受欢迎)。

在其他一些离海边较近的老公司,像鲍勃这样的房地产经纪人可能根本不存在管理房源。客户可能会咨询Rolodex&#34; Annie&#34; (简称&)表示房屋的直接地址。客户不是从像鲍勃那样的列表中读取引用的房屋细节,而是从Annie(&)获取房屋地址,然后直接前往1234 Main St,有时候不知道他们在那里可能找到什么。

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

嗯,拥有该信息的人是Bob,因此客户端让Bob调用该服务并向其发送该列表的副本。

jobKillingAutomatedListingService(Listing tinyHouseAt1234Main, int viewTally) Bob发送......

该服务最终调用此列表houseToLookAt,但它收到的确实是Bob的列表的完整副本,其中包含完全相同的VALUE,指的是1234 Main St.

这项新服务也有自己的内部统计数据,显示有多少人查看过该商家信息。该服务接受鲍勃的专业礼貌,但它并不真正关心并完全用自己的本地副本覆盖它。今天它的结果是1,而鲍勃仍然是43。

房地产公司称之为&#34;价值传递&#34;因为Bob传递了他的viewTally和他的列表tinyHouseAt1234Main的当前值。他实际上并没有经过整个实体房子,因为那是不切实际的。他也没有像Annie(&)那样传递真实的实际地址。

但是他正在将的副本传递给他所在的房子。在某些方面看起来似乎是一种愚蠢的迂腐差异,但这就是他的公司如何运作...... ..............

<2> PT II:事情变得混乱和危险......

新的自动化服务,不像其他一些时髦的金融和娱乐一样全功能和数学导向科学公司,可能有不可预见的副作用......

一旦给出了一个列表对象,它允许客户使用远程无人机机器人车队实际重新绘制位于1234 Main St的REAL房屋!它允许客户控制机器人推土机,实际上挖掘花坛!这太疯狂了!!!

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

鲍勃在他的实时网络摄像头上观看了这一切。 他辞去了他唯一的工作责任,他告诉客户关于新的丑陋油漆工作&amp;突然缺乏遏制吸引力。毕竟,他的列表 仍然是1234 Main St.新服务的houseToLookAt无法改变这种状况。鲍勃一如既往地准确,尽职地报告他tinyHouseAt1234Main的详细信息,直到他被解雇或者房子被完全摧毁。

该服务唯一可以使用其原始列表的houseToLookAt副本进行处理,将地址从1234 Main St.更改为其他地址,或更改为空白虚无,或像一个像鸭嘴兽一样的随机类型的物体。鲍勃的上市仍然总是指向1234 Main St,不管它还有什么价值。他像往常一样传递当前的价值。

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

如果您通常关心的是列表中房屋的被复制和传递,那么看起来就像是一种挑剔的语义论证,对吧?

我的意思是,如果你从事的是实际购买房屋并将它们移动到其他地址(不像移动或Tiny Homes那样的平台的预期功能),或者你是访问,重命名和改组整个街区,比如某种低级别的上帝玩疯子,那么也许你更关心的是传递那些特定的地址参考而不仅仅是房子细节的最新价值的副本。 ..

答案 53 :(得分:6)

与其他一些语言不同,Java不允许您选择按值传递或按引用传递

所有参数都按值传递。

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

  • 原始值的副本(例如,int和double类型的值)
  • 对象的引用副本。

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

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

参考文献:Java™ How To Program (Early Objects), Tenth Edition

答案 54 :(得分:6)

在我看来,“通过价值传递”是一种可怕的方式来单独描述两个相似但不同的事件。我猜他们应该先问我。

对于原语,我们将原语的实际值传递给方法(或构造函数),无论是整数“5”,字符“c”,还是你有什么。然后,该实际值成为其自己的本地原语。但是对于对象,我们所做的只是给同一个对象一个额外的引用(一个本地引用),这样我们现在有两个引用指向同一个对象。

我希望这个简单的解释有所帮助。

答案 55 :(得分:6)

我制作了这个小图,显示了如何创建和传递数据

Diagram of how data is created and passed

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

这意味着:

  • 您可以更改
  • 功能中myObject 的值
  • 但是,由于myObject不是point
  • ,您无法更改函数内myObject引用的内容
  • 请注意,pointmyObject都是参考,不同的参考,但是,这些参考指向 at相同的new Point(0,0)

答案 56 :(得分:6)

简单程序

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

        //test passing an integer
        int i=0;
        foo_int(i);
        System.out.println(i);//prints 0
    }
}

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

答案 57 :(得分:6)

最短的回答:)

  • Java具有pass-by-value(并且按值传递引用。)
  • C#也有传递参考

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

按引用传递:传递变量的方式是方法内的重新分配甚至在方法之外反映。

以下是传递引用(C#)的示例。 java中不存在此功能。

class Example
{
    static void InitArray(out int[] arr)
    {
        arr = new int[5] { 1, 2, 3, 4, 5 };
    }

    static void Main()
    {
        int[] someArray;
        InitArray(out someArray);

        // This is true !
        boolean isTrue = (someArray[0] == 1);
    }
}

另请参阅:MSDN library (C#): passing arrays by ref and out

另请参阅:MSDN library (C#): passing by by value and by reference

答案 58 :(得分:6)

一切都是通过价值传递的。基元和对象引用。但是如果对象允许,可以更改对象。

将对象传递给方法时,将传递引用,并且方法实现可以修改该对象。

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”是引用(指向对象的指针),不能更改。

原始类型按值传递。对象的引用也可以被认为是原始类型。

总结一下,一切都按价值传递。

答案 59 :(得分:5)

已经有很好的答案可以解决这个问题。我想通过分享一个非常简单的示例(将进行编译)做出一点点贡献,该示例将对比c ++中的按引用传递和Java中的按值传递之间的行为。

几点:

  1. 术语引用是重载的。在Java中,它只是表示一个指针。在术语“按引用传递”中,是指对传入的原始变量的引用。
  2. Java是按值传递,但允许我们通过按值传递Java引用(即指针)来模仿pass be reference。意味着它传递了Java参考的副本。
  3. C ++允许通过引用传递,方法是使用“&”字符(恰好与C和C中用于表示“变量地址”的字符相同)来声明引用参数C ++)。例如,如果我们传入一个指针,则参数和参数不仅指向相同的对象,而且它们是相同的变量。如果将其中一个设置为其他地址或设置为null,则另一个设置也将设置为
  4. 在下面的C ++示例中,我通过引用将指针传递给空终止的字符串 。在下面的Java示例中,我按值传递了对String的Java引用(再次与指向String的指针相同)。注意注释中的输出。

C ++通过参考示例:

using namespace std;
#include <iostream>

void change (char *&str){   // the '&' makes this a reference parameter
    str = NULL;
}

int main()
{
    char *str = "not Null";
    change(str);
    cout<<"str is " << str;      // ==>str is <null>
}

Java通过值示例传递“一个Java参考”

public class ValueDemo{

    public void change (String str){
        str = null;
    }

     public static void main(String []args){
        ValueDemo vd = new ValueDemo();
        String str = "not null";
        vd.change(str);
        System.out.println("str is " + str);        // ==> str is not null
     }
}

编辑

几个人发表了评论,似乎表明他们不是在看我的例子,还是没有得到c ++例子。不知道断开连接在哪里,但是不清楚c ++示例。我发布一个Pascal示例,因为我认为通过引用传递在pascal中看起来更干净,但我可能是错的。我可能只会让人们更加困惑;我希望不会。

在pascal中,按引用传递的参数称为“ var参数”。在下面的过程setToNil中,请注意在参数“ ptr”之前的关键字“ var”。当指针传递给该过程时,它将通过引用传递。请注意行为:当此过程将ptr设置为nil(pascal表示NULL)时,它将把参数设置为nil -在Java中不能这样做。 (我的Pascal不太好,因为已经有一段时间了。)

program passByRefDemo;
type 
   iptr = ^integer;
var
   ptr: iptr;

   procedure setToNil(var ptr : iptr);
   begin
       ptr := nil;
   end;

begin
   new(ptr);
   ptr^ := 10;
   setToNil(ptr);
   if (ptr = nil) then
       writeln('ptr seems to be nil');     { ptr should be nil, so this line will run. }
end.

编辑2

Ken Arnold, James Gosling(发明Java的人) Holmes,第2章,第2.6节,摘录自“ Java编程语言” 。 5

  

方法的所有参数均按“值”传递。换一种说法,   方法中参数变量的值是调用程序的副本   指定为参数。

他继续就物体提出了相同的观点。 。 。

  

您应该注意,当参数是对象引用时,它是   “按值” 传递的对象引用而非对象本身。

在同一部分的结尾处,他对Java仅通过值传递而从不传递引用进行了更广泛的说明。

  

Java编程语言不会通过引用传递对象;它   按值传递对象引用。因为两份相同   引用指的是同一实际对象,通过一个所做的更改   参考变量通过另一个可见。恰好有一个   参数传递模式-按值传递-有助于保持状态   简单。

本书的这一部分对Java中的参数传递以及引用传递和值传递之间的区别进行了很好的解释,它是Java的创建者所为。我会鼓励任何人阅读它,尤其是如果您仍然不相信的话。

我认为这两个模型之间的差异非常微妙,除非您在实际使用传递引用的地方进行了编程,否则很容易错过两个模型之间的差异。

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

编辑3

我可能对这个帖子有些痴迷。可能是因为我觉得Java的制造商无意间散布了错误信息。如果他们没有使用“引用”一词作为指针,而是使用了其他内容,请说 dingleberry,不会有问题。您可能会说:“ Java通过值而不是通过引用传递dingleberries”,并且不会有人感到困惑。 (因此,当引用按引用与值进行传递时,我将引用称为dinglebarries。)

这就是只有Java开发人员对此有疑问的原因。他们看着“指称”一词,以为自己确切地知道那是什么意思,因此他们甚至不必理会反对的论点。

无论如何,我注意到一个才华横溢的人的评论,他做出了我非常喜欢的气球比喻。如此之多,以至于我决定将一些剪贴画粘合在一起,制作出一系列动画片来说明这一点。找不到评论了,否则我会给这个人以荣誉。 (我可能会误会我的名字,而我却是个比喻。在这种情况下,我可能很聪明;甚至可能是个“稳定的天才”。)

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

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

答案 60 :(得分:5)

我试图简化上面的例子,只保留问题的本质。 让我把这个作为一个易于记忆和正确应用的故事。 故事是这样的: 你有一只宠物狗,吉米,它的尾巴长12英寸。 当你出国旅行时,你带着兽医留了几个星期。

兽医不喜欢吉米的长尾,所以他想把它减半。 但作为一名优秀的兽医,他知道他没有权利残害其他人的狗。 因此,他首先对狗进行克隆(使用 new 关键词)并剪切克隆的尾部。 当狗最终回到你身边时,它的原始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 clone
        dog.cutTail(6);    // cut the clone's tail
        dog.wag();
    }

    public static void badVet (Dog dog) {
        System.out.println("At the bad vet:");
        dog.wag();
        dog.cutTail(2);   // cut the original dog's tail
        dog.wag();
    }    
}

class Dog {

    int tailLength;

    public Dog(int originalLength) {
        this.tailLength = originalLength;
    }

    public void cutTail (int newLength) {
        this.tailLength = newLength;
    }

    public void wag()  {
        System.out.println("Wagging my " +tailLength +" inch tail");
    }
}

Output:
At the owner's home:
Wagging my 12 inch tail
At the good vet:
Wagging my 12 inch tail
Wagging my 6 inch tail
With the owner again:)
Wagging my 12 inch tail
At the bad vet:
Wagging my 12 inch tail
Wagging my 2 inch tail
With the owner again(:
Wagging my 2 inch tail

答案 61 :(得分:5)

A:Java确实通过引用来操作对象,并且所有对象变量都是引用。但是,Java不会通过引用传递方法参数。它通过值传递它们。

以badSwap()方法为例:

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

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

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

如果执行此main()方法,则会看到以下输出:

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

即使通过值传递,该方法也会成功更改pnt1的值;但是,pnt1和pnt2的交换失败!这是造成混乱的主要原因。在main()方法中,pnt1和pnt2仅仅是对象引用。当您将pnt1和pnt2传递给rickey()方法时,Java会像其他任何参数一样按值传递引用。这意味着传递给该方法的引用实际上是原始引用的副本。下面的图1显示了Java将对象传递给方法后,两个指向同一对象的引用。

enter image description here
图1.传递给方法后,一个对象将至少具有两个引用

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

答案 62 :(得分:5)

Java按值传递参数,但对于对象变量,值本质上是对象的引用。由于数组是对象,因此以下example code显示了差异。

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 | 
      +---+ 

答案 63 :(得分:5)

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

检查评论以了解执行中发生的事情;按照数字显示执行流程。

class Example
{
    public static void test (Cat ref)
    {
        // 3 - <ref> is a copy of the reference <a>
        // both currently reference Grumpy
        System.out.println(ref.getName());

        // 4 - now <ref> references a new <Cat> object named "Nyan"
        ref = new Cat("Nyan");

        // 5 - this should print "Nyan"
        System.out.println( ref.getName() );
    }

    public static void main (String [] args)
    {
        // 1 - a is a <Cat> reference that references a Cat object in memory with name "Grumpy"
        Cat a = new Cat("Grumpy");

        // 2 - call to function test which takes a <Cat> reference
        test (a);

        // 6 - function call ends, and <ref> life-time ends
        // "Nyan" object has no references and the Garbage
        // Collector will remove it from memory when invoked

        // 7 - this should print "Grumpy"
        System.out.println(a.getName());
    }
}

答案 64 :(得分:4)

围绕这个问题的许多困惑来自于Java试图重新定义&#34; Pass by value&#34; &#34;通过引用传递&#34;意思。了解这些是Industry Terms非常重要,并且在该背景之外无法正确理解。它们旨在帮助您编写代码并且有助于理解,因此请首先介绍它们的含义。

可以找到两者的良好描述here

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

按引用传递函数接收的值是对调用者正在使用的对象的引用。函数对值引用的对象所做的任何操作都将被调用者看到,并且它将从那一点开始处理那些更改。

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

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

不,C ++的特殊&#34;通过引用&#34;语法不是通过引用传递的唯一定义。它纯粹是一种方便的语法,旨在使你不必在传入指针后使用指针语法。它仍然传递一个指针,编译器只是隐藏了你的事实。它仍然传递指针BY VALUE,编译器只是将它隐藏起来。

因此,有了这种理解,我们可以看看Java并看到它实际上都有。所有Java基元类型总是按值传递,因为您收到了调用者对象的副本,并且无法修改其副本。所有Java引用类型始终通过引用传递,因为您收到对调用者对象的引用,并且可以直接修改其对象。

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

答案 65 :(得分:4)

分两步了解:

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

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

答案 66 :(得分:4)

@Scott Stanchfield先生写了一个很好的答案。这是您要验证的课程 正是他的意思:

public class Dog {

    String dog ;
    static int x_static;
    int y_not_static;

    public String getName()
    {
        return this.dog;
    }

    public Dog(String dog)
    {
        this.dog = dog;
    }

    public void setName(String name)
    {
        this.dog = name;
    }

    public static void foo(Dog someDog)
    {
        x_static = 1;
        // y_not_static = 2;  // not possible !!
        someDog.setName("Max");     // AAA
        someDog = new Dog("Fifi");  // BBB
        someDog.setName("Rowlf");   // CCC
    }

    public static void main(String args[])
    {
        Dog myDog = new Dog("Rover");
        foo(myDog);
        System.out.println(myDog.getName());
    }
}

所以,我们从main()传递一只名为Rover的狗,然后我们为我们传递的指针分配一个新地址,但最后,狗的名字不是Rover ,而不是Fifi,当然不是Rowlf,而是Max

答案 67 :(得分:4)

Java仅按值传递。没有通过引用传递,例如,您可以看到以下示例。

package com.asok.cop.example.task;
public class Example {
    int data = 50;

    void change(int data) {
        data = data + 100;// changes will be in the local variable 
        System.out.println("after add " + data);
        }

    public static void main(String args[]) {
        Example op = new Example();
        System.out.println("before change " + op.data);
        op.change(500);
        System.out.println("after change " + op.data);
    }
}

输出:

before change 50
after add 600
after change 50
迈克尔在评论中说:

  

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

答案 68 :(得分:3)

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

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

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

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

enter image description here

考虑传递原始数据类型,即int,float,double等。

public class PrimitiveTypeExample { 
    public static void main(string[] args) {
       int num = 10;
       System.out.println("Value before calling method: " + num);
       printNum(num);
       System.out.println("Value after calling method: " + num);
    }
    public static void printNum(int num){
       num = num + 10;
       System.out.println("Value inside printNum method: " + num);
    }
}

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

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

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

enter image description here

在上面的示例中,ref变量“ book”驻留在执行程序的线程堆栈中,并且在程序执行new Book()时在堆空间中创建Book类的对象。堆中的此存储位置由“ book”引用。当将“ book”作为方法参数传递时,将在同一线程堆栈中的方法的专用堆栈框架中创建“ book”的副本。因此,复制的参考变量指向堆中“ Book”类的同一对象。

enter image description here

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

因此,当任何东西作为方法参数传递时,它始终是Stack实体-基本变量或引用变量。我们从不传递存储在堆中的内容。因此,在Java中,我们总是将值传递到堆栈中,并且按值传递。

答案 69 :(得分:3)

我会用另一种方式说:

在Java中,传递的是引用的对象(而不是对象),而这些引用是按值传递的(引用本身被复制)。

俗话说:对于初学者来说,传递价值可能还不够清楚。例如在Python中,情况相同,但有文章描述它们通过引用传递,仅使用引用。

答案 70 :(得分:3)

似乎一切都是在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 的名称变量,但如果我们更改任何对象的名称值,则它只更改该类的副本的名称(对象)不是每个类都这样,所以当我们 System.out.println(s.name)它正在打印 taleev 时,我们只能改变我们拥有的名称值最初定义的,我们正在改变的值是对象的值而不是实例变量值,所以一旦我们定义了实例变量,我们就无法改变它

所以我认为这就是它如何显示java只处理而不是引用

this

可以理解原始变量的内存分配

答案 71 :(得分:3)

传值的底线:被调用的方法不能改变调用者的方法 变量,虽然对于对象引用变量,被调用的方法可以改变 对象引用的变量。更改变量之间的区别是什么 并改变对象?对于对象引用,它意味着被调用的方法不能 重新分配调用者的原始引用变量并使其引用不同的对象, 或者为null。

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

因为doStuff()方法有一个引用变量的副本,所以它有一种方法可以获得 到原始的Foo对象,例如调用 setName()方法。但是,doStuff()方法没有办法到达 f参考变量。所以doStuff()可以改变对象f中的值 to,但doStuff()不能改变f的实际内容(位模式)。其他 单词,doStuff()可以改变f引用的对象的状态,但它不能 make 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

答案 72 :(得分:3)

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

以badSwap()方法为例:

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

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

public void tricky(Point arg1, Point   arg2)
{ arg1.x = 100; arg1.y = 100; Point temp = arg1; arg1 = arg2; arg2 = temp; }
public static void main(String [] args) { 

 Point pnt1 = new Point(0,0); Point pnt2
 = new Point(0,0); System.out.println("X:
 " + pnt1.x + " Y: " +pnt1.y);

     System.out.println("X: " + pnt2.x + " Y:
 " +pnt2.y); System.out.println(" ");

     tricky(pnt1,pnt2);
 System.out.println("X: " + pnt1.x + " Y:" + pnt1.y);

     System.out.println("X: " + pnt2.x + " Y: " +pnt2.y); }

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

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

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

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

答案 73 :(得分:3)

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

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

    int[] tab = new int[]{1};
    foo(tab);
    System.out.println(tab[0]); // out 3 (tab[0] did change in foo)
}}

我希望这有帮助!

答案 74 :(得分:3)

Java按值传递所有内容!!

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

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

PersonClass variable2;

// variable2和variable1现在都引用相同的对象

variable2 = variable1; 


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

// variable1现在指向variable3

variable1 = variable3;

//这是什么输出?

System.out.println(variable2);
System.out.println(variable1);

Mary 32
Andre 45

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

webPage

答案 75 :(得分:2)

为了简单和详细 它的pass reference by value

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

    // we pass the object to foo
    foo(aDog);
    // aDog variable is still pointing to the "Max" dog when foo(...) returns
    aDog.getName().equals("Max"); // true
    aDog.getName().equals("Fifi"); // false
    aDog == oldDog; // true
}

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

答案 76 :(得分:2)

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

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

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

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

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

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

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

答案 77 :(得分:2)

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

该程序的输出为:

变化值 让我们逐步了解:

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

第三个插图

希望这会有所帮助。

答案 78 :(得分:2)

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

答案 79 :(得分:1)

只有两个版本:

  • 您可以传递值,即 (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.设置(b); b.设置(tmp)。专注于赋值语句是愚蠢的。如果您编写一点代码,您可以在 Java 中做完全相同的事情。

所以Java通过值和对象通过引用传递primivates。 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;
}

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

答案 80 :(得分:1)

长话短说:

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


}

答案 81 :(得分:1)

Java编程语言仅通过值传递参数,即 你不能从内部改变调用方法中的参数值 被叫方法。


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


对于很多人来说,这看起来像是通过引用传递,而且在行为方面,它具有 通过引用传递很多共同点。但是,有两个原因 这是不准确的。

  • 首先,能够改变传递给的东西 方法仅适用于对象,而不适用于原始值。

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


The following code example illustrates this point:
1 public class PassTest {
2
3   // Methods to change the current values
4   public static void changeInt(int value) {
5     value = 55;
6  }
7   public static void changeObjectRef(MyDate ref) {
8     ref = new MyDate(1, 1, 2000);
9  }
10   public static void changeObjectAttr(MyDate ref) {
11     ref.setDay(4);
12   }
13
14 public static void main(String args[]) {
15     MyDate date;
16     int val;
17
18     // Assign the int
19     val = 11;
20     // Try to change it
21     changeInt(val);
22     // What is the current value?
23     System.out.println("Int value is: " + val);
24
25 // Assign the date
26     date = new MyDate(22, 7, 1964);
27     // Try to change it
28     changeObjectRef(date);
29     // What is the current value?
30 System.out.println("MyDate: " + date);
31
32 // Now change the day attribute
33     // through the object reference
34     changeObjectAttr(date);
35     // What is the current value?
36 System.out.println("MyDate: " + date);
37   }
38 }

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

答案 82 :(得分:1)

请阅读。

许多编程语言都允许按引用或按值传递参数。在Java中,我们只能按值传递参数。这施加了一些限制,并且引起了疑问。例如,如果在方法中更改了参数值,方法执行后该值会怎样?您可能还想知道Java如何管理内存堆中的对象值。这个Java Challenger可以帮助您解决有关Java对象引用的这些以及其他常见问题。

更多:

https://www.javaworld.com/article/3512039/does-java-pass-by-reference-or-pass-by-value.html

答案 83 :(得分:0)

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

但是在编译器绑定级别层,它在内部使用未公开给用户的引用。

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

答案 84 :(得分:0)

如果你想把它放在一个句子里容易理解和记忆,最简单的答案:

Java 总是传递带有新引用的值

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

答案 85 :(得分:0)

初学者的简单答案

如果您传递一个复杂的对象(就像狗,显然是常见的例子),则它与传递引用相同。 如果您传递复杂对象的字段(仅类似于狗的名字),则与传递值相同(更改它不会更改该字段的狗/父母)。

请注意,不是整数的对象(不像我们的狗)(如Integer或String)将按值传递。 因此,即使它们是对象,并且您可以在它们上执行的所有操作,也无法在方法内对其进行修改...是的,这是很麻烦的,没有任何意义...

示例(不是狗):

public class HelloWorld {
    private Integer i;
    private String s;
    private Boolean b;

    public static void main(String[] args) {
        HelloWorld h = new HelloWorld();

        h.s = "Bill";
        h.ModMe(h.s);
        h.i = 2;
        h.ModMe(h.i);
        h.b = true;
        h.ModMe(h.b);
        System.out.println(h.s + " " + h.i + " " + h.b);

        h.ModMe(h);
        System.out.println(h.s + " " + h.i + " " + h.b);

        String test = "TEST";
        h.ModMe(test);
        System.out.println(test);
    }

    public void ModMe(Object o) {
        if (o instanceof HelloWorld) {
            ((HelloWorld) o).i = (int) Math.pow(((HelloWorld) o).i, 2);
            ((HelloWorld) o).b = !((HelloWorld) o).b;
            ((HelloWorld) o).s = ((HelloWorld) o).s.concat(" modded successfully");
        } else if (o instanceof Integer) {
            o = (Integer) o + (Integer) o;
        } else if (o instanceof String) {
            o = ((String) o).concat(" is modified.");
        } else if (o instanceof Boolean) {
            o = !(Boolean) o;
        }
    }
}

如果运行该命令,则将整个对象用作参数将修改对象h,如果仅将它们用作参数,则不修改字段s i或b。如果您进行调试,则将注意到对于字段,对象的ID将在运行更改值的行后立即更改。因此,它将自动执行与“新整数”,“新字符串”和“新布尔”相同的操作。

答案 86 :(得分:0)

Java按值传递对象的引用。

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

但是如果引用参数指向另一个对象仍然原始引用将指向原始对象。

答案 87 :(得分:0)

以上这篇文章令人困惑。

假定复制引用并将其传递给方法是按值传递,因为传递了副本。那不是我和许多人所理解的。

语言是按值传递还是按引用传递?我们为什么要问这个问题?

整个问题在调用方法时发生。这种方法是用参数定义的,并用参数调用。

区分参数和自变量很有帮助。该参数是定义方法时指定的接收到的事物的名称。参数是调用该方法时指定的已发送事物的名称。

任何存储在存储器中的内容都有一个地址,可以被访问。该内容地址称为参考。使用参考可以访问内容。如果将引用提供给层,则该层可以访问和更新引用的内容。如果对方法提供了引用,则该方法也可以访问和更新此引用内容。

将参数“传递”给方法参数时会发生什么?复制参数并将其提供给方法参数。

当我们说“按价值传递”和“按引用传递”时,我们了解什么?使用“值”一词时,我们会理解要存储的实际内容,例如某人的名字。当使用“引用”一词时,我们理解指向“值”的内存地址。

因此,当我们说“按值传递”时,我们知道传递给方法参数的参数是一些内容。当我们说“按引用传递”时,我们了解到传递给method参数的参数是指向某些内容的内存地址。

如果传递的是某些内容,则将复制此内容并将其提供给方法。由于给出的内容是副本,因此没有更改原始内容的风险。

另一方面,如果所传递的是对某些内容的引用,则此引用的副本将被复制并提供给该方法,所引用的内容本身不是原始内容的副本,而是是原始内容,仅复制了其参考文献。

在Java中,我知道原始类型作为内容传递,即按值传递,而对象作为对某些内容的引用传递,即按引用传递。

在给方法参数之前先创建引用的副本,但不会将此引用作为值。

掉期测试不是决定性指标。

如果将方法外部的对象传递给方法时可以从方法内部进行更改,则该传递是通过引用进行的。

Java文档和许多文章对此充其量是令人困惑的,最糟糕的是,是错误的。

我的两分钱。

答案 88 :(得分:0)

我认为这个简单的解释可能会帮助您理解,因为我在努力解决这一问题时也想了解同一件事。

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

考虑这个简单的示例,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 names[]={"Mehrose","Michael"};

changeContent(String[] names){
  names[0]="Rose";
  names[1]="Janet"

}

System.out.println(Arrays.toString(names)); //displays [Rose,Janet]

正如我们说过的,我们不能在函数调用中更改复制的引用,而且在单个String对象的情况下也可以看到。原因是names []变量引用了200,names [0]引用了205,依此类推。您会看到我们没有更改names []引用,在函数调用之后,它仍然仍然指向旧的相同引用,但是现在names [0]和names [1]引用已更改。我们仍然坚持我们的定义,那就是我们不能更改引用变量的引用,所以我们没有。

当您将Student对象传递给方法而您仍然能够更改Student名称或其他属性时,会发生同样的事情,这是我们不是在更改实际的Student对象,而是在更改它的内容。 / p>

你不能这样做

Student student1= new Student("Mehrose");

changeContent(Student Obj){
 obj= new Student("Michael") //invalid
 obj.setName("Michael")  //valid

}

答案 89 :(得分:0)

公共课程测试{

static class Dog {
    String name;

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public 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 foo
    foo(aDog);
    Dog oldDog = aDog;

    System.out.println(" 1: " + aDog.getName().equals("Max")); // false
    System.out.println(" 2 " + aDog.getName().equals("huahua")); // false
    System.out.println(" 3 " + aDog.getName().equals("moron")); // true
    System.out.println(" 4 " + " " + (aDog == oldDog)); // true

    // part2
    Dog aDog1 = new Dog("Max");

    foo(aDog1, 5);
    Dog oldDog1 = aDog;

    System.out.println(" 5 : " + aDog1.getName().equals("huahua")); // true
    System.out.println(" part2 : " + (aDog1 == oldDog1)); // false

    Dog oldDog2 = foo(aDog1, 5, 6);
    System.out.println(" 6 " + (aDog1 == oldDog2)); // true
    System.out.println(" 7 " + (aDog1 == oldDog)); // false
    System.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"); // true
    d.setName("huahua");
    return d;
}

}

该示例代码演示了在不同功能下对象更改的影响。

答案 90 :(得分:0)

我不同意Java只通过值传递。让我们看看这个代码片段

import java.util.ArrayList;
public class Main {
    public static void getList(ArrayList<String> list){
        list.add("Hello") ;
    }
    public static void main(String[] args) {
        ArrayList<String> list=new ArrayList();
        System.out.println(list);
        getList(list);
        System.out.println(list);
    }
}

输出

[]
[Hello]

如果JAVA是通过值调用的,那么为什么在调用函数后为什么要更改我的列表?该函数应该只更改了变量的副本,而原始列表却保持不变

答案 91 :(得分:-1)

这里的每一个答案都是通过引用从其他语言中获取传递指针,并展示在 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,它的作用是立即创建对象的克隆,它需要在对象本身的类中实现,并且类需要标记为{{1} }.之后将 Cloneable 设置为 so 不是必需的,但我在这里这样做是为了表明我们不会在此之后使用该引用。

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

答案 92 :(得分:-4)

TL; DR:最小例子:

package foobar;
import java.util.ArrayList;

public class FooBar {

  public static void main(String[] args) {
    ArrayList<Integer> list1 = new ArrayList<>(); // An object.
    list1.add(1);

    ArrayList<Integer> list2 = new ArrayList<>(); // Another object.
    list2.add(2);

    int x = 42; // A primitive (same with Integer).

    sideEffects(list1, list2, x);

    System.out.println(list1); // Output: [1]     (unchanged)
    System.out.println(list2); // Output: [2, 3]  (changed !)
    System.out.println(x);     // Output: 42      (not changed)
  }

  private static void sideEffects(ArrayList<Integer> list1, ArrayList<Integer> list2, int x) {
    list1 = list2;
    list1.add(3);
    x = 21;
  }
}

答案 93 :(得分:-7)

作为计算机科学专业的毕业生和一般的软件开发人员...并且已经研究过编译器和计算机语言... Java正在通过引用传递对象。哪个传递的引用值与C ++或C的用法没有什么不同。

答案 94 :(得分:-7)

非常简单!如果变量是原始变量,则将按值传递;如果对象是变量,则将按引用传递。

仅此而已