I was wondering about calling by value in Java and tried some code.
public class Test {
public static void main(String[] args) {
Test test = new Test();
Integer integer = 4;
System.out.println(integer);
test.change(integer);
System.out.println(integer);
}
public void change(Integer integer) {
integer++;
}
}
Since java is call-by-value, i was wandering output like:
4
5
But it printed
4
4
And then i learned that Integers are immutable, so my method "change" created new Integer with value 5, while "integer" in main was still reffering to 4.
Then I wrote following class:
public class Test {
public static void main(String[] args) {
Test test = new Test();
MyInteger myInteger = new MyInteger();
myInteger.x = 4;
System.out.println(myInteger.x);
test.change(myInteger);
System.out.println(myInteger.x);
}
public void change(MyInteger myInteger) {
myInteger.x++;
}
}
class MyInteger {
Integer x;
}
And now output was like I expected at first
4
5
And here is my thesis:
Method change(Integer integer)
modifies myInteger
object by creating a new immutable Integer
in place of Integer x
, and argument myInteger
in this method points at the same instance of MyInteger
all the time. Am I right?
Let's do a bit analysis and dig into the bytecode of both approaches.
public class Main {
public static void main(String[] args) {
Main test = new Main();
Integer integer = 4;
System.out.println(integer);
test.change(integer);
System.out.println(integer);
}
public void change(Integer integer) {
integer++;
}
}
The bytecode for change
method is:
public change(Ljava/lang/Integer;)V
L0
LINENUMBER 14 L0
ALOAD 1
ASTORE 2
ALOAD 1
INVOKEVIRTUAL java/lang/Integer.intValue ()I // gets value of wrapped int
ICONST_1 // load 1 into stack
IADD // add 1 to your value
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; // return
DUP
ASTORE 1
ASTORE 3
ALOAD 2
POP
As you can see, the value is incremented and...and lost, because it was not returned. So we need to return this value to get the result, right?.
Someone can think that output for the following code will be 4, 5
:
public class Main {
public static void main(String[] args) {
Main test = new Main();
Integer integer = 4;
System.out.println(integer);
Integer integerNew = test.change(integer);
System.out.println(integerNew);
}
public Integer change(Integer integer) {
return integer++;
}
}
Output is 4, 4
.
Why? Because this is the post-increment. You incremented a new Integer
and returned the old one. Analyzing the bytecode confirms this:
public change(Ljava/lang/Integer;)Ljava/lang/Integer;
L0
LINENUMBER 14 L0
ALOAD 1
ASTORE 2 // store the copy of the first Integer (4)
ALOAD 1 //
INVOKEVIRTUAL java/lang/Integer.intValue ()I // get value of first
ICONST_1 // load 1
IADD // add 1
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; // get modified value Integer (5)
DUP
ASTORE 1 // save it
ASTORE 3
ALOAD 2 // load the copy Integer (4)
ARETURN // return it
So, we can see that the value of Integer
was incremented and...and lost again, because we returned value in the state before it was incremented.
Pre-increment is what we are looking for:
public Integer change(Integer integer) {
return ++integer;
}
And the bytecode is:
public change(Ljava/lang/Integer;)Ljava/lang/Integer;
L0
LINENUMBER 14 L0
ALOAD 1 // Integer (4)
INVOKEVIRTUAL java/lang/Integer.intValue ()I
ICONST_1 // load 1
IADD // becomes Integer (5)
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
DUP
ASTORE 1 // save it
ARETURN // return Integer (5)
The value was incremented before it was returned and we can accept it.
Analyzing the last case:
public class Main {
public static void main(String[] args) {
Main test = new Main();
MyInteger myInteger = new MyInteger();
myInteger.x = 4;
System.out.println(myInteger.x);
test.change(myInteger);
System.out.println(myInteger.x);
}
public void change(MyInteger integer) {
integer.x++;
}
}
class MyInteger {
Integer x;
}
And its bytecode:
public change(Lcom/test/MyInteger;)V
L0
LINENUMBER 15 L0
ALOAD 1
ASTORE 2
ALOAD 2
GETFIELD com/test/MyInteger.x : Ljava/lang/Integer; // get Integer (4)
ASTORE 3 // store copy of Integer value (4)
ALOAD 2
ALOAD 2
GETFIELD com/test/MyInteger.x : Ljava/lang/Integer; // get Integer (4)
INVOKEVIRTUAL java/lang/Integer.intValue ()I
ICONST_1
IADD // add 1
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
DUP_X1
PUTFIELD com/test/MyInteger.x : Ljava/lang/Integer; // PUT Integer (5) BACK!
ASTORE 4
ALOAD 3
POP
As you can see, starting from PUTFIELD
line the MyInteger
instance starts holding the reference to the Integer
with value 5
.
Hope this will help you understand more details about the topic.
You are absolutely right. myInteger.x++
is actually changing value because Integer x
instance variable is shared.
Infact myInteger.x++
also creates a new immutable Integer
instance. But you can access this new instance via test
variable, so you never lost track of it changes.
As a thumb-rule to avoid confusion I'd say:
When you're passing a paramter to some function, it doesn't matter what you do directly with it, but what you do with its subobjects.
In your case: myInteger.x
, x
is a subobject of myInteger.
That is why you could see the changes you do.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With