Discussion 6: Generic Types

In today’s discussion, you’ll practice using and reasoning about generic types in Java. In particular, you’ll explore their connections to data structures and the subtyping rules that we learned about in recent lectures. You’ll see what problem(s) generics conceptually solve, and use them to implement some simple generic collection types.

Learning Outcomes

  1. Identify three scenarios where subtype substitution is permitted.
  2. Explain the benefits of leveraging polymorphism in object-oriented code.
  3. Implement a generic class or method with one or more generic type parameters. Use generic classes in client code.

Reminder: Discussion Guidelines

The work that you complete in discussion serves as a formative assessment tool; it offers the opportunity to assess your understanding of the material and for our course staff to get a “pulse” on how things are going, so we can make adjustments in future classes. Your grade in discussion is based on your attendance, active participation, and completion of that day’s activity. More information about the grading and expectations can be found in the syllabus.

Since discussion activities are not graded for correctness, we do not restrict what resources you may use to complete them, which include notes, books, unrestricted conversations with other students, internet searches, and the use of large language models or other generative AI. We advise you to be pragmatic about your use of these resources and think critically about whether they are enhancing your learning. Discussion activities are intended to serve as “strength training” for programming tasks we will expect on assignments and exams (and that you will encounter in future courses and careers), and their main benefit comes from thinking critically to “puzzle” them out.

Exercise 1: Other Types of Pairs
Alice is writing a program to construct and interpret scatterplots on a 2D graph. She needs a way to model the (integer) coordinates of a point on the scatterplot, so she decides to do this with a type of (mutable) collection called an IntPair. This type has a constructor that accepts two int arguments (the pair's first and second entries), as well as accessor and mutator methods for each entry. Some example code using Alice's IntPair type is shown below:
1
2
3
4
5
IntPair ip = new IntPair(1, 2);
int x = ip.first(); // Access 1st element, `x` initialized to 1.
int y = ip.second(); // Access wnd element, `y` initialized to 2.
ip.setFirst(3) // Reassigns 1st element, 1 -> 3 in `ip`
ip.setSecond(4) // Reassigns 2nd element, 2 -> 4 in `ip`
1
2
3
4
5
IntPair ip = new IntPair(1, 2);
int x = ip.first(); // Access 1st element, `x` initialized to 1.
int y = ip.second(); // Access wnd element, `y` initialized to 2.
ip.setFirst(3) // Reassigns 1st element, 1 -> 3 in `ip`
ip.setSecond(4) // Reassigns 2nd element, 2 -> 4 in `ip`
As she continues implementing her program, Alice realizes that she will need to represent the coordinates of points in between integer markings. She creates DoublePair, which behaves exactly like an IntPair, but for doubles.
(a)

Alice’s physicist friend Bob writes a lot of code performing calculations on his own ComplexNumber type. He’d like to use Alice’s pair classes to model complex coordinates within his simulations. Explain why he won’t be able to do this.

(b)

What shortcoming does this suggest in Alice’s approach to modeling pairs?

(c)

Rather than writing his own ComplexPair class, Bob decides to write a more general Pair class that can store anything in its two entries; its constructor accepts two Objects:

1
2
3
4
5
public class Pair {
  // fields
  public Pair(Object first, Object second) { ... }
  // `first()`, `second()`, `setFirst()`, `setSecond()` methods.
}
1
2
3
4
5
public class Pair {
  // fields
  public Pair(Object first, Object second) { ... }
  // `first()`, `second()`, `setFirst()`, `setSecond()` methods.
}

Bob reasons that he can pass in two ComplexNumbers as the constructor arguments (after all, Object is a supertype of ComplexNumber), so he’ll be able to use Pairs in his simulation. Moreover, Alice can take advantage of auto-boxing to use Bob’s Pair class to model pairs of Integers and Doubles.

Identify two issues with Bob’s approach.

Exercise 2: Generic Pairs
We can use generic type parameters to solve the problems that you identified in the previous exercise.
(a)

Write a generic Pair<T> class that models a pair whose entries both have type T. Your definition should provide the first(), second(), setFirst(), and setSecond() methods as described above. Make a note of all the different ways that your code utilized the generic type T parameter.

(We’d like you to submit your code written out with your responses. Writing this (relatively short) code by hand will help build muscle memory for upcoming exams. It can be challenging to get used to, since you can’t rely on IntelliJ’s linter to point out syntax errors.)

(b)

Sometimes, we may want the two entries of a pair to have different types. For example, when plotting in polar coordinates, we may want the first entry to have type Double to represent the radius, while the second entry has a custom type Angle to represent the azimuth.

Write an AsymPair class generic on two type parameters T1 and T2 modeling the types of its entries (that is, the class declaration should have the form class AsymPair<T1,T2>). Your definition should provide the first(), second(), setFirst(), and setSecond() methods as described above. Make sure you are careful about where you use each generic type.

(c)
The AsymPair class that you just wrote is a more general type than the Pair class. Use an inheritance relationship with the AsymPair class to redefine the Pair class (while still enforcing that its coordinates both have type T). You should be able to accomplish this with minimal code.
Exercise 3: Generics and Subtypes
For the rest of the discussion, you'll spend some time thinking about how generic types interact with the subtype substitution rules that we discussed in previous weeks. Suppose we define a class Player with subclasses Quarterback and TightEnd.
All of these classes define a constructor with two String arguments, the name and team of the player. Determine whether each of the following code snippets will compile, briefly explaining each of your answers.
(a)
1
2
3
Quarterback ja = new Quarterback("Josh Allen", "Buffalo");
TightEnd dk = new TightEnd("Dalton Kincaid", "Buffalo");
AsymPair<Quarterback, TightEnd> bills = new AsymPair<>(ja, dk);
1
2
3
Quarterback ja = new Quarterback("Josh Allen", "Buffalo");
TightEnd dk = new TightEnd("Dalton Kincaid", "Buffalo");
AsymPair<Quarterback, TightEnd> bills = new AsymPair<>(ja, dk);
(b)
1
2
3
Quarterback sd = new Quarterback("Sam Darnold", "Seattle");
TightEnd ab = new TightEnd("AJ Barner", "Seattle");
AsymPair<TightEnd, Quarterback> seahawks = new AsymPair<>(sd, ab);
1
2
3
Quarterback sd = new Quarterback("Sam Darnold", "Seattle");
TightEnd ab = new TightEnd("AJ Barner", "Seattle");
AsymPair<TightEnd, Quarterback> seahawks = new AsymPair<>(sd, ab);
(c)
1
2
3
Quarterback dm = new Quarterback("Drake Maye", "New England");
TightEnd hh = new TightEnd("Hunter Henry", "New England");
AsymPair<Player, Player> patriots = new AsymPair<>(dm, hh);
1
2
3
Quarterback dm = new Quarterback("Drake Maye", "New England");
TightEnd hh = new TightEnd("Hunter Henry", "New England");
AsymPair<Player, Player> patriots = new AsymPair<>(dm, hh);
(d)
1
2
3
Player lj = new Quarterback("Lamar Jackson", "Baltimore");
Player ma = new TightEnd("Mark Andrews", "Baltimore");
AsymPair<Quarterback, TightEnd> ravens = new AsymPair<>(lj, ma);
1
2
3
Player lj = new Quarterback("Lamar Jackson", "Baltimore");
Player ma = new TightEnd("Mark Andrews", "Baltimore");
AsymPair<Quarterback, TightEnd> ravens = new AsymPair<>(lj, ma);
(e)

This last one is a bit tricky! Think carefully about whether the assignment on the last line should be allowed.

1
2
3
4
Quarterback cw = new Quarterback("Caleb Williams", "Chicago");
TightEnd ck = new TightEnd("Cole Kmet", "Chicago");
AsymPair<Quarterback, TightEnd> bears = new AsymPair<>(cw, ck);
AsymPair<Player, Player> players = bears;
1
2
3
4
Quarterback cw = new Quarterback("Caleb Williams", "Chicago");
TightEnd ck = new TightEnd("Cole Kmet", "Chicago");
AsymPair<Quarterback, TightEnd> bears = new AsymPair<>(cw, ck);
AsymPair<Player, Player> players = bears;

Submission

At the end of class, your group’s primary author should create a PDF with your written answers and upload this to the “Discussion 6” Gradescope assignment, tagging all other members of the group onto the submission.