How can I pass an Integer class correctly by reference?

I am hoping that someone can clarify what is happening here for me. I dug around in the integer class for a bit but because integer is overriding the + operator I could not figure out what was going wrong. My problem is with this line:

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

Here is my reasoning: I know that java is pass by value (or pass by value of reference), so I think that in the following example the integer object should be incremented each time.

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

This is my expected output:

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

This is the actual output.

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

Why is it behaving like this?

118614 次浏览

There are two problems:

  1. Integer is pass by value, not by reference. Changing the reference inside a method won't be reflected into the passed-in reference in the calling method.
  2. Integer is immutable. There's no such method like Integer#set(i). You could otherwise just make use of it.

To get it to work, you need to reassign the return value of the inc() method.

integer = inc(integer);

To learn a bit more about passing by value, here's another example:

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

I think it is the autoboxing that is throwing you off.

This part of your code:

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

Really boils down to code that looks like:

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

Which of course.. will not changes the reference passed in.

You could fix it with something like this

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

What you are seeing here is not an overloaded + oparator, but autoboxing behaviour. The Integer class is immutable and your code:

Integer i = 0;
i = i + 1;

is seen by the compiler (after the autoboxing) as:

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

so you are correct in your conclusion that the Integer instance is changed, but not sneakily - it is consistent with the Java language definition :-)

If you change your inc() function to this

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

then you will see that it always prints "false". That means that the addition creates a new instance of Integer and stores it in the local variable i ("local", because i is actually a copy of the reference that was passed), leaving the variable of the calling method untouched.

Integer is an immutable class, meaning that you cannot change it's value but must obtain a new instance. In this case you don't have to do it manually like this:

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

instead, it is done by autoboxing.

The Integer is immutable. You can wrap int in your custom wrapper class.

class WrapInt{
int value;
}


WrapInt theInt = new WrapInt();


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

You are correct here:

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

First: Integer is immutable.

Second: the Integer class is not overriding the + operator, there is autounboxing and autoboxing involved at that line (In older versions of Java you would get an error on the above line).
When you write i + 1 the compiler first converts the Integer to an (primitive) int for performing the addition: autounboxing. Next, doing i = <some int> the compiler converts from int to an (new) Integer: autoboxing.
So + is actually being applied to primitive ints.

There are 2 ways to pass by reference

  1. Use org.apache.commons.lang.mutable.MutableInt from Apache Commons library.
  2. Create custom class as shown below

Here's a sample code to do it:

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

Output:

1
1
7
7

Good answers above explaining the actual question from the OP.

If anyone needs to pass around a number that needs to be globally updated, use the AtomicInteger() instead of creating the various wrapper classes suggested or relying on 3rd party libs.

The AtomicInteger() is of course mostly used for thread safe access but if the performance hit is no issue, why not use this built-in class. The added bonus is of course the obvious thread safety.

import java.util.concurrent.atomic.AtomicInteger

1 ) Only the copy of reference is sent as a value to the formal parameter. When the formal parameter variable is assigned other value ,the formal parameter's reference changes but the actual parameter's reference remain the same incase of this integer object.

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


}

}

class IntObj { Integer a;

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

}

We can do this using 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());
}
}

This produces output:

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