1

When a call happens for the someVoid() method if I am not mistaken reference of list pass to it. After code adds, 2 value into list and size becomes 2 accordingly. Everything here is okay and understandable for me (please, correct me if I made mistake(s)). After someVoid() method changes reference of the list to null. The question is: Why the size of the list remain 2 after these operations? Thanks in advance.

public class Test {
          List<Integer> list;
        
          public Test() {
            this.list = new ArrayList<>();
            someVoid(list);
          }
        
          private void someVoid(List<Integer> list) {
            list.add(0);
            list.add(1);
            list = null;
          }
        
          public static void main(String[] args) {
            Test test = new Test();
            System.out.println("Size is: " + test.list.size());
          }
}
trincot
  • 317,000
  • 35
  • 244
  • 286
  • 1
    Does this answer your question? [Is Java "pass-by-reference" or "pass-by-value"?](https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value) – tgdavies Dec 30 '20 at 05:59
  • 2
    Why should setting a variable to `null` change the size of a `List`? On what basis were you expecting that? – Holger Jan 05 '21 at 17:09
  • I was a little confused by the passsing-by-reference. The local variable (method parameter ```list```) escaped my notice, that's why. – elvintaghizade14 Jan 07 '21 at 06:25

1 Answers1

5

Two references to the same List object

The list variable inside your someVoid method is a local variable, the name of the argument. That local list is a separate distinct variable from the class member field coincidentally named list. So when you say list = null ;, you are clearing that local variable. You did not clear the member field list. The Test object named test still carries its own reference (pointer) to the list.

diagram showing two references in code leading to the very same List object floating around in memory

A few lessons to learn here:

  • Watch your naming. Duplicating the same name in various contexts within a piece of code can lead to confusion. In ambiguous situations, consider appending something like Arg to your argument's name. For example: listArg. But better to use semantic names, with the clearest meaning in each context.
    • Speaking of naming, Test is a poor choice of a name for a class in Java. Unit-testing is common, and such a name is distracting at best and confusing at worst.
  • Mark your arguments final, which should have been the default in the original design of Java. Changing a passed argument is poor practice and entirely unnecessary. Changing that method signature to private void someVoid ( final List < Integer > list ) prompts a compiler alert an the line list = null; telling you that you are changing a passed argument variable. Such a change is illegal if marked final.
  • In ambiguous situations, use this. syntax. For example, this.list.add(0);. In olden days, I used this. on all my code for clarity. Using this. is less necessary nowadays because of the colorizing features in modern IDEs.
  • Passing a primitive (int, float, boolean, and such) in Java passes a copy of the content of that variable. Passing an object in Java is actually passing a copy of a reference to an object, not the object itself. Your code has two references to the same List object, one reference is held in the class member variable named list and another reference to the very same List object is in the local variable list within your method.

Example code

Let's modify your code to accomplish your goal: In a method, add elements to a list stored on a class member field, then eliminate that list entirely. This work makes no sense, but works as a demo.

package work.basil.example;

import java.util.ArrayList;
import java.util.List;

public class Lister
{
    List < Integer > numbers;

    public Lister ( )
    {
        this.numbers = new ArrayList <>();
        this.sillyMethodToAddToListAndThenEliminateList();
    }

    private void sillyMethodToAddToListAndThenEliminateList ( )
    {
        this.numbers.add( 0 );
        this.numbers.add( 1 );
        this.numbers = null;   // Clearing the *local* reference to the `List` object in memory. The class member field continues to point to the `List` object. 
    }

    public static void main ( String[] args )
    {
        Lister app = new Lister();
        System.out.println( "Size is: " + app.numbers.size() );
    }
}

When run, we get a null-pointer exception. This makes sense because we removed the list we had instantiated. So at the point of the println, the class member field references no object, references nothing, is considered null.

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.util.List.size()" because "app.numbers" is null at work.basil.example.Lister.main(Lister.java:26)

Shallow copy

In real work, when handing off a collection, we often want to hand off a shallow copy of that collection. Some objects inside (actually, references to the same objects are inside). But then if the user changes the collection, adding/deleting/sorting, they are not disturbing our original collection.

Likewise, when receiving a collection you may want to make a shallow copy. This is in case the calling programmer did not think to pass a copy rather than the original. Some would argue, correctly I would say, that it is the responsibility of the calling programmer to get their data straight. The programmer of the method being called should not have to predict the failure points of the calling programmer. But you practicality may win out. So I show both the calling and called programmers making defensive copies. Plus, in this example, the calling programmer passes a non-modifiable list, so the called programmer must make a copy in order to add elements.

Tip: List.of and List.copyOf make non-modifiable lists.

package work.basil.example;

import java.util.ArrayList;
import java.util.List;

public class Lister
{
    List < Integer > numbers;

    public Lister ( )
    {
        this.numbers = new ArrayList <>();
        this.numbers.add( 42 );
        this.sendReport( List.copyOf( this.numbers ) );  // Share a shallow copy of collection.
    }

    private void sendReport ( final List < Integer > fodderForReport )
    {
        List < Integer > data = new ArrayList <>( fodderForReport ); // Make defensive and modifiable copy of passed collection.
        data.add( 0 );
        data.add( 1 );
        String report = data.toString();
        System.out.println( "report = " + report );
        //  … send report
    }

    public static void main ( String[] args )
    {
        Lister app = new Lister();
        System.out.println( "Size is: " + app.numbers.size() + " | " + app.numbers );
    }
}

When run:

report = [42, 0, 1]

Size is: 1 | [42]

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154