-2

Is arrays passed by reference to methods in java? If so, why the following code prints 1,2,3,4,5 instead of 5,4,3,2,1 ?

public class Test {
   public static void main(String[] args) {
      int[] oldList = {1, 2, 3, 4, 5};
      reverse(oldList);
      for (int i = 0; i < oldList.length; i++) {
          System.out.print(oldList[i] + " ");
      }
   }

   public static void reverse(int[] list) {
      int[] newList = new int[list.length];
      for (int i = 0; i < list.length; i++) {
          newList[i] = list[list.length - 1 - i];
      }
      list = newList;
   }
}

enter image description here

Edwin Buck
  • 69,361
  • 7
  • 100
  • 138
CHUCHU YAO
  • 11
  • 1

2 Answers2

2

Reference variables, not array variables

Your oldList, list, and newList variables are all references, references that point to an array floating around in memory someplace. The references are not in themselves an array. Each of those three variables could be made to refer to a different array with a call such as oldList = { 97, 98 , 99 } ;. Assigning a different array to a reference variable does not affect the content of the originally-assigned array.

In other words, oldList, list, and newList are not “array variables”, they are “reference variables” that happen to point to some array or another.

Conceptually, you can think of it as shown in this diagram.

diagram of the three reference variables pointing to arrays

So your line:

list = newList;

…has no beneficial effect. It does indeed re-assign the argument reference variable list from pointing to the blue first array to pointing to the salmon/yellow second array. But immediately after that re-assignment, the method ends. As the method ends, the list variable goes out of scope, and disappears. That leaves the second array dangling out in memory in limbo, eventually to be garbage collected as no more references remain pointing to it. So no lasting effect. That re-assignment of list has no effect on oldList, contrary to your apparent expectation.

The upshot is that after calling reverse(oldList);, you ended up with the same state as where you started, a reference variable oldList pointing to the original array.

diagram of a single reference <code>oldList</code> variable pointing to original array

To get your desired results you could choose either of at least two other approaches:

  • In the reverse method, make a new sorted array, and then when done, write those same sorted values back into the passed array. Let the new sorted array go out of scope when the method ends, to become a candidate for eventual garbage collection.
  • Define the reverse method to return an array, rather than return void. The calling method can then re-assign its own reference variable to point to the returned array.
    oldList = reverse( oldList ) ;

I recommend the second approach, generally-speaking. The first alternative is usually a bad idea; messing around with passed values tends to lead to confusing logic. Usually better to give results back to the calling method, and let the calling method decide what to do with those results. This approach promotes loose coupling.

All of this is a fundamental piece of understanding Java and object-oriented programming. It will be tricky to grasp at first. Keep at it until it suddenly it snaps into place in your mind, then becoming second-nature, a barely conscious part of your thinking while programming.


By the way, while irrelevant to your direct question about learning the intricacies of Java and OOP, I want to mention that in practice for real-work I would accomplish your task with different code. I would use the Java Collections framework such as List interface and ArrayList concrete class rather than simple arrays, with Integer class rather than int primitive, assisted by auto-boxing and the new List.of convenience method.

List < Integer > numbers = new ArrayList <>( List.of( 1 , 2 , 3 , 4 , 5 ) );
Collections.reverse( numbers );

The List.of result is immutable, and cannot be re-ordered. So I feed its elements to an modifiable ArrayList object’s constructor. The Collections.reverse method modifies the ArrayList, unlike with simple arrays.

Dump to console.

System.out.println( numbers );

[5, 4, 3, 2, 1]

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • And why the yellow array is 12345? In the reverse method, it creates a newList, after the loop newList[i] = list[list.length - 1 - i];, the newList will be 54321, right? – CHUCHU YAO Apr 18 '18 at 05:35
  • Oops, yes, second array should be 54321. I’ll fix tomorrow. – Basil Bourque Apr 18 '18 at 05:53
  • Ah, I see, so list now reference to newList which is 54321. But what will the method return to its caller reverse(oldList)? I thought after reverse, oldList will reference to newList, so that oldList becomes 54321. Now I know I was wrong, but I'm stuck here, it's so tricky.. – CHUCHU YAO Apr 18 '18 at 06:38
  • The caller of `reverse` gets nothing back because you declared the method as void, meaning nothing is to be returned. Solution: (1) Change `void` to `int[]`, (2) change the last line `list = newList ;` to `return newList ;`. – Basil Bourque Apr 18 '18 at 07:04
  • Thanks Basil! The diagram you draw really helps!! – CHUCHU YAO Apr 18 '18 at 17:48
  • @BasilBourque Nice drawing, what tool did you use? I'm always on the lookout for better ones. – Edwin Buck Apr 18 '18 at 19:02
  • @EdwinBuck For such drawings I use [*OmniGraffle*](https://www.omnigroup.com/omnigraffle) by [The Omni Group](https://www.omnigroup.com/about), one of the finest purveyors of macOS apps. – Basil Bourque Apr 18 '18 at 20:02
0

Because Java is pass-by-value.

The value of the "reference" to the array is copied into the local variable list. When you reset that local variable list you do no more than reset the reference copied into list. You don't reset the reference being held by oldList.

so, to make it clear, I'll show a second example

public static void main(String[] args) {
   int five = 5;
   doSomething(five);
   System.out.println("five is " + five);
}

public void doSomething(int number) {
   number = number - 3; // This overwrites the number which is scoped
                        // to this block.  Java passes by value, so
                        // the variable five is unaffected.
}

While this seems obvious for integers, it also holds for references to objects.

public static void main(String[] args) {
  my name = "Edwin"; // a String object.
  doThing(name);
  System.out.println(name); // guaranteed to print "Edwin"
}

public void doThing(String value) {
   value = "Not Edwin";  // impacts value, but only in this block
                         // which means name is unaffected
}
Edwin Buck
  • 69,361
  • 7
  • 100
  • 138
  • But why if I pass an array with two integers to a user-defined swap method, it can actually swap the two numbers? – CHUCHU YAO Apr 18 '18 at 02:56
  • @CHUCHUYAO Because you are not replacing the array, you are rearranging the contents of an array. The array reference is copied, so you can't alter the array reference to that of another array; however, if the array permits you access to it's internals (as they all do), you can easily rearrange the passed array. You just can't substitute it. If the test example above didn't create a new array and assign it to `list` but rather rearranged the internals of `list` it would have reversed the array. – Edwin Buck Apr 18 '18 at 02:59
  • Thank you so much Edwin! After reading your explanation again today, I think now I get the idea. – CHUCHU YAO Apr 18 '18 at 17:48