【问题标题】:Why do they call it pass by value in Java? [duplicate]为什么他们在 Java 中称其为按值传递? [复制]
【发布时间】:2023-03-08 08:59:01
【问题描述】:

好的,所以我是一个相对较新的程序员。我一遍又一遍地阅读有关传递变量的信息。从我在这里读到的内容是我所理解的:在 Java 中,当您将变量传递给方法(无论是原始方法还是对象)时,您总是按值传递。但是您真正所做的是将引用的副本传递给内存中的对象或变量。

那么为什么他们称之为按值传递呢?在我看来,您是通过引用传递的。另外,在 C 中,当您将指针传递给函数时,您不是在传递内存中变量的地址吗?然而,它被称为按值传递。我在看什么?有人可以解释并澄清我为什么这样做吗?我知道如何传递变量等,我已经阅读了几篇关于此的 SO 帖子,但我还不完全满意。举个例子:

  static void setNameStatic(Dog d, String name) {

        d.name = name;

  }


  public static void main (String args[]) {

           Dog d = new Dog("dog1");

           System.out.println( d.getName() );

           setNameStatic( d, "dog2" );

           System.out.println( d.getName() );

  }

不会输出:dog1 和 dog2。所以你不去参考所指的地方并改变数据吗?值与这里究竟有什么关系?感谢任何有用的 cmets。是的,我知道它是按值传递,但为什么不称为“通过发送引用副本传递”lol?

更新:感谢所有提供有用答案的人。我知道这被标记为重复,对此感到抱歉。我对现在的答案感到满意。

【问题讨论】:

  • @MarounMaroun 这仍然很有趣,因为它是按引用的值传递的。
  • 尝试将 d 更改为 setNameStatic 中的 Dog d 以指向另一个实例。
  • 总是按值..当它是引用时,引用被传递..按值。
  • 您是否能够在方法中更新原始参考(为其分配全新的值)?如果不是,那么您没有通过参考。

标签: java


【解决方案1】:

此时你的记忆是这样的:

Dog d = new Dog("dog1");

Stack (with local vars)            Heap

+-----------+     points to
|  Dog d    | --------------->    real dog object
+-----------+
|  ...      |
+-----------+
|  ...      |
+-----------+

然后,当您传入对方法的引用时,它看起来像这样(Dog dog2 是方法参数):

Stack (with local vars)            Heap
+-----------+
|  Dog d    | --------------->    real dog object
+-----------+                             ^
|  ...      |                             |
+-----------+                             |
|  Dog d2   |------------------------------
+-----------+

所以,引用是按值传递的。它指向同一个对象,但它是 new 引用!

意思是,如果你想这样尝试:

public static void main(String ... args) {
    Dog d= new Dog("dog1");
    setNameStatic(d, "dog2");
    System.out.println(d); // Still prints dog1 !!!
}

static void setNameStatic(Dog d2, String name) {
    d2= new Dog(name);
}

因为新的(复制的)引用现在指向堆上的新对象,而原始对象没有改变。

【讨论】:

    【解决方案2】:

    因为任何reference类型的变量的都是它在内存中的地址。

    因为primitive类型的任何变量的就是它的值本身。

    所以你总是传递变量的

    【讨论】:

      【解决方案3】:

      每个变量都是按值传递的。在原语的实例中,我们有一个幼稚的情况 - 值被传递到函数中。

      在对象的实例中,我们有一个稍微复杂但类似的范例 - 对对象的引用是按值传递的

      注意细微差别:

      //This is essentially a no-op. The values are only local to the function
      void swap(int a, int b){
          int temp = a;
          a = b;
          b = temp;
      }
      
      //This is different. The references to a and b are passed by value so the values of a.x and b.x are actually swapped on return.
      void swap(Point a, Point b){
          int temp = a.x;
          a.x = b.x;
          b.x = temp;
      }
      

      【讨论】:

        【解决方案4】:

        按值传递意味着将值传递给方法,即创建相同值的另一个副本:

        int i = 5; // one copy of 5
        foo(i);
        
        private void foo(int n) {
           // here n is 5, however it is "another" 5
        }
        

        按值传递的证据是您不能从方法foo() 中更改您的i

        int i = 5; // one copy of 5
        foo(i);
        // i is still 5 here
        
        private void foo(int n) {
           n = 6;
           // n is 6, however it is actual inside the method only.
        }
        

        对象也是如此,但在这种情况下,引用是按值传递的。

        【讨论】:

          【解决方案5】:

          另外,在 C 语言中,当您将指针传递给函数时,您不是在传递内存中变量的地址吗?然而它被称为传值。

          不,不是。当您将 指针 传递给 C 中的函数时,它被称为“按引用传递”。该函数可以直接访问和修改原始值,因为它有它的地址。按值传递是指传递对象的,而不是它们的位置或地址。请看以下示例。

          int addOne(int x)
          {
              return x + 1;
          }
          
          void destructiveAddOne(int *x)
          {
            *x += 1;
          }
          
          int main(void)
          {
              int x = 5;
              int y = 5;
          
              // We pass the _value_ of x, ie, 5 to addOne, and store the result in z.
              // The value of x will remain unchanged, ie, 5.
              int z = addOne(x);
          
              // We pass a REFERENCE to y, ie, it's address to destructiveAddOne()
              // destructiveAddOne then modifies the value of y to be y + 1. The value of y
              // has now CHANGED to 6. This is call by reference.
              destructiveAddOne(&y);
          
              return 0;
          }
          

          【讨论】:

            【解决方案6】:
            1. 一个变量保存一些位,这些位告诉 JVM 如何访问内存(堆)中的引用对象。

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

            3. 所以你只是传递 3bad086a 它是引用的值。
            4. 您传递的是引用的值,而不是引用本身(而不是对象)。
            5. 这个值实际上是被复制并给了方法。

            以前有过: Is Java "pass-by-reference" or "pass-by-value"?

            【讨论】:

              【解决方案7】:

              让你的头脑清醒是一件棘手的事情。我认为它是实数,尽管是抽象的。假设您的第一条狗 dog1 是在内存位置“100,000”创建的(我知道但请耐心等待)。您将参考值 100,000 传递给方法。您的方法更改了生活在 100,000 的对象的名称。

              如果您将 dog1 对象传递给执行 d = new Dog("dogXXX") 的方法,那么它将使用(例如)200,000 处的全新对象,而使您的 dog1 位于内存地址 100,000 处保持不变。

              对内存地址滥用表示歉意,这将使专家吐出他们的咖啡......

              【讨论】:

                【解决方案8】:

                在 java 中,您通过值将参数传递给方法,但如果参数是对象,则参数本身就是 ref 类型。

                C 这样的编程语言相比,允许通过引用传递,如果您打印引用,您会得到与打印它的值时不同的东西。

                为了确保对象是引用类型并将它们与原语进行比较,试试这个:

                public class TestClass {
                    public static void main(String[] args) {
                        String s = new String("a");
                        String t = new String("a");
                        System.out.println(s==t);
                        char a = 'a';
                        char b = 'a';
                        System.out.println(a==b);
                    }
                }
                

                结果是:
                false
                true

                【讨论】:

                  【解决方案9】:

                  Java 中的引用是通过引用的值传递的。 当您将引用作为方法参数传递时,将创建副本并在此副本上进行操作。 为了说明这一点,请查看以下示例代码:

                  final Person person = new Person();
                  person.setName("A");
                  changeName(person, "B");
                  System.out.println(person.getName());
                  

                  现在如果我们将changeName 实现为这样:

                  private static void changeName(Person person, String name) {
                      person.setName(name);
                  }
                  

                  那么结果是B。这是因为引用的副本仍然指向同一个对象,并且有可能修改它。

                  另一方面,如果您将changeName 方法更改为:

                  private static void changeName(Person person, String name) {
                      person = new Person();
                      person.setName(name);
                  }
                  

                  那么结果是A。这是因为引用的副本指向了新对象,但是原始引用仍然指向原始对象,并且它的值没有被修改。 如果 Java 将通过引用传递对象引用,则结果将是 B,因为它将对原始对象进行操作。

                  【讨论】:

                    【解决方案10】:

                    当你在Java中将一个对象作为参数传递时,你实际上是传递了一个引用,而引用就是堆栈中使用的值。但是,调用者不会观察到您在方法内对该引用所做的任何分配,因为您只更改了引用的副本。原始值作为参数也是如此。

                    在其他语言(如 C++)中,您可以选择通过引用传递,允许您从方法中更改引用本身。例如,在 Java 中,您无法实现带有两个参数并交换它们的交换方法。在 C++ 中这是可能的。

                    【讨论】:

                      猜你喜欢
                      • 2013-03-06
                      • 1970-01-01
                      • 1970-01-01
                      • 2015-03-06
                      • 2021-09-06
                      • 2012-01-16
                      • 2019-12-24
                      • 1970-01-01
                      • 2012-08-07
                      相关资源
                      最近更新 更多