关于方法:Java是”通过引用”还是”按值传递”?

Is Java “pass-by-reference” or “pass-by-value”?

我一直认为Java是通过引用传递的。

但是,我看到过一些博客文章(例如,这个博客)声称它不是。

我想我不明白他们的区别。

解释是什么?


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

就像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

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

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

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

同样地:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

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

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

在上面的例子中,Fifi是在调用foo(aDog)之后的狗的名称,因为对象的名称设置在foo(...)的内部。food上执行的任何操作,就所有实际目的而言,都是在aDog上执行的,但不可能改变变量aDog本身的值。


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

Java规范说Java中的所有东西都是通过值传递的。在爪哇,没有所谓的"通行证"。

理解这一点的关键是

1
Dog myDog;

不是狗,而是指向狗的指针。

这意味着,当你

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

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

(我说的基本上是因为Java指针不是直接地址,但是用这种方法最容易想到它们)

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

如果方法定义为

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

让我们看看发生了什么。

  • 参数someDog设置为值42。
  • 在"AAA"线上
    • someDogDog指向(地址42的Dog对象)
    • 要求Dog(地址42的那个)把他的名字改成Max
  • 在线"BBB"
    • 创建新的Dog。假设他在74号地址
    • 我们将参数someDog指定为74。
  • 在线"CCC"
    • 一些狗被跟踪到Dog,它指向(地址74处的Dog对象)
    • 要求Dog(地址74)改为Rowlf
  • 然后,我们回来

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

myDog有变化吗?

有钥匙。

记住,myDog是一个指针,而不是实际的Dog,答案是:myDog仍然有值42;它仍然指向原始的Dog(但请注意,由于行"a a a",它的名称现在是"max"—仍然是同一只狗;myDog的值没有改变。)

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

Java可以像C一样工作,可以分配一个指针,将指针传递给一个方法,跟随方法中的指针,并改变指向的数据。但是,您不能更改指针指向的位置。

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

如果Java具有通过引用语义,则上面定义的EDCOX1 OR 1方法将在EDOCX1×13"Ed"被指派给在线BBB的情况下发生改变。

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


Java总是不passes××arguments参考值。 >

让我解释这一年的实例: >

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main{
     public static void main(String[] args){
          Foo f = new Foo("f");
          changeReference(f); // It won't change the reference!
          modifyReference(f); // It will modify the object that the reference variable"f" refers to!
     }
     public static void changeReference(Foo a){
          Foo b = new Foo("b");
          a = b;
     }
     public static void modifyReference(Foo c){
          c.setAttribute("c");
     }
}

我会解释这步骤: >

declaring命名的Foo参考f型和新的IT assign面向属性"f"Foo带型。 >

1
Foo f = new Foo("f");

enter image description here >

  • 从法学院Foo端,参考型与adeclared name is initially null和它的分配。 >

    1
    public static void changeReference(Foo a)

    enter image description here >

  • changeReference呼叫的方法,将指定的对象a参考,是我过去一年的争论。 >

    1
    changeReference(f);

    enter image description here >

  • declaring命名的Foo参考b型和新的IT assign面向属性"b"Foo带型。 >

    1
    Foo b = new Foo("b");

    enter image description here >

  • a = b让新来的a参考配置,不f,其属性是其《面向"b"。 >

    enter image description here >

  • 你叫我modifyReference(Foo c)法,是人生c参考和面向属性的"f"与分配。 >

    enter image description here >

  • c.setAttribute("c");会改变的,c面向属性的参考点的吧,和它一样,f面向参考点的吧。 >

    enter image description here >

    我希望你明白我现在穿arguments厂如何在Java对象:) >

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

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

    第二步记住,当你将一个对象传递到一个方法中时,你传递的是对象引用而不是对象本身。

    • 学生:大师,这是否意味着Java通过了引用?
    • 主人:蚱蜢,不。

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

  • 变量保存告诉JVM如何到达内存中被引用对象(堆)的位。
  • 当向方法传递参数时,不传递引用变量,而是传递引用变量中位的副本。类似这样:3BAD086A。3BAD086A表示一种到达传递对象的方法。
  • 所以你只要通过3BAD086A,它就是参考值。
  • 您传递的是引用的值,而不是引用本身(而不是对象)。
  • 这个值实际上被复制并提供给方法。
  • 在下面(请不要尝试编译/执行此…):

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

    发生什么事了?

    • 变量person是在第1行中创建的,其开头为空。
    • 在第2行中创建一个新的Person对象,存储在内存中,变量Person被赋予对Person对象的引用。也就是它的地址。比如说3BAD086A。
    • 持有对象地址的变量人物被传递给第3行中的函数。
    • 在第4行,你可以听到寂静的声音。
    • 查看第5行的评论
    • 创建了一个方法局部变量(另一个与esamepersonObject相关的引用),然后在第6行中出现了魔力:
      • 变量/引用人员被逐位复制,并传递给函数内的另一个指向esamepersonObject的引用。
      • 没有创建新的人员实例。
      • "person"和"anotherreferencetothesamepersonobject"都具有相同的值3bad086a。
      • 不要这样做,但是person==anotherreferencetothesamepersonobject将是真的。
      • 这两个变量具有相同的引用副本,它们都引用同一个人对象、堆中的同一对象,而不是副本。

    一幅画胜过千言万语:

    Pass by Value

    请注意,esamepersonObject箭头的另一个引用指向的是对象,而不是变量person!

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

    现在,可以痛恨我了,但请注意,考虑到这一点,在讨论方法参数时传递原始数据类型和对象没有区别。

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

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

    Java is pass-by-value because inside a method you can modify the referenced Object as much as you want but no matter how hard you try you'll never be able to modify the passed variable that will keep referencing (not p _ _ _ _ _ _ _) the same Object no matter what!

    The changeName function above will never be able to modify the actual content (the bit values) of the passed reference. In other word changeName cannot make Person person refer to another Object.

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


    Java总是通过值,从来没有例外。

    那么,为什么任何人都会对此感到困惑,相信Java是通过引用,还是认为他们有Java作为引用传递的例子呢?关键是Java在任何情况下都不能直接访问对象本身的值。对对象的唯一访问是通过对该对象的引用。因为Java对象总是通过引用访问,而不是直接访问,通常将字段和变量和方法参数作为对象进行讨论,而迂腐地说它们只是对对象的引用。混淆源于术语的这种(严格来说,是不正确的)变化。

    因此,在调用方法时

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

    所以如果你有doSomething(foo)public void doSomething(Foo foo) { .. },两个foo复制了指向相同对象的引用。

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


    Java通过值传递引用。

    所以不能更改传入的引用。


    我觉得争论"通过参考值和通过值"并不是很有帮助。

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

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

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

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

    结果如下:

    primitives on the stack

    当您声明和实例化一个对象时。实际对象位于堆中。堆栈上发生了什么?堆上对象的地址。C++程序员将它称为指针,但一些Java开发人员反对"指针"这个词。无论什么。只需知道对象的地址在堆栈上。

    像这样:

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

    a b*7ch aint one!

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

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

    marx brothers

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    private static void shout(String name){
        System.out.println("There goes" + name +"!");
    }

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

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

    la da di da da da da

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


    为了显示对比度,比较下面的C++和Java片段:

    在C++中:注释:坏代码-内存泄漏!但它证明了这一点。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    void cppMethod(int val, int &ref, Dog obj, Dog &objRef, Dog *objPtr, Dog *&objPtrRef)
    {
        val = 7; // Modifies the copy
        ref = 7; // Modifies the original variable
        obj.SetName("obj"); // Modifies the copy of Dog passed
        objRef.SetName("objRef"); // Modifies the original Dog passed
        objPtr->SetName("objPtr"); // Modifies the original Dog pointed to
                                   // by the copy of the pointer passed.
        objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer,
                                       // leaving the original object alone.
        objPtrRef->SetName("objRefPtr"); // Modifies the original Dog pointed to
                                        // by the original pointer passed.
        objPtrRef = new Dog("newObjPtrRef"); // Modifies the original pointer passed
    }

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

    在Java中,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public static void javaMethod(int val, Dog objPtr)
    {
       val = 7; // Modifies the copy
       objPtr.SetName("objPtr") // Modifies the original Dog pointed to
                                // by the copy of the pointer passed.
       objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer,
                                      // leaving the original object alone.
    }

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

    Java仅具有两种传递类型:内置类型的值和对象类型的指针值。


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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    private void foo(Object bar) {
        bar = null;
    }

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

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


    我不敢相信还没有人提到芭芭拉·利斯科夫。1974年,当她设计CLU时,遇到了同样的术语问题,她发明了共享调用(也称为对象共享调用和对象调用)这一术语,用于"值为引用的值调用"这一特定情况。


    问题的关键在于,"通过引用"表达式中的单词引用是指与Java中的单词引用的通常含义完全不同的东西。

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


    在Java中,所有的东西都是引用的,所以当你有这样的东西:EDCOX1〔0〕Java做如下操作:

  • 创建新的点对象
  • 创建新的点引用,并将该引用初始化为先前创建的点对象上的点(引用)。
  • 从这里,通过点对象生命,您将通过PNT1访问该对象。参考文献。因此,我们可以说,在Java中,通过引用来操作对象。
  • enter image description here

    Java不通过引用传递方法参数,而是按值传递它们。我将使用此站点中的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public static void tricky(Point arg1, Point arg2) {
      arg1.x = 100;
      arg1.y = 100;
      Point temp = arg1;
      arg1 = arg2;
      arg2 = temp;
    }
    public static void main(String [] args) {
      Point pnt1 = new Point(0,0);
      Point pnt2 = new Point(0,0);
      System.out.println("X1:" + pnt1.x +" Y1:" +pnt1.y);
      System.out.println("X2:" + pnt2.x +" Y2:" +pnt2.y);
      System.out.println("");
      tricky(pnt1,pnt2);
      System.out.println("X1:" + pnt1.x +" Y1:" + pnt1.y);
      System.out.println("X2:" + pnt2.x +" Y2:" +pnt2.y);  
    }

    程序流程:

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

    创建两个不同的点对象,并关联两个不同的引用。enter image description here

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

    如预期,输出将是:

    1
    2
    X1: 0     Y1: 0
    X2: 0     Y2: 0

    在这一行,"通过值"进入游戏…

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

    引用pnt1pnt2是按价值传递给复杂方法的,这意味着现在你的引用pnt1pnt2copies命名为arg1arg2,所以pnt1arg1指向同一个对象。(与pnt2arg2相同)enter image description here

    tricky方法中:

    1
    2
     arg1.x = 100;
     arg1.y = 100;

    enter image description here

    下一个是tricky方法

    1
    2
    3
    Point temp = arg1;
    arg1 = arg2;
    arg2 = temp;

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

    enter image description here

    从这里开始,tricky方法的范围已经不复存在,您不再可以访问参考:arg1arg2temp。但重要的是,当这些引用"在生活中"时,你对它们所做的一切都将永久地影响它们指向的对象。

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

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

    1
    2
    3
    4
    X1: 0         Y1: 0
    X2: 0         Y2: 0
    X1: 100       Y1: 100
    X2: 0         Y2: 0

    Java总是通过值传递,而不是通过引用传递。

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

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

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class PassByValue {
        public static void main(String[] args) {
            Test t = new Test();
            t.name ="initialvalue";
            new PassByValue().changeValue(t);
            System.out.println(t.name);
        }

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

    class Test {
        String name;
    }

    这个程序的输出是:

    1
    changevalue

    让我们逐步了解:

    1
    Test t = new Test();

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

    first illustration

    1
    new PassByValue().changeValue(t);

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

    second illustration

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class PassByValue {
        public static void main(String[] args) {
            Test t = new Test();
            t.name ="initialvalue";
            new PassByValue().changeRefence(t);
            System.out.println(t.name);
        }

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

    class Test {
        String name;
    }

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

    third illustration

    希望这会有所帮助。


    引用在表示时总是一个值,不管您使用什么语言。

    获取一个框外视图,让我们看看汇编或一些低级内存管理。在CPU级别,如果任何对象被写入内存或某个CPU寄存器,则对该对象的引用将立即成为一个值。(这就是为什么指针是一个好的定义。它是一个值,同时具有目的)。好的。

    内存中的数据有一个位置,在该位置有一个值(字节、字、任何东西)。在汇编中,我们有一个方便的解决方案来给某个位置命名(也称为变量),但是在编译代码时,汇编程序只是用指定的位置替换名称,就像浏览器用IP地址替换域名一样。好的。

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

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

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

    跳台就是这样工作的。好的。

    如果我们想用foo值调用一个方法/函数/过程,有几种可能的方法可以将变量传递给方法,这取决于语言及其几种方法调用模式:好的。

  • 5被复制到一个CPU寄存器(即EAX)。
  • 5被推到堆栈。
  • 47被复制到一个CPU寄存器
  • 47推到堆栈。
  • 223被复制到其中一个CPU寄存器。
  • 223被推到堆栈。
  • 在每种情况下,都会创建一个值(一个现有值的副本)以上的值,现在该值将由接收方法来处理。在方法中编写"foo"时,它要么从eax中读取,要么自动取消引用,要么双重取消引用,过程取决于语言的工作方式和/或foo的类型。在开发人员绕过解引用过程之前,这是隐藏的。因此,引用在表示时是一个值,因为引用是一个必须处理的值(在语言级别)。好的。

    现在我们已经将foo传递给方法:好的。

    • 案例1。2。如果更改foo(Foo = 9),它只会影响本地作用域,因为您有一个值的副本。从方法内部,我们甚至无法确定原始foo在内存中的位置。
    • 案例3。4。如果使用默认语言构造并更改FoO(EDCOX1,1),它可以全局地改变FoO(取决于语言,例如Java或Pascal的EDCOX1×2)var MeDOX1(3)。但是,如果语言允许您绕过取消引用过程,您可以更改47,比如对49。在这一点上,如果你读到foo,它似乎已经被改变了,因为你已经改变了指向它的本地指针。如果您要在方法(Foo = 12中修改这个foo),您可能会妨碍程序的执行(aka)。segfault)因为您将写入一个与预期不同的内存,所以您甚至可以修改一个用于保存可执行程序的区域,并且写入该区域将修改正在运行的代码(foo现在不在47)。但是foo的47的值并没有全局变化,只有方法内部的值,因为47也是方法的副本。
    • 案例5。6。如果在方法内部修改223,它将创建与3相同的混乱。或4。(一个指针,指向一个现在不正确的值,再次用作指针),但这仍然是一个局部问题,因为223已被复制。但是,如果您能够取消引用Ref2Foo(即223),达到并修改47的指向值,即对49的指向值,则会影响全局foo,因为在这种情况下,方法得到了223的副本,但引用的47只存在一次,而将其更改为49将导致Ref2Foo双重取消对错误值的引用。

    对无关紧要的细节吹毛求疵,即使是通过引用传递的语言也会将值传递给函数,但这些函数知道它们必须将其用于取消引用的目的。这个传递引用作为值对程序员来说是隐藏的,因为它实际上是无用的,并且术语只是传递引用。好的。

    严格的传递值也是无用的,这意味着每当我们调用一个数组作为参数的方法时,就必须复制一个100兆字节的数组,因此Java不能通过值严格传递。每种语言都会通过对这个巨大数组的引用(作为一个值),并且如果该方法可以在方法中本地改变,或者允许该方法(如Java所做的)来全局地修改数组(从调用方的视图中),并且一些语言允许修改引用本身的值,那么就可以采用复制写入机制。好的。

    简而言之,在Java自身的术语中,Java是通过值传递的值:要么是实际值,要么是引用的表示值。好的。好啊。


    Java是按值调用

    它是如何工作的

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

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

    • 如果它是一个对象数据类型,如foo foo=new foo(),那么在这种情况下,对象地址的副本会像文件快捷方式一样传递,假设我们在c:desktop有一个文本文件abc.txt,假设我们对同一文件进行快捷方式,并将其放入c:desktopabc快捷方式中,这样当您从c:desktopabc.txt访问文件并写入"堆栈溢出"时'关闭文件,然后再次从快捷方式打开文件,然后写'是程序员要学习的最大的在线社区'然后总的文件更改将是'堆栈溢出是程序员要学习的最大的在线社区'这意味着无论从何处打开文件,每次我们访问同一个文件时,h在此,我们可以假设foo是一个文件,并假设foo存储在123hd7h(原始地址,如c:desktopabc.txt)地址和234jdid(复制地址,如c:desktopabc快捷方式,其中实际包含文件的原始地址)。所以为了更好的理解制作快捷方式文件并感觉…


    不,这不是通时参考。 >

    Java是由通值根据Java语言规范: >

    When the method or constructor is invoked (§15.12), the values of the actual argument expressions initialize newly created parameter variables, each of the declared type, before execution of the body of the method or constructor. The Identifier that appears in the DeclaratorId may be used as a simple name in the body of the method or constructor to refer to the formal parameter.


    据我所知,Java只知道按值调用。这意味着对于基本数据类型,您将使用副本,对于对象,您将使用对象引用的副本。然而,我认为存在一些陷阱;例如,这不起作用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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函数中,您使用的是对主引用没有影响的copy。但是,如果对象不是不变的,您可以更改它,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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是不可变的。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static void appendWorld(String s){
        s = s+" World";
    }

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

    但是,您可以为这样的字符串创建一个包装器,使其能够与字符串一起使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class StringWrapper {
        public String value;

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

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

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

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


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

    /**

    通过值

    在Java中,所有的参数都是通过值传递的,即分配一个方法参数对调用方是不可见的。

    */

    例1:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class PassByValueString {
        public static void main(String[] args) {
            new PassByValueString().caller();
        }

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

            System.out.println("output :" + output);
            System.out.println("value :" + value);
            System.out.println("valueflag :" + valueflag);

        }

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

    结果

    1
    2
    3
    output : output
    value : Nikhil
    valueflag : false

    例2:

    /****传递值**/

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class PassByValueNewString {
        public static void main(String[] args) {
            new PassByValueNewString().caller();
        }

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

            System.out.println("output :" + output);
            System.out.println("value :" + value);
            System.out.println("valueflag :" + valueflag);

        }

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

    结果

    1
    2
    3
    output : output
    value : Nikhil
    valueflag : false

    例3:

    /**这个"传递值"有一种"传递引用"的感觉。

    有些人说基元类型和字符串是"传递值"对象是"按引用传递"。

    但从这个例子中,我们可以理解它只是一个错误的传递值,请记住,这里我们将引用作为值传递。ie:引用是按值传递的。这就是为什么能够改变,并且在本地范围之后仍然保持不变的原因。但是我们不能改变原始范围之外的实际引用。下一个passByValueObjectCase2示例演示了这意味着什么。

    */

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    public class PassByValueObjectCase1 {

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

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

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

            System.out.println("output :" + output);
            System.out.println("student :" + student);
        }

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

    结果

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

    例4:

    /**

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

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

    */

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class PassByValueObjectCase2 {

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

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

            System.out.println("output :" + output);
            System.out.println("student :" + student); // Will it print Nikhil or Anand?
        }

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

    }

    结果

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

    在Java中,您永远不能通过引用,而显而易见的方式之一是当您希望从方法调用返回多个值时。在C++中考虑下面的代码位:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void getValues(int& arg1, int& arg2) {
        arg1 = 1;
        arg2 = 2;
    }
    void caller() {
        int x;
        int y;
        getValues(x, y);
        cout <<"Result:" << x <<"" << y << endl;
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void getValues(int[] arg1, int[] arg2) {
        arg1[0] = 1;
        arg2[0] = 2;
    }
    void caller() {
        int[] x = new int[1];
        int[] y = new int[1];
        getValues(x, y);
        System.out.println("Result:" + x[0] +"" + y[0]);
    }

    正如前面的答案中所解释的,在Java中,您将数组的指针作为一个值传递到EDCOX1(0)中。这就足够了,因为该方法随后会修改数组元素,按照惯例,您希望元素0包含返回值。显然,您可以用其他方法来实现这一点,例如构造代码以使其不必要,或者构造一个可以包含返回值或允许设置返回值的类。但是在C++中可用的简单模式在Java中是不可用的。


    我想我会贡献这个答案来从规范中添加更多细节。

    首先,通过引用传递和通过值传递有什么区别?

    Passing by reference means the called functions' parameter will be the
    same as the callers' passed argument (not the value, but the identity
    - the variable itself).

    Pass by value means the called functions' parameter will be a copy of
    the callers' passed argument.

    或者来自维基百科,关于引用传递的主题

    In call-by-reference evaluation (also referred to as
    pass-by-reference), a function receives an implicit reference to a
    variable used as argument, rather than a copy of its value. This
    typically means that the function can modify (i.e. assign to) the
    variable used as argument—something that will be seen by its caller.

    关于传递值的主题

    In call-by-value, the argument expression is evaluated, and the
    resulting value is bound to the corresponding variable in the function [...].
    If the function or procedure is able to assign values to its
    parameters, only its local copy is assigned [...].

    第二,我们需要知道Java在它的方法调用中使用什么。Java语言规范状态

    When the method or constructor is invoked (§15.12), the values of the
    actual argument expressions initialize newly created parameter
    variables, each of the declared type, before execution of the body of
    the method or constructor.

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

    这个参数的值是多少?

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

    There are three kinds of reference types: class types, array types,
    and interface types. Their values are references to dynamically
    created class instances, arrays, or class instances or arrays that
    implement interfaces, respectively.

    Java语言规范也指出

    The reference values (often just references) are pointers to these objects, and a special null reference, which refers to no object.

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

    所以

    1
    2
    3
    4
    5
    6
    public void method (String param) {}
    ...
    String var = new String("ref");
    method(var);
    method(var.toString());
    method(new String("ref"));

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

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

    This typically means that the function can modify (i.e. assign to) the
    variable used as argument—something that will be seen by its caller.

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

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


    这个区别,或者只是我记忆中的印象,和原来海报的印象一样,就是:Java总是通过价值传递。在爪哇,所有对象(在爪哇,除了原语以外)都是引用。这些引用是按值传递的。


    正如许多人之前提到的,Java总是通过价值传递。

    下面是另一个帮助您理解差异的示例(经典交换示例):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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;
      }
    }

    印刷品:

    Before: a = 2, b = 3
    After: a = 2, b = 3

    这是因为ia和ib是新的局部引用变量,具有相同的传递引用值(它们分别指向a和b)。因此,试图更改IA或IB的引用只会在本地范围内发生更改,而不会超出此方法的范围。


    Java只通旁路哈希值。这是非常简单的验证实例。 >

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public void test() {
        MyClass obj = null;
        init(obj);
        //After calling init method, obj still points to null
        //this is because obj is passed as value and not as reference.
    }
    private void init(MyClass objVar) {
        objVar = new MyClass();
    }


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

    Java参数都通过值传递(当方法使用时复制引用):

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

    对于对象,这是相同的:对象变量是指针(bucket),只保存使用"new"关键字创建的对象地址,并像基本类型一样复制。

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

    "字符串"对象似乎是城市传说中的一个完美反例,即"对象是通过引用传递的":

    实际上,在方法中,您将永远无法更新作为参数传递的字符串的值:

    字符串对象,由声明为final且不能修改的数组保存字符。只有对象的地址可以用"new"替换。使用"new"更新变量,将不允许从外部访问对象,因为变量最初是由值传递并复制的。


    我一直认为这是"传抄"。它是值的副本,无论是原始值还是引用值。如果它是一个原语,它是作为值的位的副本;如果它是一个对象,它是引用的副本。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class PassByCopy{
        public static void changeName(Dog d){
            d.name ="Fido";
        }
        public static void main(String[] args){
            Dog d = new Dog("Maxx");
            System.out.println("name="+ d.name);
            changeName(d);
            System.out.println("name="+ d.name);
        }
    }
    class Dog{
        public String name;
        public Dog(String s){
            this.name = s;
        }
    }

    Java PopBaseX的输出:

    name= Maxx
    name= Fido

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


    我已经为这里的任何编程语言创建了一个专门针对这些问题的线程。

    Java也被提及。以下是简短的总结:

    • Java按值传递参数
    • "按值"是Java将参数传递给方法的唯一方法
    • 使用给定对象的方法作为参数将更改对象作为引用指向原始对象。如果方法本身会改变一些值)

    对一些帖子做一些修改。

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

    不管Java中的值是什么:原语还是对象的粗略地址,它总是通过值传递。

    如果Java对象"行为"类似于它是通过引用传递的,那么这是一个可变属性,与传递机制完全无关。

    我不知道为什么这么混乱,也许是因为这么多的Java"程序员"没有被正式训练,因此不理解在记忆中到底发生了什么?


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

    一般来说,Java有原始类型(EDCOX1,0,EDCOX1,1,EDCOX1,2,EDCX1,3,等等),它们是由值直接传递的。然后Java有对象(从EDCOX1中得出的所有内容4)。对象实际上总是通过引用来处理(引用是一个你不能触摸的指针)。这意味着实际上,对象是通过引用传递的,因为引用通常并不有趣。但是,这并不意味着您不能更改指向哪个对象,因为引用本身是通过值传递的。

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

    把这个给C++,我们有参考文献。引用基本上是(在本文中)隐藏公式指针部分的句法糖分:void foo(int &x)foo(a)调用,其中编译器本身知道它是引用,应传递非引用a的地址。在Java中,所有引用对象的变量实际上都是引用类型,实际上是通过引用来强制调用大多数意图和目的,而不是由C++提供的细粒度控制(和复杂性)。


    Java通过值传递参数,仅通过值传递参数。

    长话短说:

    For those coming from C#: THERE IS NO"out" parameter.

    For those coming from PASCAL: THERE IS NO"var" parameter.

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

    解决方法是使用StringBuilder参数而不是String参数。你可以一直使用数组!


    在所有的答案中,我们看到Java是通过价值传递的,或者说是"@ Gevorg"。写着:"传递变量值的副本",这是我们应该一直铭记在心的想法。

    我将重点放在帮助我理解这个想法的例子上,它是对以前答案的补充。

    从Java中的[1 ]中,你总是通过复制传递参数;也就是说,你总是在函数内创建一个新的值实例。但有些行为会让你觉得你是在引用别人。

    • 通过复制传递:当一个变量传递给一个方法/函数时,会进行一次复制(有时我们会听到当传递原语时,您正在进行复制)。

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

    通过copy/by值传递的示例

    [参考文献1]中的示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    void incrementValue(int inFunction){
      inFunction ++;
      System.out.println("In function:" + inFunction);
    }

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

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

    [参考文献2]中的示例

    shows nicely the mechanism
    watch max 5 min

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

    [参考文献1]中的示例(记住数组是一个对象)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    void incrementValu(int[] inFuncion){
      inFunction[0]++;
      System.out.println("In Function:" + inFunction[0]);
    }

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

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

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

    [参考文献3]中的示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package com.pritesh.programs;

    class Rectangle {
      int length;
      int width;

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

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

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

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

    我最不想分享的是演讲的这一刻:内存分配我发现它非常有助于理解Java传递的值,或者更确切地说,"变量值的副本"是@ GEVORG编写的。

  • REF 1 LyDaNo
  • 参考文献2 Mehran Sahami教授
    • 手表最大5分钟
    • 内存分配
  • 参考文献3
    • 将对象作为参数传递给方法

  • 这是回答我的问题的最好方法…

    首先,我们必须理解,在Java中,参数传递行为…

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public void foo(Object param)
    {
      // some code in foo...
    }

    public void bar()
    {
      Object obj = new Object();

      foo(obj);
    }

    和…

    1
    2
    3
    4
    5
    6
    7
    8
    public void bar()
    {
      Object obj = new Object();

      Object param = obj;

      // some code in foo...
    }

    不考虑堆栈位置,这在本讨论中不相关。

    事实上,我们在Java中寻找的是变量赋值是如何工作的。我在医生那里找到的:

    One of the most common operators that you'll encounter is the simple assignment operator"=" [...] it assigns the value on its right to the operand on its left:

    int cadence = 0;
    int speed = 0;
    int gear = 1;

    This operator can also be used on objects to assign object references [...]

    很明显,这个操作符是如何以两种不同的方式工作的:赋值和赋值引用。最后,当它是一个物体时…第一,当它不是一个物体时,也就是说,当它是一个原始物体时。但是,我们能理解Java的函数PARAM可以通过值传递并通过引用吗?

    真理在法典中。让我们试试看:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    public class AssignmentEvaluation
    {
      static public class MyInteger
      {
        public int value = 0;
      }

      static public void main(String[] args)
      {
        System.out.println("Assignment operator evaluation using two MyInteger objects named height and width
    "
    );

        MyInteger height = new MyInteger();
        MyInteger width  = new MyInteger();

        System.out.println("[1] Assign distinct integers to height and width values");

        height.value = 9;
        width.value  = 1;

        System.out.println("->  height is" + height.value +" and width is" + width.value +", we are different things!
    "
    );

        System.out.println("[2] Assign to height's value the width's value");

        height.value = width.value;

        System.out.println("->  height is" + height.value +" and width is" + width.value +", are we the same thing now?
    "
    );

        System.out.println("[3] Assign to height's value an integer other than width's value");

        height.value = 9;

        System.out.println("->  height is" + height.value +" and width is" + width.value +", we are different things yet!
    "
    );

        System.out.println("[4] Assign to height the width object");

        height = width;

        System.out.println("->  height is" + height.value +" and width is" + width.value +", are we the same thing now?
    "
    );

        System.out.println("[5] Assign to height's value an integer other than width's value");

        height.value = 9;

        System.out.println("->  height is" + height.value +" and width is" + width.value +", we are the same thing now!
    "
    );

        System.out.println("[6] Assign to height a new MyInteger and an integer other than width's value");

        height = new MyInteger();
        height.value = 1;

        System.out.println("->  height is" + height.value +" and width is" + width.value +", we are different things again!
    "
    );
      }
    }

    这是我运行的输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    Assignment operator evaluation using two MyInteger objects named height and width

    [1] Assign distinct integers to height and width values
    ->  height is 9 and width is 1, we are different things!

    [2] Assign to height's value the width's value
    ->  height is 1 and width is 1, are we the same thing now?

    [3] Assign to height's value an integer other than width's value
    ->  height is 9 and width is 1, we are different things yet!

    [4] Assign to height the width object
    ->  height is 1 and width is 1, are we the same thing now?

    [5] Assign to height's value an integer other than width's value
    ->  height is 9 and width is 9, we are the same thing now!

    [6] Assign to height a new MyInteger and an integer other than width's value
    ->  height is 1 and width is 9, we are different things again!

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

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

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

    我们需要记住文档以了解发生了什么:

    This operator can also be used on objects to assign object references

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


    这真是一个相当,相当简单: >

    对原始变量(EG)型。intbooleanchar等…),当你使用它的name参数为法的价值,你是穿在它(含5true,他们'c')。获取"copied this value","变量值retains甚至invocation后其法。 >

    对变频型(Eg =。StringObject等…),当你使用它的name参数测定的方法,你是在它(含低功率值的参考值,对"点"的对象)。这个参考值获取"copied","变量值retains甚至invocation后其法。"pointing参考变量的不到相同的对象。 >

    要么你总是穿路,再由价值的东西。 >

    这样说比较C++在你可以有一个方法把年int&C,或在#在你能把ref int(虽然,在这房子,你也要穿的时候使用refmodifier变量"name"的方法。) >

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


    通常是由Java拷贝参考参考在这过去的均值,它是通过由基本价值。你可以改变的内容的"参考级如果是可变的,但你不能改变参考本身。换句话说我的地址不会被改变,但由于它是由过去的点式值的内容,是可以由地址已经改变了。在家里,由immutable of the content of the参考,要么是不能改变的。 >

    毫无疑问,Java是"价值传递"。此外,由于Java(主要是面向对象的)和对象与引用一起工作,所以很容易被混淆,并认为它是"通过引用"。

    传递值是指将值传递给方法,如果方法更改传递的值,则实际实体不会更改。另一方面,pass-by-reference意味着将引用传递给方法,如果方法更改了该方法,则传递的对象也会更改。

    在爪哇中,通常当我们把对象传递给一个方法时,我们基本上传递对象的引用作为一个值,因为Java就是这样工作的,它与引用和地址一起工作,直到堆中的对象运行为止。

    但要测试它是传递值还是传递引用,可以使用基元类型和引用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Test
    public void sampleTest(){
        int i = 5;
        incrementBy100(i);
        System.out.println("passed ==>"+ i);
        Integer j = new Integer(5);
        incrementBy100(j);
        System.out.println("passed ==>"+ j);
    }
    /**
     * @param i
     */

    private void incrementBy100(int i) {
        i += 100;
        System.out.println("incremented ="+ i);
    }

    输出是:

    1
    2
    3
    4
    incremented = 105
    passed ==> 5
    incremented = 105
    passed ==> 5

    因此,在这两种情况下,无论方法内部发生什么,都不会更改实际对象,因为该对象的值是传递的,而不是对象本身的引用。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    @Test
    public void sampleTest2(){
        Person person = new Person(24,"John");
        System.out.println(person);
        alterPerson(person);
        System.out.println(person);
    }

    /**
     * @param person
     */

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

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

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

        public int getAge() {
            return age;
        }

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

        public String getName() {
            return name;
        }

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

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

    }

    在这种情况下,输出为:

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

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

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

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

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class Balloon {

        private String color;

        public Balloon(){}

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

        public String getColor() {
            return color;
        }

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public class Test {

        public static void main(String[] args) {

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

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

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

        }

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

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

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

    1
    2
    3
    red color=Red
    blue color=Blue
    blue color=Red

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

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

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

    当我们使用new操作符创建一个类的实例时,该实例被创建,变量包含保存对象的内存的引用位置。对于我们的示例,假设"red"指向50,"blue"指向100,这是两个气球对象的内存位置。

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

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

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

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

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

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

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

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

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

    我希望上面的解释清楚了所有的疑惑,只记得变量是引用或指针,它的副本传递给方法,所以Java总是通过值传递。当您了解堆和堆栈内存以及存储不同对象和引用的位置时,您会更加清楚。


    与其他一些语言不同,Java不允许您选择传递值或传递引用,所有参数都是按值传递的。方法调用可以将两种类型的值传递给基元值(例如int和double的值)的方法副本和对象引用的副本。

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

    当涉及到对象时,不能将对象本身传递给方法。所以我们传递对象的地址,它保存在引用变量中。

    Java如何创建和存储对象:当我们创建对象时,我们将对象的地址存储在引用变量中。"scanner input"是类型和引用变量,"="是赋值运算符,"new"要求系统提供所需的空间量。创建对象的关键字new右边的构造函数由关键字new隐式调用。使用赋值运算符将创建的对象的地址(右变量的结果,即表达式)分配给左变量(即指定了名称和类型的引用变量)。"new account()"称为"类实例创建表达式"。

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

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

    在下面的图片中,你可以看到我们有两个参考变量(这些被称为C/C++中的指针,并且我认为这个术语使它更容易理解这个特性)。基本变量和引用变量保存在堆栈内存中(下图左侧)。ARARY1和ARARY2参考变量"点"(如C/C++程序员称之为)或参考A和B数组,它们是堆内存(在下面的图像右侧)对象(这些引用变量保存的对象的值)。

    Pass by value example 1

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class Test
    {
        public static void main(String args)
        {
            int[] array1 = { 1, 10, -7 };

            reverseArray(array1);
        }

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

    Pass by value example 2

    所以,如果我们说

    1
    array1[0] = 5;

    在ReverseArray方法中,它将在数组A中进行更改。

    在ReverseArray方法(array2)中,我们还有另一个引用变量指向数组C。

    1
    array1 = array2;

    在ReverseArray方法中,ReverseArray方法中的引用变量array1将停止指向数组A,并开始指向数组C(第二幅图像中的虚线)。

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

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

    所以让我们马上写下我们已经做过的所有事情。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class Test
    {
        public static void main(String args)
        {
            int[] array1 = { 1, 10, -7 };
            int[] array2 = { 5, -190, 0 };

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

        }

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

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

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

            return array2;
        }
    }

    enter image description here

    现在ReverseArray方法结束了,它的引用变量(array1和array2)也消失了。这意味着我们现在只有主方法array1和array2中的两个引用变量分别指向C和B数组。没有引用变量指向对象(数组)A。因此它符合垃圾收集的条件。

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


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

    考虑以下3种情况:

    1)试图更改原语变量

    1
    2
    3
    4
    public static void increment(int x) { x++; }

    int a = 3;
    increment(a);

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

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

    1
    2
    3
    4
    public static void increment(Person p) { p.age++; }

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

    p将复制pers的引用值,并增加"年龄"字段,变量引用同一对象,因此年龄发生了变化。

    3)试图改变参考变量的参考值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static void swap(Person p1, Person p2) {
        Person temp = p1;
        p1 = p2;
        p2 = temp;
    }

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

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

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


    这么多冗长的答案。让我举一个简单的例子:

    • Java总是通过值传递所有的东西
    • 也就是说引用是按值传递的

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


    看看这个代码。此代码不会引发NullPointerException…它会印上"Vinay"

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Main {
        public static void main(String[] args) {
            String temp ="Vinay";
            print(temp);
            System.err.println(temp);
        }

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

    如果Java通过引用,那么它应该已经抛出EDCOX1 OR 2,因为引用被设置为NULL。


    Java严格按值传递

    当我说pass-by-value时,它意味着每当调用者调用被调用者时,参数(即:要传递给另一个函数的数据)都会被复制并放置在形式参数(被调用者接收输入的局部变量)中。Java只在一个通过值环境中实现从一个函数到其他函数的数据通信。

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

    因此,这里的形参只不过是该位置的别名。对该位置所做的任何修改在变量的作用域(标识该位置)处于活动状态时都是可见的。

    在爪哇中,没有指针的概念(即:没有所谓的指针变量),尽管我们可以把引用变量当作指针,从技术上讲,我们把它称为句柄。之所以调用Java中的句柄作为句柄,是因为指针变量不仅能够执行单个撤销,而且可以执行多次撤销。例如:p中的int *p;表示p指向整数。c中的int **p;表示p是指向整数的指针。我们在Java中没有这个设施,所以它绝对正确和技术上合理地说它是一个句柄,在C.中也有指针算法的规则,它允许对有约束的指针执行算术运算。

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

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

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

    因此Java严格使用逐值机制


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

    When an object reference is passed to a method, the reference itself
    is passed by use of call-by-value. However, since the value being
    passed refers to an object, the copy of that value will still refer to
    the same object referred to by its corresponding argument.

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


    检查语言是否支持按引用传递的简单测试只是编写一个传统的交换。你能用Java编写一个传统的交换(A,B)方法/函数吗?

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

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

    1
    2
    3
    4
    5
    swap(Type arg1, Type arg2) {
        Type temp = arg1;
        arg1 = arg2;
        arg2 = temp;
    }

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

    1
    2
    3
    Type var1 = ...;
    Type var2 = ...;
    swap(var1,var2);

    实际上,切换变量var1和var2的值,该语言支持按引用传递。但是Java不允许它支持只传递值而不是指针或引用。


    值调用一词意味着方法只获取调用方提供的值。相反,引用调用意味着该方法获取调用方提供的变量的位置(地址)。

    Java编程语言总是使用按值调用。这意味着该方法获取所有参数值的副本。特别是,尽管方法可以更改调用方变量引用的对象,但它不能修改变量本身。

    在下面的示例中,println(sb_actual)调用打印"foo",而不是"bar",因为addFoo()中对sb_formal的赋值不会更改调用方的sb_actual变量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void addFoo(StringBuffer sb_formal) {
        sb_formal.append("Foo");           // Modifies the object created by the caller.
        sb_formal = new StringBuffer();    // Creates a new object.
        sb_formal.append("Bar");           // Modifies the new object.
    }

    void caller() {
        StringBuffer sb_actual = new StringBuffer();
        addFoo(sb_actual);
        System.out.println(sb_actual);  // sb_actual still refers to the original object here.
    }

    Java通过引用引用值和类类型传递基元类型

    现在,人们喜欢喋喋不休地争论"通过引用"是否是描述Java等的正确方式。事实上是这样。重点是:

  • 传递对象不会复制该对象。
  • 传递给函数的对象可以由函数修改其成员。
  • 传递给函数的基元值不能由函数修改。复印件。
  • 在我的书中,这就是所谓的参照传递。

    &#brian bi-哪些编程语言是通过引用传递的?


    在一年attempt to add,这更是我的思想,包括《学习指南SCJP型的话题。这是从制造的指南,是通到太阳/ Oracle的行为测试的Java源代码,所以这是一个很好的使用为这所有的讨论。 >

    Passing Variables into Methods (Objective 7.3)

    7.3 Determine the effect upon object references and primitive values when they are passed into methods that perform assignments or other modifying operations on the parameters.

    Methods can be declared to take primitives and/or object references. You need to know how (or if) the caller's variable can be affected by the called method. The difference between object reference and primitive variables, when passed into methods, is huge and important. To understand this section, you'll need to be comfortable with the assignments section covered in the first part of this chapter.

    Passing Object Reference Variables

    When you pass an object variable into a method, you must keep in mind that you're passing the object reference, and not the actual object itself. Remember that a reference variable holds bits that represent (to the underlying VM) a way to get to a specific object in memory (on the heap). More importantly, you must remember that you aren't even passing the actual reference variable, but rather a copy of the reference variable. A copy of a variable means you get a copy of the bits in that variable, so when you pass a reference variable, you're passing a copy of the bits representing how to get to a specific object. In other words, both the caller and the called method will now have identical copies of the reference, and thus both will refer to the same exact (not a copy) object on the heap.

    For this example, we'll use the Dimension class from the java.awt package:

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

    When we run this class, we can see that the modify() method was indeed able to modify the original (and only) Dimension object created on line 4.

    1
    2
    3
    4
    5
    C:\Java Projects
    eference>java ReferenceTest
    Before modify() d.height = 10
    dim = 11
    After modify() d.height = 11

    Notice when the Dimension object on line 4 is passed to the modify() method, any changes to the object that occur inside the method are being made to the object whose reference was passed. In the preceding example, reference variables d and dim both point to the same object.

    Does Java Use Pass-By-Value Semantics?

    If Java passes objects by passing the reference variable instead, does that mean Java uses pass-by-reference for objects? Not exactly, although you'll often hear and read that it does. Java is actually pass-by-value for all variables running within a single VM. Pass-by-value means pass-by-variable-value. And that means, pass-by-copy-of- the-variable! (There's that word copy again!)

    It makes no difference if you're passing primitive or reference variables, you are always passing a copy of the bits in the variable. So for a primitive variable, you're passing a copy of the bits representing the value. For example, if you pass an int variable with the value of 3, you're passing a copy of the bits representing 3. The called method then gets its own copy of the value, to do with it what it likes.

    And if you're passing an object reference variable, you're passing a copy of the bits representing the reference to an object. The called method then gets its own copy of the reference variable, to do with it what it likes. But because two identical reference variables refer to the exact same object, if the called method modifies the object (by invoking setter methods, for example), the caller will see that the object the caller's original variable refers to has also been changed. In the next section, we'll look at how the picture changes when we're talking about primitives.

    The bottom line on pass-by-value: the called method can't change the caller's variable, although for object reference variables, the called method can change the object the variable referred to. What's the difference between changing the variable and changing the object? For object references, it means the called method can't reassign the caller's original reference variable and make it refer to a different object, or null. For example, in the following code fragment,

    1
    2
    3
    4
    5
    6
    7
    8
            void bar() {
               Foo f = new Foo();
               doStuff(f);
            }
            void doStuff(Foo g) {
               g.setName("Boo");
               g = new Foo();
            }

    reassigning g does not reassign f! At the end of the bar() method, two Foo objects have been created, one referenced by the local variable f and one referenced by
    the local (argument) variable g. Because the doStuff() method has a copy of the reference variable, it has a way to get to the original Foo object, for instance to call the setName() method. But, the doStuff() method does not have a way to get to the f reference variable. So doStuff() can change values within the object f refers to, but doStuff() can't change the actual contents (bit pattern) of f. In other words, doStuff() can change the state of the object that f refers to, but it can't make f refer to a different object!

    Passing Primitive Variables

    Let's look at what happens when a primitive variable is passed to a method:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class ReferenceTest {
        public static void main (String [] args) {
          int a = 1;
          ReferenceTest rt = new ReferenceTest();
          System.out.println("Before modify() a =" + a);
          rt.modify(a);
          System.out.println("After modify() a =" + a);
        }
        void modify(int number) {
          number = number + 1;
          System.out.println("number =" + number);
        }
    }

    In this simple program, the variable a is passed to a method called modify(),
    which increments the variable by 1. The resulting output looks like this:

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

    Notice that a did not change after it was passed to the method. Remember, it was a copy of a that was passed to the method. When a primitive variable is passed to a method, it is passed by value, which means pass-by-copy-of-the-bits-in-the-variable.


    这有点难以理解,但Java总是复制值-点是,通常值是一个引用。所以你最终得到的是同一个物体而没有考虑它…


    第1部分:不动产清单

    有一个120平方英尺的蓝色"小房子",目前停在1234主街,前面有一个精心修剪的草坪和花坛。好的。

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

    我们叫那个房地产经纪人"鲍勃"。你好,鲍勃。好的。

    Bob用一个网络摄像头来实时记录实际房屋的任何变化,从而使他的清单保持最新,他称之为tinyHouseAt1234Main。他还记录了有多少人询问过这个清单。鲍勃为房子设计的整数viewTally今天是42。好的。

    每当有人想知道1234主街蓝色小房子的情况,他们就会问鲍勃。好的。

    Bob查了一下清单tinyHouseAt1234Main,把它的颜色、漂亮的草坪、阁楼床和堆肥厕所等都告诉了他们,然后他把他们的询问加到了viewTally上。但他并没有告诉他们真实的实际地址,因为鲍勃的公司专门经营可以随时搬迁的小房子。现在的数字是43。好的。

    在另一家公司,房地产经纪人可能会明确地说,他们的挂牌"指向"了位于主大街1234号的房子,这意味着旁边有一点*,因为他们主要处理很少搬家的房子(尽管可能有理由这样做)。鲍勃的公司不费心这么做。好的。

    现在,当然,鲍勃不会亲自去把房子放在卡车上直接展示给客户——这是不切实际的,也是一种荒谬的资源浪费。传递一份完整的计数表是一回事,但总是在整个房子里传递是昂贵和荒谬的。好的。

    (旁白:鲍勃的公司也不会每次有人问起,3D打印出一所上市公司的新的和独特的副本。这就是新贵,同样被称为基于网络的公司及其子公司所做的事情——这是昂贵和缓慢的,人们经常混淆这两个公司,但不管怎样,他们还是很受欢迎)。好的。

    在其他一些更靠近大海的老公司,像鲍勃这样的房地产经纪人甚至可能不存在来管理上市。客户可以咨询Rolodex"Annie"(简称EDOCX1(简称5))了解房子的直接地址。客户没有像Bob那样从清单中读取参考的房屋详细信息,而是从Annie(&处获得房屋地址,直接前往1234 Main St,有时不知道他们可能在那里找到什么。好的。

    一天,鲍勃的公司开始提供一项新的自动化服务,这项服务需要客户感兴趣的房子上市。好的。

    好吧,有这个信息的人是Bob,所以客户让Bob调用服务并将列表的副本发送给它。好的。

    jobKillingAutomatedListingService(Listing tinyHouseAt1234Main, int viewTally)Bob把…好的。

    服务在其末尾称这个清单为houseToLookAt,但实际上它收到的是Bob清单的精确副本,其中的值完全相同,指的是位于1234大街的房子。好的。

    这个新的服务也有它自己的内部记录,记录有多少人看过这个列表。该服务接受Bob出于专业礼貌而进行的计数,但它并不真正关心,而且还是用它自己的本地副本覆盖它。今天的记录是1,而鲍勃的记录是43。好的。

    房地产公司称之为"传递价值",因为Bob传递了他的viewTally和他的上市tinyHouseAt1234Main的当前价值。他实际上并没有穿过整个实体房子,因为那是不切实际的。他也不会像安妮(&)那样传递真实的物理地址。好的。

    但他正在传递一份他对房子的参考价值。在某些方面似乎是一种愚蠢的学究式的区别,但他的公司就是这样运作的…………好的。第二部分:事情变得混乱和危险…

    新的自动化服务并不像其他一些流行的金融和科学公司那样功能性和面向数学的服务,可能会产生不可预见的副作用…好的。

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

    该服务还允许客户将houseToLookAt完全重定向到另一个地址的其他房子,而不涉及Bob或他的列表。突然间,他们看到的可能是4321榆树街,这与鲍勃的上市没有任何关系(谢天谢地,他们不能再做损害)。好的。

    鲍勃在他的实时网络摄像头上看这些。屈从于他唯一的工作职责,他告诉客户新的难看的油漆工作和突然缺乏抑制吸引力。毕竟,他的名单仍在主街1234号。新服务的houseToLookAt无法改变这一点。鲍勃像往常一样准确、尽职地报告了他的埃多克斯的详细情况,直到他被解雇或房子被一无所有所毁。好的。

    实际上,该服务唯一不能做的事情就是将Bob原始列表的houseToLookAt副本地址从1234主街更改为其他地址,或更改为虚无,或更改为某种随机类型的对象,如鸭嘴兽。Bob的列表仍然指向1234主街,不管它值多少钱。他像往常一样传递它的当前值。好的。

    将清单传递给新的自动化服务的这种奇怪的副作用,对于询问它如何工作的人来说是令人困惑的。真的,遥控机器人改变1234主屋的状态与实际去那里并因为安妮给你地址而造成破坏的能力有什么区别??好的。

    如果您通常关心的是列表中的房子的状态被复制和传递,那么这似乎是一种吹毛求疵的语义争论,对吧?好的。

    我的意思是,如果你真的在收拾房子并把它们搬到其他地址(而不是像移动或小房子那样的平台的预期功能),或者你在访问、重命名和洗牌整个社区,就像某种低级的上帝在玩疯子,那么也许你更关心abo。但传递这些特定的地址引用,而不仅仅是最新价值的房子细节的副本…好的。好啊。


    有一个非常简单的方法来理解这一点。让我们用C++来传递引用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    #include <iostream>
    using namespace std;

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

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

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

       return 0;
    }

    结果如何?

    1
    2
    3
    4
    1
    1
    99
    99

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

    现在,让我们看看java说了什么。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public class Ref {

        private static class Foo {
            private int x;

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

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

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

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

    }

    它说:

    1
    2
    3
    4
    1
    1
    99
    1

    喂,传给BAR的foo的主引用仍然保持不变!

    这个例子清楚地表明,当我们说"通过引用"时,Java和C++是不一样的。从本质上讲,Java将"引用"作为"值"传递给函数,意味着Java是通过值传递的。


    一切都是按值传递的。基本体和对象引用。但是,如果对象的接口允许,则可以对其进行更改。

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

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

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

    1
    2
    3
    4
    5
    6
    7
    void renameToJon(Person p) {
        p = new Person("Jon"); // this will not work
    }

    jack = new Person("Jack");
    renameToJon(jack);
    sysout(jack); // jack is unchanged

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

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

    总而言之,一切都是按价值传递的。


    简单程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    import java.io.*;
    class Aclass
    {
        public int a;
    }
    public class test
    {
        public static void foo_obj(Aclass obj)
        {
            obj.a=5;
        }
        public static void foo_int(int a)
        {
            a=3;
        }
        public static void main(String args[])
        {
            //test passing an object
            Aclass ob = new Aclass();
            ob.a=0;
            foo_obj(ob);
            System.out.println(ob.a);//prints 5

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

    从C/C++程序员的角度来看,Java使用传递值,因此对于原始数据类型(int、char等),函数中的更改不会反映在调用函数中。但是当你传递一个对象时,在函数中你改变了它的数据成员或者调用了能改变对象状态的成员函数,调用函数将得到这些改变。


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

    Diagram of how data is created and passed

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

    这意味着:

    • 您可以在函数内部更改myObject的值。
    • 但是在函数内部不能更改myObject引用的内容,因为point不是myObject
    • 记住,pointmyObject都是引用,不同的引用指向同一个new Point(0,0)

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

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

    • Java使用传递值
      • 传递基元数据时,复制基元数据类型的值。
      • 传递对象时,它复制对象的地址并传递给被调用方方法变量。

    使用基元数据类型的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class PassByValuePrimitive {
        public static void main(String[] args) {
            int i=5;
            System.out.println(i);  //prints 5
            change(i);
            System.out.println(i);  //prints 5
        }


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

        }
    }

    使用对象的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class PassByValueObject {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("prem");
            list.add("raj");
            new PassByValueObject().change(list);
            System.out.println(list); // prints [prem, raj, ram]

        }


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


    与其他语言不同,Java不允许您选择传递值或通过引用。

    所有参数都是按值传递的。

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

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

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

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

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


    Java是按值传递的。

    这个问题已经有了很好的答案。不知怎么地,我从来就不清楚传递值/引用是关于原始数据类型还是关于对象的。因此,为了让我满意和清晰,我用下面的代码进行了测试;这可能有助于某些人寻求类似的清晰性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    class Test    {

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

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

    }

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

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

    }

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

    如果行b = new DummyObject()未注释,则随后所做的修改将在新对象(新的实例化)上进行。因此,它不会反映在调用方法的位置。但是,否则,更改会反映为修改仅在对象的"引用"上进行,即-b指向同一个DummyObject。

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


    最短答案:)

    • Java具有传递值(并通过值传递引用)。
    • C也有旁路参考

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

    传递引用:以这样的方式传递变量:方法内部的重新分配即使在方法外部也会反映出来。

    下面是一个按引用传递的示例(c)。这个特性在Java中不存在。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Example
    {
        static void InitArray(out int[] arr)
        {
            arr = new int[5] { 1, 2, 3, 4, 5 };
        }

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

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

    另请参见:msdn-library(c):按引用和out传递数组

    另请参见:msdn-library(c):通过值和引用传递


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

    对于原语,我们将把原语的实际值传递给方法(或构造函数),不管它是整数"5"、字符"c"还是您所拥有的。然后,这个实际值就变成了它自己的本地原语。但是对于对象,我们所做的就是给同一个对象一个附加的引用(本地引用),这样我们现在就有两个引用指向同一个对象。

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


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

    兽医不喜欢吉米的长尾巴,所以他想把它切成两半。但是作为一个好兽医,他知道他没有权利残害别人的狗。因此,他首先克隆了狗(用新的关键字),并割断了克隆人的尾巴。当狗最终回到你身边时,它机智地拥有原来12英寸的尾巴。美满结局!

    下次旅行时,你会不知不觉地把狗带到一个邪恶的兽医那里。他也是一个讨厌长尾巴的人,所以他把它减到可怜的2英寸。但他这样做是为了你亲爱的吉米,而不是克隆人。当你回来的时候,看到吉米可怜地摇着一根2英寸长的树桩,你很震惊。

    这个故事的寓意是:当你把宠物传给别人时,你会全心全意地放弃。把宠物交给兽医保管。他可以随意摆弄它。通过值、引用和指针传递都只是技术上的争论。除非兽医先克隆它,否则他最终会把原来的狗弄残。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    public class Doggie {

        public static void main(String...args) {
            System.out.println("At the owner's home:");
            Dog d = new Dog(12);
            d.wag();
            goodVet(d);
            System.out.println("With the owner again:)");
            d.wag();
            badVet(d);
            System.out.println("With the owner again(:");
            d.wag();
        }

        public static void goodVet (Dog dog) {
            System.out.println("At the good vet:");
            dog.wag();
            dog = new Dog(12); // create a clone
            dog.cutTail(6);    // cut the clone's tail
            dog.wag();
        }

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

    class Dog {

        int tailLength;

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

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

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

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public static void dummyIncrease(int[] x, int y)
    {
        x[0]++;
        y++;
    }
    public static void main(String[] args)
    {
        int[] arr = {3, 4, 5};
        int b = 1;
        dummyIncrease(arr, b);
        // arr[0] is 4, but b is still 1
    }

    main()
      arr +---+       +---+---+---+
          | # | ----> | 3 | 4 | 5 |
          +---+       +---+---+---+
      b   +---+             ^
          | 1 |             |
          +---+             |
                            |
    dummyIncrease()         |
      x   +---+             |
          | # | ------------+
          +---+      
      y   +---+
          | 1 |
          +---+

    已经有了很好的答案。我想通过分享一个非常简单的例子来编译一个小的例子(它将编译)对比C++中的逐个引用和Java中的逐值行为。

    几点:

  • 术语引用是重载的。在Java中,它只是指一个指针。在术语pass by reference中,它是指对传入的原始变量的引用。
  • Java是通过值传递的,但是允许我们通过传递一个Java引用(即指针)作为值来引用PASS。这意味着它传递了一个Java引用的副本。
  • C++通过使用"&"字符来声明引用参数(它恰好是用于指示C和C++中的变量的地址)的相同字符,从而允许传递引用。例如,如果我们传入一个指针,参数和参数不仅仅指向同一个对象,而是指向同一个变量。如果一个被设置为不同的地址或为空,另一个也被设置为空。
  • 在下面的C++示例中,我通过引用传递一个指向空终止字符串的指针。在下面的Java示例中,我通过值将Java引用传递给字符串(同样,与字符串的指针相同)。注意注释中的输出。
  • C++参考例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    using namespace std;
    #include <iostream>

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

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

    JAVA PASS"Java引用"的价值示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class ValueDemo{

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

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

    斯科特·斯坦奇菲尔德先生写了一个很好的答案。这是您要验证的类正是他的意思:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    public class Dog {

        String dog ;
        static int x_static;
        int y_not_static;

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

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

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

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

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

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


    围绕这个问题的许多困惑来自Java试图重新定义"按值传递"和"通过引用"这一事实。重要的是要理解这些是行业术语,不能在该上下文之外正确理解。它们是用来帮助您编写代码的,理解起来很有价值,所以让我们先看看它们的含义。

    在这里可以找到对这两者的很好描述。

    传递值函数接收到的值是调用方正在使用的对象的副本。它对于函数是完全唯一的,您对该对象所做的任何操作都只能在函数中看到。

    pass by reference函数接收到的值是对调用方使用的对象的引用。函数对该值引用的对象所做的任何操作都将被调用方看到,并且它将从该点开始处理这些更改。

    从这些定义中可以清楚地看到,引用是通过值传递的这一事实是不相关的。如果我们接受这个定义,那么这些术语就变得毫无意义了,所有的语言都只是传递值而已。

    无论您如何传递引用,它都只能通过值传递。这不是重点。重点是您将对自己的对象的引用传递给了函数,而不是它的副本。你可以扔掉你收到的推荐信这一事实无关紧要。同样,如果我们接受了这个定义,这些术语就变得毫无意义了,而且每个人都在传递价值。

    而且,C++的特殊"按引用"语法不是传递引用的唯一定义。它纯粹是一种方便的语法,用于在传入指针后不需要使用指针语法。它仍在传递一个指针,编译器只是在向您隐藏这个事实。它仍然按值传递指针,编译器只是对您隐瞒了这一点。

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

    不能修改调用方的引用这一事实与传递引用无关,并且在支持传递引用的每种语言中都是正确的。


    分两步理解:

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

    如果要更改引用后面的值,则只能在堆栈上声明一个同名"d"的新变量。查看带有标志@的引用,您会发现引用已更改。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static void foo(Dog d) {
      d.Name ="belly";
      System.out.println(d); //Reference: Dog@1540e19d

      d = new Dog("wuffwuff");
      System.out.println(d); //Dog@677327b6
    }
    public static void main(String[] args) throws Exception{
      Dog lisa = new Dog("Lisa");
      foo(lisa);
      System.out.println(lisa.Name); //belly
    }


    Java是一直通备用副本的参数值,通过银行有什么对象定义的变量,都有使用和参考参考,是可变的,记忆的存储器地址的对象是在记忆的地方。 >

    检查评论对理解什么会发生在我跟他们的执行;数字节目流的执行。 >

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    class Example
    {
        public static void test (Cat ref)
        {
            // 3 - <ref> is a copy of the reference
            // both currently reference Grumpy
            System.out.println(ref.getName());

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

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

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

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

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

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

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

    以badswap()方法为例:

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

    当badswap()返回时,作为参数传递的变量仍将保留其原始值。如果我们将参数类型从.nbsp和nbsp转换为nbsp对象,方法也会失败,因为Java也通过值传递对象引用。现在,这是一个棘手的问题:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public void tricky(Point arg1, Point   arg2)
    { arg1.x = 100; arg1.y = 100; Point temp = arg1; arg1 = arg2; arg2 = temp; }
    public static void main(String [] args) {

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

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

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

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

    如果我们执行此main()方法,将看到以下输出:

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

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

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


    Java通过值传递所有东西!!

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

    1
    2
    3
    PersonClass variable1 = new PersonClass("Mary", 32);

    PersonClass variable2;

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

    1
    2
    3
    4
    variable2 = variable1;


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

    //variable1现在指向variable3

    1
    variable1 = variable3;

    //这个函数的输出是什么?

    1
    2
    3
    4
    5
    System.out.println(variable2);
    System.out.println(variable1);

    Mary 32
    Andre 45

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

    网页


    在Java中,一切都是按价值调用的,正如我试图通过下面的程序理解的那样

    类S

    1
    2
    3
    4
    5
    class S{
    String name="alam";
    public void setName(String n){
    this.name=n;
    }}

    类样本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
        public class Sample{
        public static void main(String args[]){
        S s=new S();
        S t=new S();
        System.out.println(s.name);
        System.out.println(t.name);
        t.setName("taleev");
        System.out.println(t.name);
        System.out.println(s.name);
        s.setName("Harry");
        System.out.println(t.name);
        System.out.println(s.name);
        }}

    产量

    alam

    alam

    taleev

    alam

    taleev

    harry

    正如我们已经用实例变量名和值taleev定义了类S一样,我们从它初始化的所有对象都将具有值为taleev的名称变量,但是如果我们更改任何对象的名称值,那么它只更改该类(对象)副本的名称,而不是每个类的名称,因此之后,当我们执行system.out.println(s.name)时,它只打印taleev,我们不能更改名称的值t我们最初定义的值是对象的值,而不是实例变量的值,因此一旦定义了实例变量,我们就无法更改它。

    所以我认为这就是Java只处理参考值而不是引用的方式。

    基本变量的内存分配可以理解为


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package com.asok.cop.example.task;
    public class Example {
        int data = 50;

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

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

    输出:

    1
    2
    3
    before change 50
    after add 600
    after change 50

    在Java中有一个用于引用的解决方案。让我用这个例子来解释一下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class Yo {
    public static void foo(int x){
        System.out.println(x); //out 2
        x = x+2;
        System.out.println(x); // out 4
    }
    public static void foo(int[] x){
        System.out.println(x[0]); //1
        x[0] = x[0]+2;
        System.out.println(x[0]); //3
    }
    public static void main(String[] args) {
        int t = 2;
        foo(t);
        System.out.println(t); // out 2 (t did not change in foo)

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

    希望这有帮助!


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

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

    因为dostuff()方法有一个引用变量的副本,所以它有一种方法到原始foo对象,例如调用setname()方法。但是,dostuff()方法无法F引用变量。所以dostuff()可以更改对象f引用的值to,但dostuff()不能更改其他函数中f.的实际内容(位模式)words,dostuff()可以更改f引用的对象的状态,但它不能使F引用另一个对象!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    package test.abc;

    public class TestObject {

        /**
         * @param args
         */

        public static void main(String[] args) {
            bar();
        }

        static void bar() {
            Foo f = new Foo();
            System.out.println("Object reference for f:" + f);
            f.setName("James");
            doStuff(f);
            System.out.println(f.getName());
            //Can change the state of an object variable in f, but can't change the object reference for f.
            //You still have 2 foo objects.
            System.out.println("Object reference for f:" + f);
            }

        static void doStuff(Foo g) {
                g.setName("Boo");
                g = new Foo();
                System.out.println("Object reference for g:" + g);
            }
    }


    package test.abc;

    public class Foo {
        public String name ="";

        public String getName() {
            return name;
        }

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

    }

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

    控制台输出:

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

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

    喝倒采f:test.abc.foo@62f72617的对象引用


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


    根据C++术语:

  • 基本类型及其包装器-传递值
  • 其他复杂数据类型-按引用传递
  • (虽然Java完全通过值,但在第二种情况下,它将引用传递给对象,在这种情况下,如果改变了对象的值,则反映在main函数中,因此我称之为C++引用的传递引用。)如果你从C++中取笑,那么Java对于原始类型和它们的包装类(如int、整数、布尔、布尔)都是按值传递的,也就是说,如果传递这些数据类型的值,原始函数就不会有变化。对于所有其他类型的数据类型,Java只是通过它们,如果进行了任何更改,则可以在原始函数中看到更改(可以根据C++术语称为"按引用传递")。


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

    首先,我们应该理解什么是旁路值或旁路参考。

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

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

    假设我们有一个像下面这样的class Balloon

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class Balloon {
    private String color;
    public Balloon(){}
    public Balloon(String c){
        this.color=c;
    }
    public String getColor() {
        return color;
    }

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

    我们有一个简单的程序,用一个通用方法来处理swap两个objectsclass如下所示。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public class Test {
    public static void main(String[] args) {

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

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

        foo(blue);
        System.out.println("blue color="+blue.getColor());
    }
    private static void foo(Balloon balloon) {
            balloon.setColor("Red");
            balloon = new Balloon("Green");
            balloon.setColor("Blue");
        }

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

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

    1
    2
    3
    red color=Red
    blue color=Blue
    blue color=Red

    如果你看一下output的前两行,很明显swap方法不起作用。这是因为Javapass by value,这个swap()方法测试可以与任何编程语言一起使用,以检查它是pass by value还是pass by reference


    初学者的简单答案

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

    请注意,不"复杂"(不象我们的狗)的对象(如整数或字符串)是通过值传递的。因此,即使它们是对象,并且您可以对它们做任何操作,它们也不能在方法内修改…是的,这是****的,没有任何意义…

    示例(不是狗):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    public class HelloWorld {
        private Integer i;
        private String s;
        private Boolean b;

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

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

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

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

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

    如果运行该命令,则如果将整个对象用作参数,则对象h将被修改;如果仅将字段s i或b用作参数,则不会修改字段s i或b。如果进行调试,您会注意到对于字段,只要运行值更改处的行,对象的ID就会更改。因此,它将自动执行与"new integer"、"new string"和"new boolean"相同的操作。


    Java编程语言仅通过值传递参数,也就是说,不能从内部更改调用方法中的参数值调用的方法。

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

    对许多人来说,这看起来像是一个参照物,从行为上来说,它有与传递引用有很多相同之处。然而,有两个原因这是不准确的。

    • 首先,改变事物的能力方法只适用于对象,而不适用于基元值。

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

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

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

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

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


    tl;dr:最小示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package foobar;
    import java.util.ArrayList;

    public class FooBar {

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

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

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

        sideEffects(list1, list2, x);

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

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

    很简单!如果变量是基元,则按值传递;如果变量是对象,则按引用传递。

    这就是全部


    推荐阅读