【问题标题】:How can I pass an Integer class correctly by reference?如何通过引用正确传递 Integer 类?
【发布时间】:2011-03-20 20:10:14
【问题描述】:

我希望有人可以为我澄清这里发生的事情。我在整数类中挖了一会儿,但因为整数 覆盖 + 运算符我无法弄清楚出了什么问题。我的问题在于这一行:

Integer i = 0;
i = i + 1;  // ← I think that this is somehow creating a new object!

这是我的推理: 我知道java是按值传递的(or pass by value of reference),所以我认为在下面的例子中,整数对象应该每次递增。

public class PassByReference {

    public static Integer inc(Integer i) {
        i = i+1;    // I think that this must be **sneakally** creating a new integer...  
        System.out.println("Inc: "+i);
        return i;
    }

    public static void main(String[] args) {
        Integer integer = new Integer(0);
        for (int i =0; i<10; i++){
            inc(integer);
            System.out.println("main: "+integer);
        }
    }
}

这是我的预期输出:

公司:1 主要:1 公司:2 主要:2 公司:3 主要:3 公司:4 主要:4 公司:5 主要:5 公司:6 主要:6 ...

这是实际输出。

公司:1 主要:0 公司:1 主要:0 公司:1 主要:0 ...

为什么会这样?

【问题讨论】:

标签: java integer pass-by-reference


【解决方案1】:

有两个问题:

  1. 整数是按值传递,而不是按引用传递。更改方法内的引用不会反映到调用方法中传入的引用中。
  2. 整数是不可变的。没有像Integer#set(i) 这样的方法。否则,您可以直接使用它。

要使其工作,您需要重新分配inc() 方法的返回值。

integer = inc(integer);

要了解有关按值传递的更多信息,这里有另一个示例:

public static void main(String... args) {
    String[] strings = new String[] { "foo", "bar" };
    changeReference(strings);
    System.out.println(Arrays.toString(strings)); // still [foo, bar]
    changeValue(strings);
    System.out.println(Arrays.toString(strings)); // [foo, foo]
}
public static void changeReference(String[] strings) {
    strings = new String[] { "foo", "foo" };
}
public static void changeValue(String[] strings) {
    strings[1] = "foo";
}

【讨论】:

  • 这不是真正的价值传递。它是按引用的值传递的......因为为对象传递的唯一东西是内存地址。
  • 严格来说,对于引用,它是“按值传递引用”,而对于原语,它只是“按值传递”。
  • 嗯,引用的副本被发送到函数中
  • 发送给函数的引用副本与发送给函数的引用有何不同?有什么明显的区别吗?
  • BalusC 已经搞定了,但这里是简单的解释。 Java 发送一个在函数堆栈上创建的引用副本。行 strings = new String[]{"foo", "foo"};修改在 changeReference(String[]) 本地堆栈上创建的引用副本。调用函数 main() 知道在它自己的堆栈上创建的引用(而不是在 changeReference() 堆栈上创建的引用)。
【解决方案2】:

整数是不可变的。您可以将 int 包装在您的自定义包装类中。

class WrapInt{
    int value;
}

WrapInt theInt = new WrapInt();

inc(theInt);
System.out.println("main: "+theInt.value);

【讨论】:

  • 为什么整数是不可变的?
  • @RahulJain 不确定,我怀疑是为了和实际的值类型(int)一致,所以它们的语义是一样的。
  • 一致是什么意思。 int 可以更改,但不能更改 Integer,这是包装类的缺点。
  • @RahulJain intprimitive 类型。原始类型是不可变的。我怀疑您将不变性与 final 关键字混淆了——它们是不同的东西。您可以更改Integer 变量的引用(与int 变量的值相同)。它们非常一致。
【解决方案3】:

上面解释了 OP 的实际问题的好答案。

如果有人需要传递需要全局更新的数字,请使用AtomicInteger(),而不是创建建议的各种包装类或依赖第 3 方库。

AtomicInteger() 当然主要用于线程安全访问,但如果性能没有问题,为什么不使用这个内置类。额外的好处当然是明显的线程安全性。

import java.util.concurrent.atomic.AtomicInteger

【讨论】:

  • 性能怎么样,完好无损吗?显然,如果只需要通过引用传递 AtomicInteger,则线程安全不需要它。
  • 性能似乎与 Integer 相同(假设您只是使用 .set()、.get() 并且没有使用任何线程安全操作)。
【解决方案4】:

引用传递有2种方式

  1. 使用 Apache Commons 库中的 org.apache.commons.lang.mutable.MutableInt
  2. 如下图所示创建自定义类

这是一个示例代码:

public class Test {
    public static void main(String args[]) {
        Integer a = new Integer(1);
        Integer b = a;
        Test.modify(a);
        System.out.println(a);
        System.out.println(b);

        IntegerObj ao = new IntegerObj(1);
        IntegerObj bo = ao;
        Test.modify(ao);
        System.out.println(ao.value);
        System.out.println(bo.value);
    }


    static void modify(Integer x) {
        x=7;
    }
    static void modify(IntegerObj x) {
        x.value=7;
    }   
}

class IntegerObj {
    int value;
    IntegerObj(int val) {
        this.value = val;
    }
}

输出:

1
1
7
7

【讨论】:

    【解决方案5】:

    您在这里看到的不是重载的+ 操作符,而是自动装箱行为。 Integer 类是不可变的,您的代码:

    Integer i = 0;
    i = i + 1;  
    

    被编译器(在自动装箱后)视为:

    Integer i = Integer.valueOf(0);
    i = Integer.valueOf(i.intValue() + 1);  
    

    所以您的结论是正确的,即 Integer 实例已更改,但并非偷偷摸摸 - 它与 Java 语言定义一致 :-)

    【讨论】:

      【解决方案6】:

      你说得对:

      Integer i = 0;
      i = i + 1;  // <- I think that this is somehow creating a new object!
      

      首先:整数是不可变的。

      第二:Integer 类没有覆盖 + 运算符,该行涉及自动拆箱和自动装箱(在旧版本的 Java 中,您会在上面的行中收到错误)。
      当您编写 i + 1 时,编译器首先将 Integer 转换为(原始)int 以执行加法:自动拆箱。接下来,执行i = &lt;some int&gt; 编译器会将int 转换为(新)整数:自动装箱。
      所以+实际上是应用于原始ints。

      【讨论】:

        【解决方案7】:

        我认为是自动装箱让你失望。

        这部分代码:

           public static Integer inc(Integer i) {
                i = i+1;    // I think that this must be **sneakally** creating a new integer...  
                System.out.println("Inc: "+i);
                return i;
            }
        

        真正归结为如下代码:

          public static Integer inc(Integer i) {
                i = new Integer(i) + new Integer(1);      
                System.out.println("Inc: "+i);
                return i;
            }
        

        这当然..不会改变传入的引用。

        你可以用这样的东西来修复它

          public static void main(String[] args) {
                Integer integer = new Integer(0);
                for (int i =0; i<10; i++){
                    integer = inc(integer);
                    System.out.println("main: "+integer);
                }
            }
        

        【讨论】:

        • 嗯,好吧,这很有趣。感谢自动装箱术语。不可变似乎也很关键。周末愉快!
        • 你看看自动装箱。回到 Java 1.4,您只能将整数与整数相加,或整数与整数相加。您现在可以以任何方式做的事实可能有点难以仅看代码来概念化。
        【解决方案8】:

        如果你把你的 inc() 函数改成这个

         public static Integer inc(Integer i) {
              Integer iParam = i;
              i = i+1;    // I think that this must be **sneakally** creating a new integer...  
              System.out.println(i == iParam);
              return i;
          }
        

        然后你会看到它总是打印“false”。 这意味着添加创建了一个新的 Integer 实例并将其存储在 local 变量 i 中(“本地”,因为 i 实际上是传递的引用的副本),留下调用方法不变。

        Integer 是一个不可变类,这意味着您不能更改它的值,但必须获取一个新实例。在这种情况下,您不必像这样手动执行此操作:

        i = new Integer(i+1); //actually, you would use Integer.valueOf(i.intValue()+1);
        

        相反,它是通过自动装箱完成的。

        【讨论】:

          【解决方案9】:

          1 ) 只有引用的副本作为值发送到形式参数。当形参变量被赋值为其他值时,形参的引用发生变化,但实参的引用在这个整数对象的情况下保持不变。

          公共类理解对象{

          public static void main(String[] args) {
          
              Integer actualParam = new Integer(10);
          
              changeValue(actualParam);
          
              System.out.println("Output " + actualParam); // o/p =10
          
              IntObj obj = new IntObj();
          
              obj.setVal(20);
          
              changeValue(obj);
          
              System.out.println(obj.a); // o/p =200
          
          }
          
          private static void changeValue(Integer formalParam) {
          
              formalParam = 100;
          
              // Only the copy of reference is set to the formal parameter
              // this is something like => Integer formalParam =new Integer(100);
              // Here we are changing the reference of formalParam itself not just the
              // reference value
          
          }
          
          private static void changeValue(IntObj obj) {
              obj.setVal(200);
          
              /*
               * obj = new IntObj(); obj.setVal(200);
               */
              // Here we are not changing the reference of obj. we are just changing the
              // reference obj's value
          
              // we are not doing obj = new IntObj() ; obj.setValue(200); which has happend
              // with the Integer
          
          }
          

          }

          类 IntObj { 整数a;

          public void setVal(int a) {
              this.a = a;
          }
          

          }

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-05-05
            • 2021-11-05
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-01-24
            相关资源
            最近更新 更多