Call by reference confusion

78 Views Asked by At

I was given this hypothetical block of code, where although the language used is Java, and Java uses call by value, in this case call by reference had to be used for every method call, and I had to give an output in the format:

y:[result]; y: [result]; y:[result]; x:[result]; a:[result]
public class Main {
    static int x = 2;

    public static void main(String[] args) {
        int[] a = {17, 43, 12};
        foo(a[x]);
        foo(x);
        foo(a[x]);
        System.out.println("x:" + x);
        System.out.println("a:" + Arrays.toString(a));
    }

    static void foo(int y) {
        x = x - 1;
        y = y + 2;
        if (x < 0) {
            x = 5;
        } else if (x > 20) {
            x = 7;
        }
        System.out.println("y:" + y);
    }
}.

I'm not 100% sure on how the call by reference works in some cases, and I'm not sure which result is the right one.

Anyway here is one:

foo(a[x]) is called with a[2] (which is 12). y becomes 12 + 2 = 14. x is decremented to 1.

foo(x) is called with x (which is 1). Both x and y point to the value 1 of x. x is decremented to 0 and then x becomes 3 because y=y+2 and y was pointing at the value 1 of x.

foo(a[x]) is called with a[3] (which doesnt exists). x is decremented to 2.

The array a transforms into 17,43,14.

So, the results would be like:

y : 14; y : 3; y : ?; x : 2; a : 17,43,14

I think the thing that confuses me the most is in the case of foo(x). Does y point at variable x or the value of x at the moment the method is called?

2

There are 2 best solutions below

1
Remy Lebeau On

foo() is taking in a primitive int by value, so it is a copy of whatever value you pass in at the call site. There is no reference here.

It doesn't matter what foo() does internally to the value of y, that value does not get reflected back to the caller.

Also, there is no such thing as "point at a value". You can have a pointer/reference to a variable, but not point/refer to a value. A variable holds a value.

then x becomes 3 because y=y+2 and y was pointing at the value 1 of x.

foo(a[x]) is called with a[3] (which doesnt exists). x is decremented to 2.

This is incorrect. It is y, not x, that becomes 3.

y = y + 2;
^

x is still 0 at this point. So the subsequent call to foo(a[x]) is using a[0] instead, which then decrements x to -1 and then sets x to 5.

0
rzwitserloot On

I was given this hypothetical block of code, where although the language used is Java, and Java uses call by value, in this case call by reference

Java isn't that kind of language. In some languages, notably C (as well as, oversimplifying a bit, Rust) you can specify on a per-parameter basis whether it is pass-by-ref or pass-by-value. But in java you cannot do that. A parameter is pass-by-value. Period. You can't configure it to be pass-by-ref.

However.

All types in java are references, except the primitives. The primitives are hardcoded. It's: long, int, short, byte, float, double, boolean, and char. That's the list. It's set in stone. You can't make your own. The list will never change. Everything else is a reference type. including String.

That means every value is a pointer.

These pointers are passed-by-value. But they are pointers.

So what does that mean for primitives?

They are passed by value, and there are no complications here. Hence, none what you said is going to work that way because when you call foo(x) or even foo(arr[x]), the value (i.e. a copy) is handed to the method. The method can do whatever it wants with the value it got; this will not modify either your x variable or the entry in your array, at all:

class Test {
  public static void main(String[] args) {
    int a = 5;
    int[] b = new int[10];
    increment(a);
    increment(b[0]);
    System.out.println(a); // prints 5. Never 6.
    System.out.println(b[0]); // prints 10. Never 11.
  }

  public static void increment(int v) {
    v = v + 1;
  }
}

That increment method does nothing and nothing you're going to do is ever going to change that. There is no code you can put in the body of increment that would make this work. A method that takes an int value cannot modify it in the caller.

What does that mean for non-primitives

It's complicated. The best way to think about objects is a humongous beach. It's vast, nearly infinite. And objects are treasure. new makes new treasure: A treasure chest pops into existence, but, the rule is, all treasure must always be buried. So you bury it immediately. Variables cannot possibly hold treasure chests, this whole system works on treasure maps. So:

List<String> list = new ArrayList<String>();

This says:

  1. new ArrayList: Create a treasure chest. It pops into existence and is buried. Objects have no name. Hence, this object is not called list. It has no name.
  2. List<String> list: Create a treasure map. It is small (just a piece of paper), and has a name. It's name is list. It is restricted: It can be blank, or, it is a map with 'X marks the spot' - but if it's that, there is necessarily a treasure chest buried at X, and that is a chest of the List<String> type.
  3. We draw a map to the buried treasure on the piece of paper called list. (that's what the = does).

Now, know that [] and . are java for 'follow the map, get out a shovel, and dig'.

So:

list.add("Hi");

Says: Take the treasure map called list. Follow it and dig. Open the treasure chest. Open it, yell add at it, handing it a copy of a treasure map. Because "Hi"` is itself an expression of a non-primitive type, so it is a treasure map.

In contrast, = is 'wipe out a map and re-assign it'.

Hence:

int[] x = new int[1];
x[0] = 5;

foo(x);
System.out.println(x.length); // 1
System.out.println(x[0]); // 10

void foo(int[] arr) {
  arr[0] = 10;
  arr = new int[] {1, 2, 3};
}

Here foo is interesting. The arr[0] really is 'visible' to the caller. After all, [] is java-ese for 'follow the map, grab a shovel, an dig'. So, you have a map that leads to treasure, you make a copy of this map and hand me the map. I then follow my map (which is a copy of your map), dig, and do something to the box. If you then later also follow your map and dig, you see what I did (System.out.println(b[0]) also uses [], so, also follows the map and digs).

However, foo's arr = new int[] {1,2,3}; has no effect. Because = is 'wipe the map and draw a new map to a new treasure'. You wipe out your copy of my map. That has no effect on my map.

So how do I do primitives pass-by-ref?

You don't.

Java does not make it possible.

You can use an AtomicInteger or a new int[1] to do the job. Or make a class definition that captures the parameters and has setters, and pass one of those. That layer of indirection allows you to do it.

Wait.. Strings are references, so this means I need to make defensive copies??

No. If a class def makes it impossible to modify it, then it doesn't matter: You can hand out copies of your treasure map to valuable treasure if that treasure is made out of indestructible unobtainium. String has no methods whatsoever to modify it. things like .toLowerCase() does not modify the string; it makes a new string (new treasure) and returns it (returns a map to it, to be specific).