Java Arena
Step 2 of 9

A list as an instance variable

A class can keep a list in an instance variable, just as it keeps a String or an int. Here a recipe has a name and a list of ingredients:

Main.java
public class Main {
    public static void main(String[] args) {
        Recipe pancakes = new Recipe("Pancakes");
        pancakes.addIngredient("flour");
        pancakes.addIngredient("milk");
        pancakes.addIngredient("eggs");

        Recipe toast = new Recipe("Toast");
        toast.addIngredient("bread");
        toast.addIngredient("butter");

        System.out.println(pancakes.getName() + " needs " + pancakes.ingredientCount() + " ingredients");
        System.out.println(toast.getName() + " needs " + toast.ingredientCount() + " ingredients");
    }
}
Recipe.java
import java.util.ArrayList;

public class Recipe {
    private String name;
    private ArrayList<String> ingredients;

    public Recipe(String name) {
        this.name = name;
        this.ingredients = new ArrayList<>();
    }

    public void addIngredient(String ingredient) {
        this.ingredients.add(ingredient);
    }

    public int ingredientCount() {
        return this.ingredients.size();
    }

    public String getName() {
        return this.name;
    }
}
Output
Pancakes needs 3 ingredients
Toast needs 2 ingredients
  • import java.util.ArrayList; is at the top of Recipe.java, because Recipe uses ArrayList. Each file imports what it uses. Main.java needs no import here: main never writes ArrayList.
  • private ArrayList<String> ingredients; declares the instance variable. Like the others, it's private, so code outside Recipe can't reach the list.
  • The constructor makes the list: this.ingredients = new ArrayList<>();. The constructor runs once for each new Recipe, so each recipe gets a new, empty list of its own. The pancakes' three ingredients never mix with the toast's two.
  • The methods use the list through this.ingredients, like any other instance variable. addIngredient passes the ingredient on to the list's add, and ingredientCount returns the list's size().

main never touches the list itself: it asks the recipe. So the recipe's methods decide what can be done with its ingredients. This Recipe can add and count them, and nothing else. There's no way to remove an ingredient, because Recipe has no method for that.

Forgetting to make the list

Declaring the instance variable doesn't make a list. private ArrayList<String> ingredients; only says what the variable can refer to, and an instance variable of a class type is null until something gives it an object (module 22). If the constructor leaves out new ArrayList<>(), the first add crashes:

Main.java
public class Main {
    public static void main(String[] args) {
        Recipe salad = new Recipe("Salad");
        salad.addIngredient("lettuce");
        System.out.println(salad.ingredientCount());
    }
}
Recipe.java
import java.util.ArrayList;

public class Recipe {
    private String name;
    private ArrayList<String> ingredients;

    public Recipe(String name) {
        this.name = name;
    }

    public void addIngredient(String ingredient) {
        this.ingredients.add(ingredient);
    }

    public int ingredientCount() {
        return this.ingredients.size();
    }
}
The program crashes with
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.util.ArrayList.add(Object)" because "this.ingredients" is null
	at Recipe.addIngredient(Recipe.java:12)
	at Main.main(Main.java:4)

The message names the instance variable that was null, this.ingredients. The first at line says where it happened: in addIngredient, line 12 of Recipe.java. The line below says who called it: main, on line 4. The fix is in neither place, though. It's the missing line in the constructor, where the recipe should have gotten its list.

Your task

Challenge 1 of 3

A photo album has a title and a list of photo names. In PhotoAlbum.java, add a private instance variable photos, a list of Strings, and make a new, empty list for it in the constructor. Then write two methods: add(String photo) adds the photo to the list, and count() returns how many photos the album holds. The check runs:

PhotoAlbum trip = new PhotoAlbum("Summer trip");
trip.add("beach.jpg");
trip.add("tent.jpg");
PhotoAlbum winter = new PhotoAlbum("Winter");
System.out.println(trip.getTitle() + ": " + trip.count());
System.out.println(winter.getTitle() + ": " + winter.count());

and expects:

Summer trip: 2
Winter: 0

Challenge 2

Now a whole class of your own. Mailbox.java is empty: write the class Mailbox in it, from the first line. A mailbox has an owner and a list of letters, which are Strings.

  • The constructor Mailbox(String owner) keeps the owner and makes a new, empty list for the letters.
  • deliver(String letter) adds a letter to the list.
  • count() returns how many letters there are.
  • toString() returns the owner, a colon and the list, like Ana: [bill, magazine]. Joining the list to a String writes it out in brackets, just as printing it does.

The check runs:

Mailbox ana = new Mailbox("Ana");
Mailbox leo = new Mailbox("Leo");
ana.deliver("bill");
leo.deliver("postcard");
ana.deliver("magazine");
System.out.println(ana.count() + " " + leo.count());
System.out.println(ana);
System.out.println(leo);

and expects:

2 1
Ana: [bill, magazine]
Leo: [postcard]

Challenge 3

This program crashes with a NullPointerException. Read the message, find the cause and fix it in Choir.java. Then add a method isEmpty() to Choir: it returns true when nobody has joined the choir yet. Main.java is ready, and when both are done it prints:

true
false
2