Java Arena
Step 1 of 5

Why type parameters

Since module 13 you've written types like ArrayList<String> and ArrayList<Integer>, and since module 31 HashMap<String, Integer>. ArrayList is one class, written once by Java's developers. Yet each list knows the type of its values: get on an ArrayList<String> gives a String, get on an ArrayList<Integer> gives an Integer, and adding a number to a list of Strings doesn't compile. The type in the angle brackets makes that work. This module shows how to write classes like that yourself.

A box for one type

A storeroom keeps things in boxes, one thing per box. Here's a class for a box that holds a String:

public class StringBox {
    private String content;

    public StringBox(String content) {
        this.content = content;
    }

    public String getContent() {
        return this.content;
    }
}

A box for a number would be the same class again, with Integer in place of String in three places. A box for a Book, once more. The code would be the same each time, and only the type would change.

Why not Object?

Every class inherits from Object (module 34), so a box whose content is an Object takes anything. But then what comes out is just an Object, and Java no longer knows what kind:

Main.java
public class Main {
    public static void main(String[] args) {
        ObjectBox box = new ObjectBox("tent pegs");
        String content = box.getContent();
        System.out.println(content.toUpperCase());
    }
}
ObjectBox.java
public class ObjectBox {
    private Object content;

    public ObjectBox(Object content) {
        this.content = content;
    }

    public Object getContent() {
        return this.content;
    }

    public void setContent(Object content) {
        this.content = content;
    }
}
What javac prints
Main.java:4: error: incompatible types: Object cannot be converted to String
        String content = box.getContent();
                                       ^
1 error

You know there's a String in the box, but the compiler only knows that getContent() returns an Object. A cast, (String) box.getContent(), would make it compile (module 36). But nothing stops other code from putting a number into that box, and then the cast fails while the program runs, with a ClassCastException.

A type parameter

The way out is to let the code that creates a box say what goes in it. The class gets a type parameter, written in angle brackets after its name:

Main.java
public class Main {
    public static void main(String[] args) {
        Box<String> pegs = new Box<String>("tent pegs");
        System.out.println(pegs.getContent().toUpperCase());

        Box<Integer> coins = new Box<Integer>(340);
        int total = coins.getContent() + 25;
        System.out.println(total);
    }
}
Box.java
public class Box<T> {
    private T content;

    public Box(T content) {
        this.content = content;
    }

    public T getContent() {
        return this.content;
    }

    public void setContent(T content) {
        this.content = content;
    }
}
Output
TENT PEGS
365
  • public class Box<T> says that Box has a type parameter called T. T stands for a type that isn't decided yet: each box decides it when it's created.
  • Inside the class, T is used like any other type: for the instance variable, private T content;, for the constructor's parameter, as the return type of getContent, and as the type of setContent's parameter.
  • Box<String> is a box whose T is String. For this box, getContent() returns a String, so toUpperCase() works without a cast. coins is a Box<Integer>, so its getContent() returns an Integer, which can be added to.
  • The constructor's header is public Box(T content). The <T> comes only after the class's name in the class header, never after the constructor's name.

You can picture it like this: when Java checks code that uses a Box<String>, it reads every T in Box as String. One class, written once, works for any type. That's how ArrayList is written too: its header begins public class ArrayList<E>, and its get returns an E.

Naming a type parameter

A type parameter can have any name, but the custom is a single capital letter, so that it's easy to tell from a class's name: T for "type", E for "element" (Java's lists use it), and K and V for "key" and "value" (HashMap uses them).

Your task

Challenge 1 of 3

A learning app shows a word of the day and a number of the day, with one class for both. Fill in the three blanks so that Featured works for any type: the type parameter in the class header, the type of the instance variable, and the return type of get. The program prints:

Word of the day: lantern (7 letters)
Number of the day: 64, half of it is 32

Challenge 2

Two boxes from the storeroom both hold a seven: one as a String, the other as an Integer. Box is the class from this step. What does this program print?

Challenge 3

A sports club's trophies pass from holder to holder. The chess trophy goes to a player, given by name. The relay shield goes to a team, given by its number. In Trophy.java, write the class Trophy<T> with:

  • a constructor Trophy(String name, T holder): the trophy's name and its first holder
  • getHolder(), which returns the current holder
  • passTo(T newHolder), which makes newHolder the holder, and returns the holder before it
  • toString(), which returns the trophy's name, a colon and the holder, like Chess Cup: Ravi

The name is always a String; only the holder's type changes. The check runs:

Trophy<String> chess = new Trophy<String>("Chess Cup", "Ada");
String before = chess.passTo("Ravi");
System.out.println(before + " passed it on");
System.out.println(chess);
Trophy<Integer> relay = new Trophy<Integer>("Relay Shield", 4);
int old = relay.passTo(7);
System.out.println(old + relay.getHolder());
System.out.println(relay);

and expects:

Ada passed it on
Chess Cup: Ravi
11
Relay Shield: 7