0

When I have a function that changes an object, I expect the value of the object to change in the calling function (as described in Is Java “pass-by-reference” or “pass-by-value” since the reference is not changed). But it doesn't seem to work for the wrapper classes (like Integer).

The following code:

public class Test
{
    private static class MyInt
    {
        private int i;
        public MyInt(int i)
        {
            this.i = i;
        }
        public int get()
        {
            return i;
        }
        public void set(int i)
        {
            this.i = i;
        }
        public void increment()
        {
            ++this.i;
        }
        public String toString()
        {
            return Integer.toString(i);
        }

    }

    public static void test(int a, Integer b, MyInt c)
    {
        ++a;
        ++b;
        System.out.println("b after increment is " + b);
        c.increment();
    }

    public static void main(String[] args)
    {
        int a = 5;
        Integer b = new Integer(5);
        MyInt c = new MyInt(5);

        test(a, b, c);

        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
    }
}

generates the following output:

b after increment is 6
5
5
6

from which I can see that Integer object does actually change inside the function, becoming 6, but it doesn't change the value of b in main. What is happening here? Are wrapper objects passed in some other way than regular objects?

Mark Rotteveel
  • 82,132
  • 136
  • 114
  • 158
v010dya
  • 4,283
  • 5
  • 24
  • 43
  • 5
    "Are wrapper objects passed in some other way than regular objects?" Objects are never passed. References are passed, *always* by value. You won't observe any `Integer` object changing, because `Integer` is immutable (like `String`). What you're seeing is the value of the `a` and `b` parameters changing to be references to different `Integer` objects - that's not the same thing. – Jon Skeet Apr 21 '21 at 11:38
  • @kaya3 No, that is probably the exact opposite of this question. – v010dya Apr 21 '21 at 12:29
  • 2
    Well, the question you asked is *"Why is Integer object passed by value rather than by reference?"* and the answer is that *nothing* in Java is ever passed by reference. So if that isn't the answer you're looking for then your question needs to be clarified. – kaya3 Apr 21 '21 at 12:32
  • @kaya3 Yes, because that is how everybody understands it. Saying something like "it's passed by value, but you pass the reference" is as helpful as "i am not driving a car, i am sitting in the driver's seat and making the car drive itself by pressing pedals" – v010dya Apr 21 '21 at 12:36
  • 2
    @v010dya: No, it's really not like that at all. It's a matter of being precise in the terminology. Sure, it needs more explanation than *just* that sentence if you want to go through it in detail, but it's more than just being pedantic for the sake of it. Pass-by-value and pass-by-reference have pretty clear meanings across multiple languages, and Java is strictly pass-by-value, and never passes objects. Playing fast and loose with the terminology causes problems - as demonstrated by the question here. – Jon Skeet Apr 21 '21 at 12:40
  • The distinction is that if it were pass-by-reference, then the `test` method *could* change the local variable `b` in the `main` method to refer to a different object, but because it is pass-by-value, then the `test` method cannot change the value of a local variable in the `main` method. So this is not a matter of pedantry; your question seems to be why the `test` method does not change the local variable `b` in the `main` method, and the answer is that Java does not have pass-by-reference semantics. – kaya3 Apr 21 '21 at 12:42
  • If you really think the distinction between pass-by-value and pass-by-reference semantics is like the difference between *"driving a car"* and *"sitting in the driver's seat and making the car drive itself by pressing pedals"*, i.e. the difference is just in phrasing rather than what the actual outcome is, then you have not actually understood pass-by-value vs. pass-by-reference. They have different outcomes, and the difference in outcomes does explain the phenomenon you are asking about. – kaya3 Apr 21 '21 at 12:48
  • @kaya3 Sitting in a driver's seat of a self-driving car can be different than sitting in a driver's seat of a regular car. But everybody understands that there is a difference. This question is about this particular difference. Both `Integer` and `MyInt` are passed in the same way, you can call it "pass-by-bilbo-baggins" it won't change the fact that they are passed in the same way. Yet the outcome is different. That is the point. – v010dya Apr 21 '21 at 12:51
  • They are passed the same way but not *used* the same way, one creates a new object in the `test` method and the other mutates the state of the object passed in. This is isomorphic to the two examples in the accepted answer to the linked duplicate, where the different behaviour when creating a new object locally vs. mutating the object passed in, is demonstrated and explained in that answer. – kaya3 Apr 21 '21 at 12:55
  • @kaya3 You finally understood that. The difference is not in the way they are passed, but in the way they are used. There is an unexpected (at least potentially unexpected) change in the reference itself. That is why this question cannot be the same as the difference between pass-by-value vs pass-by-reference. – v010dya Apr 21 '21 at 13:11
  • That is the *answer*, and as I said, it is answered in the linked duplicate. – kaya3 Apr 21 '21 at 13:17

1 Answers1

0

What is happening when one does the following lines is actually different than what one would expect:

Integer b = Integer.valueOf(5);
System.out.println("code of b is " + System.identityHashCode(b));
++b;
System.out.println("code of b is " + System.identityHashCode(b));

As can be seen by the output after the incrementation b is actually pointing to a different object, rather than changing the internal value in the existing one.

As such ++ operator could be closer approximated with a different line of code than c.increment(), rather you should write c = new MyInt(c.get() + 1);

v010dya
  • 4,283
  • 5
  • 24
  • 43
  • 4
    "is actually different than what one would expect" - it's not different to what I would expect. I suspect most people who have used the wrapper types for any significant amount of time (and are aware that they're immutable) would not be surprised by this behavior. – Jon Skeet Apr 21 '21 at 11:39
  • Try printing out `System.out.println("code of Integer.valueOf(6) is " + System.identityHashCode(Integer.valueOf(6)));` You'll notice it's exactly the "new" b. If b remained the same object, then `Integer.valueOf(5)` would have a value of 6 instead of 5, no? – Scratte Apr 21 '21 at 11:51
  • @Scratte You are constructing the same object, most likely this is a multiton. However, if you have the user enter 6 and then parse it into `Integer` it may well generate a different reference despite values technically being the same. – v010dya Apr 21 '21 at 13:25
  • No, it will not generate a different reference object. `Integer` of values from -128 to 127 are kept in a pool. There's no creating two Integer instances of value 6, there's always only the one instance. Not entirely sure if Java has a shortcut of `++` on an Integer instance, or if `++b` will unwrap the Integer, then add the value and then wrap the int back into an Integer. – Scratte Apr 21 '21 at 13:35
  • See [Why is 128==128 false but 127==127 is true when comparing Integer wrappers in Java?](https://stackoverflow.com/questions/1700081/why-is-128-128-false-but-127-127-is-true-when-comparing-integer-wrappers-in-ja) about the restrictions on instances of Integer and other wrapping classes. – Scratte Apr 21 '21 at 13:43