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

在...创建的问题 Wed, Jul 11, 2018 12:00 AM

我一直以为Java 传递参考

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

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

解释是什么?

    
6009
  1. 我认为这个问题的大部分混淆都与不同的人对“参考”一词有不同定义的事实有关。来自C ++背景的人认为“引用”必须意味着它在C ++中的含义,来自C背景的人认为“引用”必须与其语言中的“指针”相同,依此类推。说Java通过引用传递是否正确取决于“引用”是什么意思。
    2011-07-30 07:23:05Z
  2. 我尝试一致地使用在评估策略文章。应该指出的是,尽管文章指出这些术语因社区而异,但它强调call-by-valuecall-by-reference的语义在一个非常关键的方式上有所不同。 (就我个人而言,我更喜欢在 call-by-object-sharing 上使用 call-by-value[-of-the-reference] ,因为它描述了高级别的语义并且不会与 call-by-value 产生冲突>,底层实现。)
    2011-12-15 06:12:03Z
  3. @ Gravity:你能把你的评论放在巨大的广告牌上吗?简而言之,这就是整个问题。它表明这一切都是语义学。如果我们不同意参考的基本定义,那么我们将不同意这个问题的答案:)
    2013-11-12 20:58:58Z
  4. 我认为混淆是“通过引用传递”与“引用语义”。 Java是带有引用语义的pass-by-value。
    2014-03-27 13:54:30Z
  5. @ Gravity,而你绝对正确,因为来自C ++的人会本能地对术语“参考”有不同的直觉集,我个人认为身体更多地埋藏在“通过”中。 “传递”令人困惑,因为它与Java中的“传递”完全不同。但是在C ++中,通俗地说不是。在C ++中,您可以说“传递引用”,并且理解它将通过swap(x,y)测试
    2015-03-23 13:44:28Z
  6. 醇>
    30答案                              30 跨度>                         

    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"aDog中的值mainfoo 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(...)foo上执行的任何操作都是这样的,出于所有实际目的,它们在d上执行,但是可以更改变量aDog本身的值。     
    5353
    2019-01-14 23:10:29Z
    1. 这个问题与内部细节有点混淆吗?没有概念“传递引用”和“传递引用值”之间的区别,假设您的意思是“指向对象的内部指针的值”。
      2008-09-08 14:58:53Z
    2. 但是有一个细微的差别。看看第一个例子。如果纯粹通过引用传递,aDog.name将是“Fifi”。它不是 - 你得到的引用是一个值引用,如果在退出函数时将被恢复,将被恢复。
      2008-09-11 06:55:48Z
    3. @ Lorenzo:不,在Java中,一切都是按值传递的。基元按值传递,对象引用按值传递。对象本身永远不会传递给方法,但是对象总是在堆中,只有对象的引用传递给方法。
      2009-02-12 23:02:20Z
    4. 我尝试以可视化对象传递的方式:想象一个气球。调用fxn就像将第二个字符串绑定到气球并将该行传递给fxn。 parameter = new Balloon()将剪切该字符串并创建一个新的气球(但这对原始气球没有影响)。 parameter.pop()仍然会弹出它,因为它跟随字符串到相同的原始气球。 Java是按值传递的,但传递的值不是很深,而是处于最高级别,即基元或指针。不要将其与完全克隆并传递对象的深度传递混淆。
      2010-10-20 16:38:55Z
    5. 令人困惑的是对象引用实际上是指针。在开始时,SUN称它们为指针。然后营销告知“指针”是一个坏词。但是你仍然可以在NullPointerException中看到“正确的”命名法。
      2011-02-09 09:46:44Z
    6. 醇>

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

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

    理解这一点的关键是像

    这样的东西  aDog

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

    这意味着,当你有

    时  
    Dog myDog;
    

    您实际上是将创建的

    Dog myDog = new Dog("Rover");
    foo(myDog);
    
    对象的地址传递给Dog方法。

    (我说的主要是因为Java指针不是直接地址,但最容易以这种方式来考虑它们)

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

    如果方法被定义为

     Dog

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

    • 参数
      public void foo(Dog someDog) {
          someDog.setName("Max");     // AAA
          someDog = new Dog("Fifi");  // BBB
          someDog.setName("Rowlf");   // CCC
      }
      
      设置为值42
    • 在“AAA”行
      •  someDog跟随someDog指向(地址42处的Dog对象)
      • 要求Dog(地址为42的那个)将他的名字改为Max
    • 在“BBB”行
      • 创建了一个新的Dog。让我们说他在地址74
      • 我们将参数Dog分配给74
    • 在“CCC”行
      • someDog跟随它指向的someDog(地址74处的Dog对象)
      • 要求Dog(地址为74的那个)将他的名字改为Rowlf
    • 然后,我们返回

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

    Dog改变了吗?

    有钥匙。

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

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

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

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

    如果Java已经过了-reference语义,我们上面定义的myDog方法在foo指向myDog BBB时指向someDog时会发生变化。

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

        
    2858
    2015-08-28 15:32:38Z
    1. 这就是为什么常见的副词“Java没有指针”是如此误导。
      2009-04-02 17:52:18Z
    2. 你错了,imho。 “记住myDog是一个指针,而不是一个真正的狗,答案是否定的。我的狗仍然有42值;它仍然指向原来的狗。” myDog的值为42,但其名称参数现在包含“Max”,而不是//@ AAA线上的“Rover”。
      2009-04-06 05:22:09Z
    3. 以这种方式思考。有人在一张名为“annArborLocation”的纸条上写着密歇根州安娜堡(我的家乡,GO BLUE!)的地址。你把它复制在一张名为“myDestination”的纸上。你可以开车去“myDestination”并种一棵树。您可能在该位置更改了有关城市的信息,但它并未更改任何纸张上写入的LAT /LON。您可以在“myDestination”上更改LAT /LON,但不会更改“annArborLocation”。这有帮助吗?
      2009-04-23 13:06:40Z
    4. @ Scott Stanchfield:我读了大约一年前的文章,这对我来说真的很有帮助。谢谢!我可以谦卑地建议一点补充:你应该提一下,实际上有一个特定的术语描述了这种形式的“价值呼唤,其中价值是一个参考”,这是Barbara Liskov为描述她的CLU语言的评估策略所发明的。 1974年,为了避免混淆,例如你的文章所说的那个:通过共享调用(有时称为通过对象共享调用或简单地按对象调用),几乎完美地描述了语义。
      2010-09-07 22:12:35Z
    5. @ Gevorg - C /C ++语言不拥有“指针”的概念。还有其他语言使用指针但不允许C /C ++允许的相同类型的指针操作。 Java有指针;他们只是为了防止恶作剧。
      2011-12-30 18:30:02Z
    6. 醇>

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


    让我通过示例解释一下:

     
    public class Main{
         public static void main(String[] args){
              Foo f = new Foo("f");
              changeReference(f); // It won't change the reference!
              modifyReference(f); // It will modify the object that the reference variable "f" refers to!
         }
         public static void changeReference(Foo a){
              Foo b = new Foo("b");
              a = b;
         }
         public static void modifyReference(Foo c){
              c.setAttribute("c");
         }
    }
    

    我将分步说明:

    1. 声明名为f的类型为Foo的引用,并为其指定一个类型为Foo的新对象,其属性为"f"

       
      Foo f = new Foo("f");
      

    2. 从方法方面,声明了名称为Foo的类型为a的引用,并且它最初被指定为null

       
      public static void changeReference(Foo a)
      

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

       
      changeReference(f);
      

    4. 声明名为b的类型为Foo的引用,并为其指定一个类型为Foo的新对象,其属性为"b"

       
      Foo b = new Foo("b");
      

    5. a = b对其属性为a的对象f "b")进行了新的分配。


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

    7. c.setAttribute("c");将更改重新对象的属性ference c指向它,它与引用f指向同一个对象。

    8. 醇>

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

          
    1583
    2019-01-14 23:31:28Z
    1. 2013-06-07 08:46:12Z
    2. @ Eng.Fouad这是一个很好的解释,但是如果a指向与f相同的对象(并且永远不会得到它自己的对象f指向的副本),那么任何变化使用a创建的对象应该修改f(因为它们都使用相同的对象),所以在某些时候a必须得到自己的副本对象f指向。
      2013-07-15 19:08:28Z
    3. @ MrD当'a'指向同一个对象'f'也指向,然后通过'a'对该对象所做的任何更改也可通过'f'观察到',但它不会改变'f'。 'f'仍然指向同一个对象。你可以完全改变对象,但你永远不能改变'f'指向的对象。这是一些基本问题,由于某些原因,有些人无法理解。
      2013-12-01 13:02:34Z
    4. 这是我发现的最佳解释。顺便问一下,基本情况怎么样?例如,参数需要一个int类型,它是否仍然将int变量的副本传递给参数?
      2016-05-21 12:38:43Z
    5. 很好地解释了。现在我可以说是的。参数名称用于指向不同的位置。所以它的价值😉
      2019-02-03 14:41:29Z
    6. 醇>

    这将为您提供一些有关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。

    一张图片胜过千言万语:

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

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

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

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

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

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


      

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


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

        
    697
    2018-01-04 10:17:22Z
    1. 你的意思是指针吗?..如果我说得对,在public void foo(Car car){ ... }中,carfoo的本地,它包含对象的堆位置?因此,如果我将car的值更改为car = new Car(),它将指向堆上的不同对象?如果我将car的属性值更改为car.Color = "Red",则会修改car指向的堆中的Object。另外,它在C#中是一样的吗?请回复!谢谢!
      2012-02-25 03:29:07Z
    2. @ domanokz你杀了我,请不要再说这个词了! ;)请注意,我可以回答这个问题而不说“参考”。这是一个术语问题,而且会使情况变得更糟。不幸的是,我和苏格兰人对此有不同的看法。我想你已经掌握了它在Java中是如何工作的,现在你可以称之为按值传递,按对象共享,按副本复制变量值,或者随意提出其他内容!只要你知道它的工作方式以及Object类型的变量中的内容,我就不在乎了!只是一个邮政信箱地址! ;)
      2012-02-25 21:25:22Z
    3. 听起来你只是 传递了一个参考 ?我将支持Java仍然是一种复制的传递引用语言这一事实​​。它是复制引用的事实不会改变术语。两个REFERENCES仍指向同一个对象。这是纯粹主义者的论点......
      2014-03-12 17:38:45Z
    4. 我想Java的设计考虑了指针。否则,为什么NullPointerException存在?然而,对于这个精彩的解释,获取指针只会让它变得复杂
      2014-08-20 17:42:17Z
    5. 那么请参阅理论上的#9 System.out.println(person.getName());将会出现什么? “汤姆”还是“杰瑞”?这是帮助我克服这种困惑的最后一件事。
      2014-10-19 12:20:23Z
    6. 醇>

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

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

    所以,在调用方法时

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

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

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

        
    625
    2016-01-22 03:37:07Z
    1. 由于基元的值是不可变的(如String),两种情况之间的差异并不真正相关。
      2011-02-03 01:23:04Z
    2. 完全正确。对于所有可以通过可观察的JVM行为来判断,原语可以通过引用传递,并且可以存在于堆上。他们没有,但实际上并没有以任何方式观察到。
      2011-07-31 04:50:55Z
    3. 原语是不可变的?是Java 7中的新东西吗?
      2011-08-22 11:26:09Z
    4. @ CarlosHeuberger,“原语是不可变的?”。关键是你可以假装'原语'实际上(可变)引用到不可变对象。
      2015-07-30 16:29:07Z
    5. 指针是不可变的,原语通常是可变的。 String也不是原始的,它是一个对象。而且,String的底层结构是一个可变数组。关于它的唯一不可变的事情是长度,这是数组的固有特性。
      2015-08-12 18:44:10Z
    6. 醇>

    Java按值传递引用。

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

        
    302
    2008-09-02 20:20:08Z
    1. 但是不断重复的事情“你不能改变在参数中传递的对象的值”显然是错误的。您可能无法使它们引用不同的对象,但您可以通过调用其方法来更改其内容。 IMO这意味着您将失去参考的所有好处,并且不会获得额外的保证。
      2011-07-24 19:13:09Z
    2. 我从未说过“你不能改变在参数中传递的对象的值”。我会说“你不能改变作为方法参数传入的对象引用的值”,这是关于Java语言的真实陈述。显然你可以改变对象的状态(只要它不是不可变的)。
      2011-08-01 14:19:38Z
    3. 请记住,您实际上无法在java中传递对象;对象留在堆上。可以传递对象的指针(将其复制到被调用方法的堆栈帧中)。因此,您永远不会更改传入的值(指针),但您可以自由地关注它并更改它所指向的堆上的内容。这是传值。
      2012-01-26 22:17:56Z
    4. 听起来你只是 传递了一个参考 ?我将支持Java仍然是一种复制的传递引用语言这一事实​​。它是复制引用的事实不会改变术语。两个REFERENCES仍指向同一个对象。这是纯粹主义者的论点......
      2014-03-12 17:38:26Z
    5. Java不传递对象,它将指针的值传递给对象。这将在新变量中创建一个指向原始对象的内存位置的新指针。如果在方法中更改此指针变量的值(它指向的内存地址),则方法调用方中使用的原始指针保持不变。如果你正在调用参数一个引用,那么事实上它是原始引用的副本,而不是原始引用本身,这样现在有两个对象的引用意味着它是pass-by值
      2014-11-19 16:38:17Z
    6. 醇>

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

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

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

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

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

    结果如下:

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

    像这样:

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

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

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

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

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

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

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

        
    218
    2018-01-04 16:18:47Z
    1. 但是,你应该跟进一个更复杂的例子,其中一个函数appears将变量改为其引用的地址。
      2013-11-01 06:53:33Z
    2. 人们不是“围绕堆栈与堆的真正问题跳舞”,因为那是不是真正的问题。这是一个最好的实现细节,最糟糕的是彻头彻尾的错误。 (对象很可能存在于堆栈中;谷歌“逃避分析”。并且大量对象包含可能生活在堆栈上的原语。)真正的问题是确切地引用类型和值类型之间的区别 - 特别是,引用类型变量的值是引用,而不是它引用的对象。
      2013-12-18 04:48:00Z
    3. 这是一个“实现细节”,因为Java永远不需要实际显示对象在内存中的位置,事实上似乎决定避免我>泄露了这些信息。它可以把对象放在堆栈上,你永远不会知道。如果你关心,你就会关注错误的事情 - 在这种情况下,这意味着忽略了真正的问题。
      2013-12-18 14:38:19Z
    4. 无论哪种方式,“原语进入堆栈”都是不正确的。原始局部变量进入堆栈。 (当然,如果它们没有被优化掉。)但是,本地参考变量也是如此。在对象中定义的原始成员生活在对象所在的任何地方。
      2013-12-18 14:55:10Z
    5. 同意这里的评论。堆栈/堆是一个侧面问题,并不相关。一些变量可能在堆栈上,一些变量在静态内存中(静态变量),并且堆上有很多变量(所有对象成员变量)。这些变量中的任何一个都不能通过引用传递:从被调用的方法中,永远不可能更改作为参数传递的变量的值。因此,Java中没有pass-by-reference。
      2014-03-02 15:37:40Z
    6. 醇>

    为了显示对比,请比较以下 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只有两种类型的传递:内置类型的值,以及对象类型的指针值。

        
    178
    2016-09-27 14:42:57Z
    1. + 1我还要将Dog **objPtrPtr添加到C ++示例中,这样我们就可以修改指针“指向”的内容。
      2014-07-02 03:59:12Z
    2. 这是我到目前为止看到的最佳答案之一。它主要避免了其他地方出现的“参考值与参考值”的无关语义论证,而是讨论了实际发生的机制。由于他们的自相矛盾或事实错误的内容,我不得不提供大多数其他“传递价值”的答案-1,但这个答案会改为+1。
      2017-08-02 15:32:06Z
    3. 但这并不能解决Java中的给定问题。事实上,Java方法中的所有内容都是Pass By Value,仅此而已。
      2018-11-06 09:11:44Z
    4. 这个例子只是证明java在C中使用了非常等效的指针吗? C中的值传递基本上是只读的吗?最后,这整个线程是不可理解的
      2019-02-21 12:58:41Z
    5. 醇>

    Java按值传递对象的引用。

        
    160
    2008-09-02 20:23:30Z

    基本上,重新分配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将重新定义baznull

        
    153
    2018-07-10 09:57:33Z
    1. 我宁愿说bar是引用baz(或baz别名)的副本,它最初指向同一个对象。
      2015-02-11 05:36:27Z
    2. String类和所有其他类之间是不是有些不同?
      2018-12-10 21:53:15Z
    3. 醇>

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

        
    146
    2010-09-07 22:07:02Z
    1. 我喜欢命名法中的这种区别。遗憾的是Java支持通过共享对象来调用,而不是通过值调用(就像C ++一样)。 Java支持仅对原始数据类型而不是复合数据类型进行值调用。
      2010-09-08 14:16:37Z
    2. 我真的不认为我们需要一个额外的术语 - 它只是特定类型值的传值。添加“原语调用”会添加任何澄清吗?
      2010-10-25 20:03:18Z
    3. 2011-09-16 00:29:43Z
    4. 所以我可以通过共享一些全局上下文对象来传递引用,甚至传递包含其他引用的上下文对象?我仍然按值传递,但至少我可以访问我可以修改的引用并使它们指向别的东西。
      2016-01-08 00:14:02Z
    5. Java已经传递了参数。它被称为“按值传递”。引入另一个术语只会增加混乱。
      2019-05-22 21:31:26Z
    6. 醇>

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

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

        
    109
    2018-02-06 10:18:14Z
    1. 通俗地称为指针。
      2011-02-09 09:50:58Z
    2. @ Gevorg - 那么什么是“NullPointerException”?
      2013-09-22 14:09:21Z
    3. @ Hot:一个遗憾的命名异常来自Java之前的明确术语。 c#中语义上等效的异常称为NullReferenceException。
      2013-09-23 14:08:10Z
    4. 在我看来,在Java术语中使用“引用”是一种妨碍理解的做法。
      2013-09-23 15:46:14Z
    5. 我学会了将这些所谓的“指向对象的指针”称为“对象的句柄”。这减少了歧义。
      2015-12-29 21:36:08Z
    6. 醇>

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

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

      Java不通过引用传递方法参数;它按值传递它们。我将使用这个网站

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

      该计划的流程:

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

      创建两个不同的Point对象,并将两个不同的引用关联起来。

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

      正如预期的输出将是:

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

      在这一行'传递价值'进入游戏......

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

      参考文献pnt1pnt2 通过值传递给棘手的方法,这意味着现在您的参考文献pnt1pnt2copies名为arg1arg2。所以pnt1arg1 到同一个对象。 (pnt2arg2相同)

      tricky方法中:

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

      tricky方法中的下一步

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

      在这里,您首先创建新的temp点参考,它将指向在同一个地方,如arg1参考。然后将参考arg1移动到 point arg2参考的同一个地方。 最后,arg2指向temp这样的同一个地方。

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

      因此在执行方法tricky后,当您返回main时,您遇到以下情况:

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

       
      X1: 0         Y1: 0
      X2: 0         Y2: 0
      X1: 100       Y1: 100
      X2: 0         Y2: 0
      
          
    80
    2015-09-16 15:18:11Z
    1. 在java中,当你说“在java中一切都是引用”时,你的意思是对象通过引用传递。 原始数据类型不会通过引用传递。
      2015-10-22 06:42:28Z
    2. 我能够在main方法中打印交换的值。在三重奏ethod,添加如下语句arg1.x = 1; arg1.y = 1; arg2.x = 2; arg2.y = 2;所以,因为arg1现在持有pnt2 refrence和arg2现在持有pnt1引用,所以,它的打印X1: 2 Y1: 2 X2: 1 Y2: 1
      2016-12-20 01:29:33Z
    3. 醇>

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

    首先,我们需要了解传递值和传递引用的内容。

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

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

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

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

    该程序的输出是:

     
    changevalue
    

    让我们一步一步地理解:

     
    Test t = new Test();
    

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

     
    new PassByValue().changeValue(t);
    

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

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

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

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

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

    希望这会有所帮助。

        
    77
    2018-04-17 12:57:46Z

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

    获取框外视图,让我们看一下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得到了复印件d到其中一个CPU寄存器。
    6. 223获得PUSHd到堆栈。
    7. 醇>

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

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

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

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

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

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

        
    68
    2015-12-28 07:36:27Z
    1. 在具有pass-by-reference的语言中,传递的东西(引用)是短暂的;收件人不应该“复制”它。在Java中,传递一个数组是一个“对象标识符” - 相当于一张纸上写着“对象#24601”,当构造的第24601个对象是一个数组时。收件人可以在任何想要的地方复制“对象#24601”,任何一张纸条上写着“对象#24601”的人都可以用任何数组元素做任何想做的事情。传递的位模式当然不会说“对象#24601”,但是......
      2014-01-10 17:02:04Z
    2. ...关键点是标识数组的object-id的接收者可以在任何地方存储该object-id并将其提供给任何人想要的,任何收件人都可以随时访问或修改数组。相比之下,如果我们是阵列通过像Pascal这样支持这种事情的语言引用传递,被调用的方法可以对数组做任何想做的事情,但不能以允许代码在返回后修改数组的方式存储引用。
      2014-01-10 17:04:46Z
    3. 实际上,在Pascal中你可以获取每个变量的地址,无论是通过引用传递还是本地复制,addr都是无类型的,@使用类型,您可以稍后修改引用的变量(本地复制的变量除外)。但我不明白为什么你会这样做。你的例子中的那张纸(Object#24601)是一个引用,它的目的是帮助在内存中找到数组,它本身不包含任何数组数据。如果重新启动程序,则相同的数组可能会获得不同的object-id,即使其内容与上一次运行中的内容相同。
      2014-01-10 22:48:25Z
    4. 我认为“@”运算符不是标准Pascal的一部分,而是作为公共扩展实现的。它是标准的一部分吗?我的观点是,在一种具有真正的pass-by-ref的语言中,并且没有能力构造一个短暂的指向短暂对象的指针,这个代码在传递数组之前保持对数组的唯一引用,在宇宙中的任何地方。参考可以知道,除非收件人“作弊”,否则它仍将保留唯一的参考。在Java中实现这一目标的唯一安全方法是构造一个临时对象...
      2014-01-10 22:52:31Z
    5. ...封装了AtomicReference,既没有暴露引用也没有暴露目标,而是包含了对目标做事的方法;一旦传递对象的代码返回,AtomicReference [其创建者保留直接引用]应该被废弃并放弃。这将提供适当的语义,但它会慢而且icky。
      2014-01-10 22:54:39Z
    6. 醇>

    Java是按值调用

    工作原理

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

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

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

    58
    2019-03-01 04:52:14Z

    据我所知,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中的引用没有影响。但是如果你的对象是e不是不可变的你可以改变它,例如:

     
    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这样的不可变对象。

        
    53
    2009-04-02 17:58:44Z
    1. + 1用于交换测试 - 可能是区分通过引用传递传递引用的最直接且最相关的方式按价值。 Iff你可以很容易地编写一个函数swap(a, b),它(1)从调用者的POV交换ab,(2)是类型无关的,只要静态类型允许(意味着使用它与另一种类型只需要更改声明的类型) ab)和(3)不要求调用者显式传递指针或名称,然后语言支持通过引用传递。
      2013-12-17 21:24:26Z
    2. “..对于原始数据类型,您将使用副本,对于对象,您将使用对象的引用副本” - 完美编写!
      2016-11-24 13:16:43Z
    3. 醇>

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

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

      

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

        
    53
    2015-08-22 05:50:12Z

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

    /**

    按值传递

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

    * /

    示例1:

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

    结果强>

     
    output : output
    value : Nikhil
    valueflag : false
    

    示例2:

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

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

    结果强>

     
    output : output
    value : Nikhil
    valueflag : false
    

    示例3:

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

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

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

    * /

     
    public class PassByValueObjectCase1 {
    
        private class Student {
            int id;
            String name;
            public Student() {
            }
            public Student(int id, String name) {
                super();
                this.id = id;
                this.name = name;
            }
            public int getId() {
                return id;
            }
            public void setId(int id) {
                this.id = id;
            }
            public String getName() {
                return name;
            }
            public void setName(String name) {
                this.name = name;
            }
            @Override
            public String toString() {
                return "Student [id=" + id + ", name=" + name + "]";
            }
        }
    
        public static void main(String[] args) {
            new PassByValueObjectCase1().caller();
        }
    
        public void caller() {
            Student student = new Student(10, "Nikhil");
            String output = method(student);
            /*
             * 'output' is insignificant in this example. we are more interested in
             * 'student'
             */
            System.out.println("output : " + output);
            System.out.println("student : " + student);
        }
    
        public String method(Student student) {
            student.setName("Anand");
            return "output";
        }
    }
    

    结果强>

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

    示例4:

    /**

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

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

    * /

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

    结果强>

     
    output : output
    student : Student [id=10, name=Nikhil]
    
        
    49
    2015-05-12 21:30:02Z

    你永远不能通过refeJava中的rence,其中一种显而易见的方法是当你想从方法调用中返回多个值时。考虑一下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中不可用。

        
    47
    2009-03-08 06:28:52Z

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

    首先,传球之间有什么区别通过引用与通过值?

      

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

         

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

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

      

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

    关于价值传递的主题

      

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

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

      

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

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

    参数的价值是什么?

    让我们考虑参考类型, Java虚拟机规范声明

      

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

    Java语言规范还说明

      

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

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

    所以

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

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

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

      

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

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


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

        
    46
    2017-05-23 12:18:26Z
    1. 感谢您开始讨论这些术语的定义。这使得你的答案与众不同,是正确理解的必要条件。
      2017-08-02 15:39:05Z
    2. 醇>

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

        
    36
    2014-04-30 04:50:16Z
    1. 我发现你的倒数第二句非常误导。 “Java中的所有对象都是引用”并不是真的。它只是那些引用的对象的引用
      2017-01-25 10:09:26Z
    2. 醇>

    正如许多人之前提到的那样, Java总是以值传递

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

     
    public class Test {
      public static void main(String[] args) {
        Integer a = new Integer(2);
        Integer b = new Integer(3);
        System.out.println("Before: a = " + a + ", b = " + b);
        swap(a,b);
        System.out.println("After: a = " + a + ", b = " + b);
      }
    
      public static swap(Integer iA, Integer iB) {
        Integer tmp = iA;
        iA = iB;
        iB = tmp;
      }
    }
    

    打印:

      

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

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

        
    35
    2008-09-03 20:01:59Z
    1. 我理解这个答案
      2018-04-25 18:04:21Z
    2. 醇>

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

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

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

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

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

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

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

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

        
    32
    2017-11-05 20:39:05Z
    1. 所以它是关于对象的byRef和关于基元的byVal?
      2017-01-18 11:40:26Z
    2. @ mox请阅读:对象不通过引用传递,这是一个ledgend:String a = new String(“changed”);
      2017-01-25 23:28:13Z
    3. public void changeit(String changeit){changit =“changed”;} changeIt(a);断言(a.equals( “不变”));
      2017-01-25 23:35:22Z
    4. 这是“它不是通过引用传递”语义论证的谬误的另一个很好的例子。这个答案用大胆的第一句话说:“只有参考文献才能通过。”传递引用,或者不传递引用。你刚才自己说过了一个参考文献。通过说“它不是通过引用传递,因为引用被传递但是......”你试图通过修改英语来说明一点。
      2017-08-02 15:07:10Z
    5. @ Aaron“按引用传递”并不意味着“传递一个值,该值是Java中称为'引用'的类型的成员”。 “参考”的两种用法意味着不同的东西。
      2017-09-27 08:36:50Z
    6. 醇>

    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();
    }
    
        
    31
    2017-04-29 04:31:33Z
    1. 这是看到Java按值传递的最清晰,最简单的方法。 objnull)的值传递给init,而不是obj的参考。
      2017-06-10 23:24:59Z
    2. 醇>

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

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

        
    30
    2009-01-12 20:47:43Z
    1. “按副本传递”是传递值的意思
      2016-08-07 08:46:02Z
    2. 醇>

    我已经为任何编程语言创建了一个致力于这类问题的线程此处

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

    • Java按值传递参数
    • “by value”是唯一的way在java中将参数传递给方法
    • 使用作为参数给出的对象的方法将改变 对象作为引用指向 原始物体。 (如果说 方法本身改变了一些值)
    25
    2017-05-23 12:02:58Z

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

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

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

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

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

        
    25
    2009-12-26 20:19:22Z
    1. + 1。 C 支持的是处理引用(其中C调用指针作为第一类值,以及然后通过值传递
      2010-09-07 22:02:17Z
    2. 是的,在C语言中,您不仅可以在对象上创建指针,还可以在任意变量上创建指针 - 这样您就可以轻松地模拟任何变量的“按引用调用”。在Java中,这仅适用于具有周围对象的变量,您可以给它。
      2011-02-03 01:27:19Z
    3. 醇>

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

    通常,Java具有直接按值传递的原始类型(int,bool,char,double等)。然后Java有对象(从java.lang.Object派生的所有东西)。实际上,对象总是通过引用来处理(引用是一个你无法触摸的指针)。这意味着实际上,对象是通过引用传递的,因为引用通常不是很有趣。但它确实意味着您无法更改指向哪个对象,因为引用本身是按值传递的。

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

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

        
    24
    2014-05-04 09:33:48Z
    1. 我认为它非常接近我对Java对象及其参考的理解。 Java中的对象通过引用副本(或别名)传递给方法。
      2015-02-11 05:58:48Z
    2. 醇>

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

    长话短说:

      

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

         

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

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

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

        
    23
    2017-02-11 18:52:29Z
    1. 我认为你通过说you can't change the reference from the object itself
      来钉它
      2017-03-30 05:41:44Z
    2. 所以一个对象可以通过使用它的公共接口来改变,但是不能用其他方式替换,即deference,并且这种方式可以实现两全其美
      2017-03-30 06:00:56Z
    3. 醇>
来源放置 这里