如何正确地通过引用传递一个整数类?

88

我希望有人能为我澄清这里发生了什么。我在整数类中查找了一段时间,但由于integer类正在覆盖+运算符,所以我无法弄清楚出了什么问题。我的问题出在这一行:

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

我的推理如下: 我知道Java是按值传递(或者是按引用传递的值),所以我认为在下面的例子中,整数对象应该每次都会增加。

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

这是我期望的输出结果:

Inc: 1
main: 1
Inc: 2
main: 2
Inc: 3
main: 3
Inc: 4
main: 4
Inc: 5
main: 5
Inc: 6
main: 6
...

这是实际输出结果:

Inc: 1
main: 0
Inc: 1
main: 0
Inc: 1
main: 0
...

为什么会出现这种情况?


相关问题:https://dev59.com/V2855IYBdhLWcg3wIAka - koppor
11个回答

80

这里有两个问题:

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

19
它并不是真正的按值传递,而是按引用的值传递......因为对象唯一传递的东西是内存地址。 - bwawok
34
严格来说,对于引用类型,是“按值传递引用”,而对于基本数据类型,则是“按值传递”。 - BalusC
2
好的,函数中传入了一个引用的副本。 - Rodislav Moldovan
将引用的副本发送到函数与将引用发送到函数有何不同?是否存在任何显著差异? - Sreekanth Karumanaghat
BalusC 已经把它搞定了,但这里是简单的解释。Java 发送一个在函数的堆栈上创建的引用副本。行 strings = new String[]{"foo", "foo"}; 修改了在 changeReference(String[]) 本地堆栈上创建的引用副本。调用函数 main() 知道它自己堆栈上创建的引用(而不是在 changeReference() 堆栈上创建的引用)。 - A.B.

30

上面的回答很好地解释了OP提出的实际问题。

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

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

import java.util.concurrent.atomic.AtomicInteger

性能方面怎么样,还完好无损吗?显然,如果只需要使用AtomicInteger来进行引用传递,则不需要它来确保线程安全。 - krizajb
性能似乎与整数相同(假设您只是使用.set(),.get()而不使用任何线程安全操作)。 - PlsWork
这是真正的解决方案,应该置于顶部。 - Vikrant Pandey

26

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

class WrapInt{
    int value;
}

WrapInt theInt = new WrapInt();

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

3
为什么整数被设计成不可变的? - Rahul Jain
3
不确定,我怀疑这是为了与实际值类型(int)保持一致,从而使它们具有相同的语义。 - Markos
1
什么是一致性?int 可以更改,但 Integer 不行,这是包装类的缺点。 - Rahul Jain
2
@RahulJain int 是一种原始类型。原始类型是不可变的。我怀疑你把不可变性和 final 关键字混淆了 - 它们是不同的东西。你可以改变 Integer 变量的引用(与 int 变量的值相同)。它们基本上是一致的。 - Markos

19

有两种方法可以通过引用传递

  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

17

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

Integer i = 0;
i = i + 1;  

编译器(在自动装箱之后)将其视为:

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

所以你的结论是正确的,Integer 实例被更改了,但并不是偷偷摸摸的——这与 Java 语言定义是一致的 :-)


7

您在这里是正确的:

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

首先:Integer是不可变的。

其次:Integer类没有重写+运算符,在该行中涉及到自动拆箱和装箱(在旧版本的Java中,您将在上面的行上获得错误)。
当您编写i + 1时,编译器首先将Integer转换为(原始的)int进行加法运算:自动拆箱。接下来,执行i = <some int>时,编译器会从int转换为(新的)Integer:自动装箱。
因此,+实际上被应用于原始的int


3
我认为是自动装箱使你感到困惑。
你代码中的这部分内容:

```

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

1
嗯,好的,这很有意思。感谢您提供自动装箱术语。不可变似乎也非常重要。祝您周末愉快! - sixtyfootersdude
1
你可以查阅关于自动装箱的内容。在Java 1.4中,你只能将int类型与int类型相加,或者将Integer类型与Integer类型相加。现在可以以任何方式进行操作,仅凭代码很难理解这一点。 - bwawok

3
如果你将你的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的新实例并将其存储在本地变量i中(“本地”是因为i实际上是传递的引用的副本),使调用方法的变量不受影响。

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

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

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

1

1) 仅将引用的副本作为值发送到形式参数。当给形式参数变量赋其他值时,形式参数的引用会改变,但在这种整数对象的情况下,实际参数的引用保持不变。

public class UnderstandingObjects {

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 { Integer a; }

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

}


0

我们可以使用Apache Commons Mutable Int来实现这个。

public static Integer inc(MutableInt i) {
        i.increment();    
        System.out.println("Inc: "+i.getValue());
        return i;
}

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

这将产生输出:

Inc: 1
main: 1
Inc: 2
main: 2
Inc: 3
main: 3
Inc: 4
main: 4
Inc: 5
main: 5
Inc: 6
main: 6
Inc: 7
main: 7
Inc: 8
main: 8
Inc: 9
main: 9
Inc: 10
main: 10

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接