Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Immutable Integer wrapped into another class (call by value)

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?

like image 478
steve1337 Avatar asked May 02 '18 09:05

steve1337


2 Answers

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.

like image 63
J-Alex Avatar answered Nov 03 '22 09:11

J-Alex


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.

like image 25
Shanu Gupta Avatar answered Nov 03 '22 08:11

Shanu Gupta