0

EDIT 1: I am aware this question has been marked as a duplicate and I initially deleted it. However, after reading in this Meta Post that deleting is bad and that I should edit my question rather than deleting it, I undeleted and am trying to improve the question for anyone else who might have the same issue I did.

After the post marked as "already answered" linked to something I already knew (and a post I've read often) but didn't represent my question/confusion, I tried to expand my test case and realized my problem was not in knowing about references, but rather misunderstanding of how autoboxing worked and the relationship between an Integer reference and its (immutable) value. See edit below the original question which demonstrates what my confusion was.

Original Question:

I'm still trying to learn how to leverage Java 8+ lambda functional interfaces. While I know it's not necessarily a best practice to modify a parameter passed to a method, I am not sure why the input value to a lambda expression, modified within the expression, is not modified.

Here's an example program which I would expect to modify the value of its input (t) to the value 42:

import java.util.function.Consumer;

public class Main {
    static Consumer<Integer> foo = n -> {
        n = 42;
    };

    public static void main(String... args) {
        Integer t = 0;
        foo.accept(t);
        System.out.println("Why is " + t + " not equal to 42?");
    }
}

Output:

Why is 0 not equal to 42?

EDIT 2:

As has been pointed out by the first commenter, the problem was trying to reassign the parameter (an immutable Integer container). I wasn't really trying to change the reference of the Integer, I was trying to change the boxed value and assumed that Java would do that for me automagically. I was wrong.

Below, I have modified the earlier code to demonstrate what I thought would happen and what I was expecting. While the marked duplicate about "pass by value" is related and one of several associated issues, it was not the source of my confusion. It was ultimately my misunderstanding of how autoboxing works (creating a new object, rather than modifying the boxed value of the existing object) that was the root of my issue.

import java.util.function.Consumer;

public class Main {
    /**
     * How I thought {@link Integer} worked, not how it works.
     */
    class BoxedInt {
        int boxed;

        BoxedInt(int i) {
            box(i);
        }

        void box(int i) {
            this.boxed = i;
        }

        int unbox() {
            return boxed;
        }

        public String toString() {
            return String.valueOf(unbox());
        }
    }

    static Consumer<BoxedInt> foo = n -> {
        n.box(42); // what I thought happened with n = 42
    };

    public static void main(String... args) {
        BoxedInt t = new Main().new BoxedInt(0);
        foo.accept(t);
        System.out.println("See how " + t + " is equal to 42.");
    }
}

Result:

See how 42 is equal to 42.

Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63
  • 2
    This doesn't have anything to do with Functional Interfaces. You can't reassign a parameter and have it effect any other references. You would have to mutate a mutable container to achieve this. – Carcigenicate Mar 21 '19 at 18:27
  • 1
    Java is pass-by-value only. – Peter Lawrey Mar 21 '19 at 18:28
  • @PeterLawrey The question you have linked as a duplicate is not related to the issue. I am aware that Java passes a reference to the Integer class which points to an integer value stored elsewhere. You may wish to link it to a different duplicate question. – Daniel Widdis Mar 22 '19 at 00:42
  • 1
    @DanielWiddis: *"The question you have linked as a duplicate is not related to the issue"* ... in fact, it really is related. The local variable `t` that you initialized to 0 in `main` is not passed by reference to the function object `foo`, only its value is. Changing the local variable `n` in your function object has no effect on the local variable `t`. – scottb Mar 22 '19 at 00:51
  • @scottb the local variable `t` is autoboxed to an `Integer` with some memory address pointing to the value `0` somewhere else. The functional interface takes an `Integer` input so it is in fact this same `Integer` object. Carcigenicate, the first commenter, pointed out that if I had passed a "mutable container" then I could do what I wanted; the problem is that `Integer` is not a mutable container. – Daniel Widdis Mar 22 '19 at 01:38
  • @DanielWiddis that's strange: you understand Java is pass-by-value, you understand auto-boxing (and unboxing I believe). However, you still miss what the line `n = 42;` is doing? – Adrian Shum Mar 22 '19 at 01:44
  • @DanielWiddis: *"if I had passed a "mutable container" then I could do what I wanted"* ... this isn't quite true. Even if `Integer` were mutable, you would have gotten the same result from the print statement in `main`. The assignment statement in your function object would not attempt to mutate the referenced object even if it were not immutable. A new object is created via autoboxing and assigned to the local variable `n` which quickly falls out of scope. The local variable `t` never had a reference to the object created in your function object `foo`. – scottb Mar 22 '19 at 01:48
  • @scottb I have updated my question with a more clear explanation of what I was thinking. If you have other advice for how I can edit the question (rather than just deleting it, which is apparently frowned upon) I would appreciate it. – Daniel Widdis Mar 22 '19 at 02:02
  • This has nothing to do with the immutability of `Integer`. You were reassigning a parameter. That works the same for all types, whether the variable is of a primitive type or a reference type (and regardless of its mutability). Because Java is pass by value, the parameter holds a copy of the value of the argument used to invoke the method. The parameter has no relation (reference) to any potential variable used as an argument to a method. – Sotirios Delimanolis Mar 22 '19 at 02:24
  • 2
    Also, duplicates in and of themselves are a _good thing_. – Sotirios Delimanolis Mar 22 '19 at 02:24
  • 1
    @SotiriosDelimanolis thanks. At this point it's a matter of semantics. I thought Integer autoboxing behaved as in my second snippet (modified the boxed value). I was obviously wrong. Anyway, thanks all for the education. – Daniel Widdis Mar 22 '19 at 02:39
  • 1
    Note: `n.box(42); // what I thought happened with n = 42`. There is no way to make Java turn an operator into a method call, or overriding the behaviour of an operator, even though it would be nice in some cases to add this syntactic sugar. Other JVM languages such as Groovy, Scala and Kotlin do support overriding the behaviour of some operators. – Peter Lawrey Mar 22 '19 at 16:16
  • 1
    In particular, modifying the object of an autoboxed value would be very bad as it uses a cache by default for small values i.e. `Integer i = 42; Integer j = 42; assert (Object) i == (Object) j;` i.e. they point to the same object. If you change it via reflection, for example, all the values of `(Integer) 42` now look different and corrupted! – Peter Lawrey Mar 22 '19 at 16:18

0 Answers0