Concepts / Generic Classes with One Type Parameter

Generic Classes with One Type Parameter

A generic class with two type parameters declares both names in one angle-bracket list after the class name, separated by a comma, as in Pair<T,S>.

  • Programming

A Box That Holds Two Kinds of Values

Sometimes a single object needs to hold two values that do not have to be the same kind of thing. Imagine a small container that stores a name and a favorite color together, or a number and a label together. A generic class solves this by leaving the kinds of values open until the object is actually created. The class is written once, and each object made from it can fill its two spots with different types.

The source uses a class called Pair to illustrate this. Pair holds two objects that may have different types. Before reading on, try to predict the answer to the question below, then check it against the reveal.

What do you think happens?

In the declaration Pair<String,Color>, which real type does the placeholder T stand for?

  • String

  • Color

  • Both String and Color

  • Neither, because T is a fixed name

Reveal answer
Answer:

String

The declaration binds T to String and S to Color. Each type argument binds to its placeholder by position, so the first argument, String, fills T, and the second argument, Color, fills S.

Reading the Class Header

A generic class is declared with its type parameters in angle brackets directly after the class name. When the class needs more than one type, the parameter names are separated by commas inside the same brackets. The Pair class uses the names T and S, so its header introduces two placeholders for two types that the class can hold. The standard library follows the same pattern: the interface Map is declared with the two parameters K and V.

The best way to read the header is as one unit with two parts. The name Pair identifies the class. The bracketed list that follows introduces the two placeholders, and both placeholders belong to the whole class rather than to any single member. Whatever types are chosen for them when an object is created will apply everywhere inside that object.

Pairclass name<T,S>type parameters for thewhole classPair(...)constructor, no type list
Where does the type parameter list <T,S> appear, and why does it attach to Pair but not to the constructor inside the class?

Placeholders Inside the Class Body

Inside the class body, the placeholders take the place of real types. The Pair class declares a field of type T and a field of type S, and its constructor accepts a value for each. The class body is written against the names T and S as if they were the names of specific, actual types, even though no concrete type exists yet. The placeholders are only useful because they can be replaced when an object is created.

Code walkthrough
public class Pair<T,S> {
    private T first;
    private S second;

    public Pair(T first, S second) {
        this.first = first;
        this.second = second;
    }

    public T getFirst() { return first; }
    public S getSecond() { return second; }
}
  1. 1
    Line 1 declares the class and names two placeholders, T and S, separated by a comma.
  2. 2
    Lines 2 and 3 declare one field for each placeholder. Neither field has a concrete type yet.
  3. 3
    Lines 5 to 8 declare the constructor. Its name is Pair with no brackets, and its parameters are typed with T and S.
  4. 4
    Lines 10 and 11 return the values through methods whose return types are the placeholders.
  • The header Pair<T,S> introduces two placeholders for the whole class.
  • The field first has the placeholder type T, and the field second has the placeholder type S.
  • The constructor is named Pair with no angle brackets, and it takes one value for each placeholder.
  • Each getter returns the type of the placeholder that governs its field.

Binding Type Arguments to Slots

When you declare a variable, you supply a type argument for each placeholder, in order. The declaration Pair<String,Color> binds T to String and S to Color for that variable. A second declaration, such as one that binds both placeholders to Double, uses the same class but produces a differently typed object. The diamond operator on the right side of the assignment lets the constructor call take its type arguments from the declared variable type.

TStringSColor
In Pair<String,Color>, which argument fills T and which fills S, and how do the constructor's arguments line up with those slots?
java
Notes on the code
  • The declared type Pair<String,Color> binds T to String and S to Color.
  • The diamond operator <> lets the constructor call take those same type arguments from the declared type.
  • The first constructor argument, Ann, lines up with T, and the second, favoriteColor, lines up with S.
Worked example

Matching arguments to slots

Given Pair<Double,String> q = new Pair<>(3.5, "blue"), which placeholder does each argument fill?

  1. 1
    Read the type arguments in order

    The first type argument is Double and the second is String. By position, Double binds to T and String binds to S.

  2. 2
    Match the first constructor argument

    The first constructor argument, 3.5, sits in the first position, so it fills the slot governed by T. Its type must be Double.

  3. 3
    Match the second constructor argument

    The second constructor argument, blue, sits in the second position, so it fills the slot governed by S. Its type must be String.

Result

The value 3.5 fills T as a Double, and the value blue fills S as a String. The two placeholders are independent, so the slots do not have to hold the same type.

Finding the Slot Behind a Type Mismatch

When a generic object behaves differently from what you expected, the fastest way to find the cause is to ask which placeholder governs the position that failed. Start from the declared type argument list, identify the slot involved, and compare the value you supplied in that position against the type bound to that slot. Because the two placeholders are independent, the problem is usually local to one slot.

Suppose you write Pair<String,Color> p = new Pair<>("Ann", 42). The first argument is a String, which fits T. The second argument is the number 42, which does not fit S, because S is bound to Color. The failure is in the second position, so the slot to check is S, not T. Changing the first argument would not help.

Argument 1: Anngoverned by T, StringArgument 2: 42governed by S, Color
When new Pair<>("Ann", 42) is assigned to Pair<String,Color>, which slot governs the failing position, and why does that slot reject the value?

Mistakes Beginners Make with Two Parameters

  • Common mistake

    Repeating the type list on the constructor

    What it looks like
    public Pair<T,S>(T first, S second) { ... }
    Why it fails

    The brackets belong to the class header. The constructor is named without type parameters, just as every method is.

    Do this instead
    public Pair(T first, S second) { ... }
  • Common mistake

    Assuming both placeholders must hold the same type

    What it looks like

    Pair<String,String> is treated as the only valid form because the names look alike.

    Why it fails

    T and S are independent placeholders. The source shows Pair<String,Color> and a separate declaration that binds both to Double, so each slot takes its own type.

    Do this instead

    Bind each placeholder to whatever type that slot needs, such as Pair<String,Color>.

  • Common mistake

    Matching arguments to slots by guessing instead of by position

    What it looks like

    Reading Pair<String,Color> and passing a Color first, expecting it to fill T.

    Why it fails

    Each type argument binds to its placeholder by position, so the first type argument always governs the first slot.

    Do this instead

    Read the type argument list left to right, then check each constructor argument against the slot in the same position.

Checking Your Slot Matching

EASY

Consider the declaration Pair<Integer,String> r = new Pair<>(7, "seven"). Which placeholder does the value 7 fill, and what type must the second constructor argument have? Then predict what would go wrong if you wrote new Pair<>("seven", 7) instead.

Hints
  • Read the type argument list left to right: the first type argument binds to T and the second to S.
  • The value 7 is in the first position, so it must match the type bound to T.
  • Swapping the arguments places a String in the first position and an Integer in the second.

Key Takeaways

  1. 1
    A generic class with two type parameters lists both names in one angle-bracket list after the class name, separated by a comma, as in Pair<T,S>.
  2. 2
    The bracketed list belongs to the whole class. The constructor and methods are named without type parameters.
  3. 3
    Inside the class body, T and S act as placeholders that are replaced by real types when an object is created.
  4. 4
    Type arguments bind to placeholders by position, so you can match each argument to its slot and check which slot a failing value belongs to.

At a glance

  • A generic class with two type parameters lists both names in one angle-bracket list after the class name, separated by a comma, as in Pair<T,S>.
  • The type parameter list belongs to the class header, and constructors and methods are named without it.
  • Inside the class body, T and S are placeholders for types supplied at object creation.
  • Type arguments bind to placeholders by position, so a mismatch usually traces back to one slot.