1

I'm writing some JAVA assignment I got in school for a few days now, and I ran into something quite weird (well, at least for me it is...).
Since the question has nothing to do with my project in particular, I wrote some code that presents the behaviour I wanted to ask about, so please ignore any problems you might encounter in the following code, that don't relate to this specific issue.
Consider the following class:

package test;

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

public class Car {

    List<Doors> doors;
    int numOfDoors;

    public Car(int numOfDoors) {
        this.numOfDoors=numOfDoors;
    }

    public void prepare() {
        run(doors);
    }

    public void run(List<Doors> listOfDoors) {
        listOfDoors=new ArrayList<Doors>(numOfDoors);
    }

    public List<Doors> getDoors() {
        return doors;
    }
}

And this:

package test;

import java.util.List;

public class TestDrive {

    public static void main(String[] args) {

        Car car=new Car(5);
        car.prepare();
        List<Doors> listOfDoors=car.getDoors();

        if (listOfDoors==null) {
            System.out.println("This is not the desired behaviour.");
        }
        else {
            System.out.println("This is the desired behaviour.");
        }
    }
} 

I agree, it's kinda stupid and has no point, but again - I wrote it only to satisfy my curiosity.

Now, as you might have guessed, the output is "This is not the desired behaviour.", meaning, the field "doors" holds a null pointer, even though it was assigned with a new object in "run()" method. so my question is why? why is it null?
I mean, I know that creating a local variable - may it be a primitive, an object or a reference to an object - will result in loosing it right when we leave the method's scope, but that is not exactly the case here, since there IS a live reference to that newly created object (doors), then why would JAVA destroy it?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
so.very.tired
  • 2,958
  • 4
  • 41
  • 69

8 Answers8

2

Let's analyse it step by step.

Car car=new Car(5);
car.prepare();

You create a new car, and you expect it to have a list of 5 doors, after the prepare. Let's take a look now at what happens during prepare.

public void prepare() {
    run(doors);
}

public void run(List<Doors> listOfDoors) {
    listOfDoors=new ArrayList<Doors>(numOfDoors);
}

The new ArrayList is being assigned to the local variable listOfDoors. When you write run(doors) you're not passing a pointer to the variable doors, as you might expect in a C context, for instance. You're passing a null (because doors insn't yet initialized) to the run method.

When run starts, we have

List<Doors> listOfDoors = null;

This is a result of passing the null reference upon invocation. Then you assign a new list to this local variable, only to be destroyed when the method terminates. As you can see, nothing was assigned to doors, leading to the unexpected behaviour.

To solve this, remove the run method and rewrite your prepare method.

public void prepare() {
    doors = new ArrayList<Doors>(numOfDoors);
}

With this, you should get the expected behaviour.

afsantos
  • 5,178
  • 4
  • 30
  • 54
  • 1
    Ohh... OK, so basically this is the result of Java passing references **by value**... – so.very.tired Dec 29 '13 at 19:46
  • 1
    @so.very.tired Exactly, the reference is passed by value. That is, the *address* that `doors` stores is copied and then passed, not the *address* to the `doors` variable itself. – afsantos Dec 29 '13 at 21:14
0

There are two things:

  • passing parametrs as reference

  • passing parametrs as value

In Java you can only pass parametrs as value - that means method run gets copy of doors reference (yes - it may be confusing). So now you have got reference doors and copy of this reference named - listOfDoors. But it refers to nothing yet. So when you write

listOfDors = new ArrayList<Doors>();

now listOfDoors references to arraylist of doors, but variable doors still reference to nothing.

So by the way - if you initialized your doors object before passing copy of that reference, you could write:

listOfDoors.add(new Door());

and it would be the same as

doors.add(new Door());

Despite those would be two different references, they would refer to the same object.

Andy
  • 1,035
  • 1
  • 12
  • 28
0

The problem is here:

public void run(List<Doors> listOfDoors) {
    listOfDoors = new ArrayList<Doors>(numOfDoors);
}

It should be:

public void run(List<Doors> listOfDoors) {
    doors = new ArrayList<Doors>(numOfDoors);
}

Read about parameter passing.

Community
  • 1
  • 1
Christian Tapia
  • 33,620
  • 7
  • 56
  • 73
0

You need to initialize Doors in Car constructor after checking the number of doors passed to the constructor. Also, run() needs to access this.doors instead of accessing local doors (method argument).

aviad
  • 8,229
  • 9
  • 50
  • 98
0

When you assign listOfDoors=new ArrayList<Doors>(numOfDoors); in run, you are not changing doors, because it is a different reference. Calling run(doors) creates a new reference to the object which doors refers to, which you are then setting and doing nothing with (this reference loses scope when run exits). The doors reference is never set to the object you're creating in run.

Mark H
  • 13,797
  • 4
  • 31
  • 45
0

The method run may have your existing doors as the parameter, but in the function the parameter doors gets overwritten with a new list (it is not the same object with a different content).

The original doors object therefore is still null.

ljgw
  • 2,751
  • 1
  • 20
  • 39
0

Java is not pass by reference. so

public void run(List<Doors> listOfDoors) {
    listOfDoors=new ArrayList<Doors>(numOfDoors);
}

Does not update the same list that is passed in as the variable doors in run(doors). Instead the run method assigns to a new reference so your variable doors is never set.

This is also why the usual swap method does not work in java

dkatzel
  • 31,188
  • 3
  • 63
  • 67
0

You are thinking of assignment in the wrong direction, by passing door to doors to run you are having a new variable / reference which is declared as the argument of that method and which is local to that it, you then initialize it and it live only as long as the method executes. The variable doors remains unchanged. Its not like in C or C++ where you can pass a pointer and assign a value to the object it is pointing to.

You need to initialize doors, a good place would be in the constructor:

public Car(int numOfDoors) {
    this.numOfDoors=numOfDoors;
    this.doors = new ArrayList<>(this.numOfDoors);
}

And there is no need - at least if you intention is that the method run works on the member variable doors - to pass a reference to it to that method, just write:

public void run() {
    // user this.doors here
}
A4L
  • 17,353
  • 6
  • 49
  • 70