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>.
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?
Reveal 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.
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.
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; }
}- 1Line 1 declares the class and names two placeholders, T and S, separated by a comma.
- 2Lines 2 and 3 declare one field for each placeholder. Neither field has a concrete type yet.
- 3Lines 5 to 8 declare the constructor. Its name is Pair with no brackets, and its parameters are typed with T and S.
- 4Lines 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.
- 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.
Matching arguments to slots
Given Pair<Double,String> q = new Pair<>(3.5, "blue"), which placeholder does each argument fill?
- 1Read 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.
- 2Match 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.
- 3Match 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.
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.
Mistakes Beginners Make with Two Parameters
Common mistake Repeating the type list on the constructor
What it looks likepublic Pair<T,S>(T first, S second) { ... }Why it failsThe brackets belong to the class header. The constructor is named without type parameters, just as every method is.
Do this insteadpublic Pair(T first, S second) { ... }Common mistake Assuming both placeholders must hold the same type
What it looks likePair<String,String> is treated as the only valid form because the names look alike.
Why it failsT 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 insteadBind 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 likeReading Pair<String,Color> and passing a Color first, expecting it to fill T.
Why it failsEach type argument binds to its placeholder by position, so the first type argument always governs the first slot.
Do this insteadRead the type argument list left to right, then check each constructor argument against the slot in the same position.
Checking Your Slot Matching
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
- 1A 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>.
- 2The bracketed list belongs to the whole class. The constructor and methods are named without type parameters.
- 3Inside the class body, T and S act as placeholders that are replaced by real types when an object is created.
- 4Type 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.