Java Arena
Step 5 of 8

Any type: a generic list

Playlist holds Strings, and nothing else. A list of whole numbers, or of objects of your own class, would be the same class again with another type in it. Module 44's type parameters are made for this: write the class once, as SimpleList<T>, with T wherever Playlist said String.

The array is the snag

The instance variable becomes private T[] values;, an array of T. But creating it, the obvious way, doesn't compile:

Main.java
public class Main {
    public static void main(String[] args) {
        SimpleList<String> songs = new SimpleList<>();
        System.out.println("A new list");
    }
}
SimpleList.java
public class SimpleList<T> {
    private T[] values;
    private int size;

    public SimpleList() {
        this.values = new T[4];
        this.size = 0;
    }
}
What javac prints
SimpleList.java:6: error: generic array creation
        this.values = new T[4];
                      ^
1 error

An array knows the type of its elements while the program runs: an array created as new String[4] stays an array of Strings. So to create an array, Java must know the element type at that moment. Type parameters don't last that long. javac uses T to check the code, and then the type argument is gone: the running program doesn't know whether a SimpleList was made for Strings or for Integers. So new T[4] can't be created.

The way around it is an array of Object, which can hold any object, since every class inherits from Object (module 34). Cast it to T[], and the rest of the class can treat it as an array of T:

this.values = (T[]) new Object[4];

Java's own ArrayList keeps an Object array inside as well.

javac warns about the cast

With that line, the class compiles and runs, but javac prints a note:

Note: SimpleList.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

It's a warning, not an error. "Unchecked" means that javac can't check this cast: it doesn't know what T will be, so it can't promise that the array really fits. Here the cast is safe, for a reason javac can't see: the array never leaves the class, and the only values that go into it come through add(T value), which takes nothing but T.

To tell javac that you've checked it, put the annotation @SuppressWarnings("unchecked") above each method or constructor that has such a cast. Like @Override, it's a note to javac. It turns the warning off for that one method, and changes nothing else. The code in this course uses it, so that javac stays quiet. If you leave it out, you'll see the note, and the program still runs.

SimpleList

Here's the whole class: Playlist with T in place of String, and a cast in the constructor and in grow, the two places that create an array:

Main.java
public class Main {
    public static void main(String[] args) {
        SimpleList<String> songs = new SimpleList<>();
        songs.add("Night Train");
        songs.add("Paper Boats");
        System.out.println(songs + " " + songs.contains("Paper Boats"));

        SimpleList<Integer> minutes = new SimpleList<>();
        int[] lengths = {4, 3, 5, 6, 2};
        for (int length : lengths) {
            minutes.add(length);
        }
        int total = 0;
        for (int i = 0; i < minutes.size(); i++) {
            total += minutes.get(i);
        }
        System.out.println(minutes + ", " + total + " minutes in all");

        minutes.remove(1);
        minutes.remove(Integer.valueOf(6));
        System.out.println(minutes);
    }
}
SimpleList.java
public class SimpleList<T> {
    private T[] values;
    private int size;

    @SuppressWarnings("unchecked")
    public SimpleList() {
        this.values = (T[]) new Object[4];
        this.size = 0;
    }

    public void add(T value) {
        if (this.size == this.values.length) {
            grow();
        }
        this.values[this.size] = value;
        this.size++;
    }

    @SuppressWarnings("unchecked")
    private void grow() {
        T[] bigger = (T[]) new Object[this.values.length * 2];
        for (int i = 0; i < this.size; i++) {
            bigger[i] = this.values[i];
        }
        this.values = bigger;
    }

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

    public T get(int index) {
        if (index < 0 || index >= this.size) {
            throw new IndexOutOfBoundsException("Index " + index + " out of bounds for size " + this.size);
        }
        return this.values[index];
    }

    public int indexOf(T value) {
        for (int i = 0; i < this.size; i++) {
            if (this.values[i].equals(value)) {
                return i;
            }
        }
        return -1;
    }

    public boolean contains(T value) {
        return indexOf(value) >= 0;
    }

    public T remove(int index) {
        T removed = get(index);
        for (int i = index; i < this.size - 1; i++) {
            this.values[i] = this.values[i + 1];
        }
        this.values[this.size - 1] = null;
        this.size--;
        return removed;
    }

    public boolean remove(T value) {
        int index = indexOf(value);
        if (index < 0) {
            return false;
        }
        remove(index);
        return true;
    }

    @Override
    public String toString() {
        String text = "[";
        for (int i = 0; i < this.size; i++) {
            if (i > 0) {
                text += ", ";
            }
            text += this.values[i];
        }
        return text + "]";
    }
}
Output
[Night Train, Paper Boats] true
[4, 3, 5, 6, 2], 20 minutes in all
[4, 5, 2]
  • One class, two lists: SimpleList<String> and SimpleList<Integer>. For each of them, javac reads every T as the type argument, so minutes.get(i) gives an Integer, and total += minutes.get(i); needs no cast.
  • indexOf compares with equals, which every object has, so the search works for any type. Integer's equals compares the numbers. For a class of your own, write an equals (module 32): without one, equals is true only for the very same object, and contains can't find an equal copy.
  • toString adds each value to the text, which calls the value's own toString.
  • The type argument must be a reference type, not a primitive type, as module 44 showed: SimpleList<Integer>, not SimpleList<int>. Java turns 4 into an Integer when it goes in, and back into an int when it comes out.
  • In a SimpleList<Integer>, remove(1) removes the value at index 1, because 1 is an int and remove(int index) takes an int as it is. remove(Integer.valueOf(6)) gives an Integer, which matches remove(T value). It's the same rule as for ArrayList in module 14, and now you can see where it comes from: the two methods in your own class.

Your task

Challenge 1 of 3

A market stall keeps its products and their prices in two lists of one generic class. Fill in the three blanks of SimpleList.java: the type parameter in the class header, the class of the array that's created, and the return type of get. The program prints:

pears: 6.5 in all

Challenge 2

SimpleList.java is the class from this step. A list of Integers has two remove methods that both seem to fit. What does this program print?

Challenge 3

A card game keeps a discard pile: each card goes on top, and a player can take the top card, the one put there last. In Pile.java, write the generic class Pile<T>, on top of an array of its own, with:

  • a constructor with no parameters, which creates the array with room for 4 values, as (T[]) new Object[4]
  • put(T value), which puts a value on top; when the array is full, it first moves the values into an array twice as long
  • take(), which removes the top value from the pile and returns it. On an empty pile, it throws an IllegalStateException with the message The pile is empty.
  • size(), which returns the number of values in the pile

No ArrayList: the top of the pile is simply the last used place of the array. The check runs:

Pile<String> discards = new Pile<>();
discards.put("7 of hearts");
discards.put("queen of spades");
discards.put("2 of clubs");
System.out.println(discards.take());
System.out.println(discards.take() + ", " + discards.size() + " left");

and expects:

2 of clubs
queen of spades, 1 left