Discussion 6: Generics
Solutions
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.
ints and doubles, since ComplexNumber is neither of these, Bob can't use either of these existing implementations.
What shortcoming does this suggest in Alice’s approach to modeling pairs?
IntPair and DoublePair classes are essentially the same, just with different types declared for the fields, parameters, and return values.
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:
|
|
|
|
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.
- The return types of the
first()andsecond()methods in Bob's implementation will need to beObject; using any subtype would eliminate the flexibility that Bob is after. However, this means that the static type of these method calls will beObject, and the Compile Time Reference Rule will prevent us from doing most things we'd like to do with the pair entries. - To get around this, we'd need to introduce a lot of explicit casting into our code, which is cumbersome and leaves us vulnerable
ClassCastExceptions. - If both fields of the constructor are
Object, then Java has no way of enforcing that the two arguments to the constructor have the same (more specific) type. Alice may want to create a pair with twoIntegers, but accidentally create one with oneIntegerand oneStringwithout any warning.
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.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
|
|
|
|
bills's first element must be a Quarterback and its second element must be a TightEnd, and ja and dk match the types in the correct order.
|
|
|
|
seahawks's first element must be a TightEnd and its second element must be a Quarterback. Even though its constructor is being passed a TightEnd and a Quarterback, they're in the wrong order, AsymPair cannot store a Quarterback where it's expecting a TightEnd and vice versa.
|
|
|
|
patriots is expecting both of its elements to be Players. Even though dm is specifically a Quarterback and hh is specifically a TightEnd, these classes are both subtypes of the Player class. Java can automatically upcast them to work with the pair; this is an instance of the parameter subtype substitution rule.
|
|
|
|
lj's dynamic type is Quarterback and ma's dynamic type is TightEnd, they are both statically typed to be Players. Since ravens is specifically looking for objects that are statically typed to be a Quarterback and TightEnd in that order, and Java doesn't automatically downcast, ravens cannot accept the parameters passed into its constructor.We could create this pair using explicit casting:
|
|
|
|
|
|
|
|
bears compiles; however, the fourth line will not. For this assignment statement to work, we'd need the static type of bears to be a subtype of the AsymPair<Player, Player>, but this is not the case. The type AsymPair<Player, Player> supports a setFirst() method that can accept any Player as its argument. The type AsymPair<Quarterback, TightEnd> cannot do this; its setFirst() method can only accept a Quarterback as this argument. Concretely, it must be valid to call players.setFirst(new TightEnd("Colston Loveland", "Chicago")) - however, it would be invalid to call bears.setFirst(new TightEnd("Colston Loveland", "Chicago")). Thus AsymPair<Quarterback, TightEnd> is missing functionality that would be allowed in AsymPair<Player, Player>, so the compiler does not allow this assignment.