10.8. Old Quiz: Using Generics¶
10.8.1. Generics Practice Quiz¶
Generics Practice Quiz
Generics Quiz
Consider the generic Pair class below:
Which statements below are appropriate uses of generics? An appropriate use of generics will compile and avoid the use of raw types. Select all that apply. Assume each option is independent of the others.
Pair<String, String> p = new Pair<>("a", "b");Pair<> p = new Pair<String, String>("Bred's", "Fern");Pair p<String, Boolean> = new Pair<String, Boolean>("Brad", true);Pair<String, Boolean> p = new Pair("Brad", true);Pair<Person, Boolean> p = new Pair<Person, Boolean>("Jack", true);Pair<Pair<Integer, Double>, String> p = new Pair<>(new Pair<Integer, Double>(4, 5.0), "yas");Pair<String, Boolean> p = new Pair<String, Boolean>("Brad", true);Pair p = new Pair(7.2, 4);Pair p = new Pair("Sally", "Joe");Pair<Shape, String> p = new Pair<Ellipse, String>(new Ellipse(4.2, 3.8), "hello");
Solution
Yes. This is the second-best way to do this kind of statement. The variable type is parameterized and the constructor call makes use of the diamond operator to infer the types for the type parameters.
No. The diamond operator is not allowed with type declarations. That is, you must always parameterize the generic type parameters in a variable declaration.
No. You cannot parameterize the variable name. The programmer probably intended to parameterize the variable’s type.
No. This will compile, but the compiler will generate a warning because the programmer is assigning raw type to a parameterized type.
No. The first parameter (“Jack”) is a
Stringwhich is not compatible with the expectedPersontype.Yes. Believe it or not, this actually compiles! It follows all the rules for parameterizing the variable type and makes proper use of the diamond operator in the constructor calls. It’s tricky because one of the type parameters itself is a parameterized generic type!
Yes. This is the preferred way to do this kind of statement. The variable type and the constructor call are both parameterized.
No. This declares and instantiates raw type. The type parameters
TandUboth default toObject. Although the class itself is generic, this use of a raw type will likely still require casting to be useful.No. This declares and instantiates raw type. The type parameters
TandUboth default toObject. Although the class itself is generic, this use of a raw type will likely still require casting to be useful.No.
Pair<Ellipse, String>is not compatible withPair<Shape, String>becausePair<Shape, String>is not a parent ofPair<Ellipse, String>. The fact thatEllipseis compatible withShapehas no impact on the compatibility between these types.
10.9. Additional Practice Exercises¶
Question 1
Using the UML diagram above (from section 10.1), explain the following:
Explain how the type parameter DATATYPE allows a single generic class to handle multiple product types without creating new classes.
Solution
The type parameter
DATATYPEis a placeholder for the product type. When creating aShippingContainer, you provide the actual type (e.g.,DroneorCamera). This allows one class to store any type of product without creating separate classes. The compiler ensures that only objects of the specified type can be stored, preventing type errors.Consider the following code and explain why this code produces a compile-time error.
1ShippingContainer<Drone> droneContainer; 2droneContainer = new ShippingContainer<>(new Drone(175.0), 25.0); 3droneContainer.setContents(new Camera(128.0));
Solution
Here,
droneContainercan only storeDroneobjects, andcameraContainercan only storeCameraobjects.Trying to store a
CameraindroneContainerwould produce a compile-time error.Discuss why catching type errors at compile time is preferable to runtime errors in large software systems like Amazeon’s.
Solution
Catching type errors at compile time is preferable because it allows programmers to find and fix mistakes before the program runs, reducing the risk of crashes or unexpected behavior in production. Compile time errors are also easier to find as the compiler often points you directly to the cause of the issue.
In large systems like Amazeon’s, runtime errors caused by storing the wrong type could affect thousands of shipments, be costly to debug, and compromise reliability. Compile-time type checking ensures safer, more predictable, and easier-to-maintain code.
Question 2
Explain the difference between a type parameter and a type argument in the context of Java Generics.
Solution
A type parameter is a placeholder used in a generic class or method definition. It does not represent a real type until the class or method is used. For example, in
ShippingContainer<DATATYPE>,DATATYPEis a type parameter.A type argument is the actual type supplied when creating an object from a generic class or calling a generic method. It replaces the type parameter. For example, in
ShippingContainer<Integer>,Integeris the type argument that tells the compiler what typeDATATYPEshould be for this instance.In the following declaration, identify the type parameter and the type argument:
ShippingContainer<Integer> intContainer = new ShippingContainer<Integer>(17);
Solution
Type parameter:
DATATYPEin the class definitionShippingContainer<DATATYPE>Type argument:
IntegerinShippingContainer<Integer>
Why does the following code not compile?
ShippingContainer<int> intContainer = new ShippingContainer<int>(17);
Solution
Java generics cannot use primitive types (such as
int,char,double) as type arguments. Generics require reference types, not primitive types. Using a primitive type directly causes a compile-time error.
Question 3
1/**
2 * Utility class providing generic methods for arrays.
3 */
4 public class Utility {
5
6 /**
7 * Prints all elements of a given array to standard output.
8 *
9 * @param <T> the type of array elements
10 * @param array the array whose elements are to be printed
11 */
12 public static <T> void printArray(T[] array) {
13 for (T item : array) {
14 System.out.println(item);
15 } // for
16 } // printArray
17
18 /**
19 * Finds the maximum element in a given array of Comparable objects.
20 *
21 * @param <T> the type of array elements, which must implement Comparable
22 * @param array the array to search for the maximum element
23 * @return the maximum element in the array
24 * @throws NullPointerException if the array or its first element is null
25 * @throws IllegalArgumentException if the array is empty
26 */
27 public static <T extends Comparable<T>> T findMax(T[] array) {
28 if (array == null || array.length == 0) {
29 throw new IllegalArgumentException("Array must not be null or empty");
30 } // if
31
32 T max = array[0];
33 for (T item : array) {
34 if (item.compareTo(max) > 0) {
35 max = item;
36 } // if
37 } // for
38 return max;
39 } // findMax
40 } // Utility
1public class Main {
2
3 public static void main(String[] args) {
4
5 String[] names = {"Alice", "Bob", "Charlie"};
6 Integer[] scores = {95, 82, 77};
7 Double[] gpas = {3.8, 3.4, 3.9};
8
9 // Printing arrays of different types
10 Utility.printArray(names);
11 Utility.printArray(scores);
12 Utility.printArray(gpas);
13
14 // Finding maximum values
15 System.out.println("Highest score: " + Utility.findMax(scores));
16 System.out.println("Highest GPA: " + Utility.findMax(gpas));
17 } // main
18} // Main
Using the code above, answer the following questions:
How does the
<T>type parameter inprintArrayallow the same method to work for arrays of any type?Why is
<T extends Comparable<T>>necessary infindMax, and what does it ensure about the data passed to the method?How do generic methods help reduce code duplication compared to writing separate methods for each type?
How does the compiler help catch type errors at compile time when using generic methods?
How does this approach improve code maintenance and prevent mistakes in large applications with many data types?
Solutions
The type parameter
<T>is a placeholder for a specific type that gets determined when the method is called. For example:String[] names = {"Alice", "Bob", "Charlie"}; Integer[] scores = {95, 82, 77}; printArray(names); // Here T is String printArray(scores); // Here T is Integer
This means the method is not tied to any one data type. Instead, the compiler automatically substitutes the appropriate type when the method is used. As a result, one method definition works for all object types, while preserving type safety.
<T extends Comparable<T>>restrictsTso that the type parameter (the replacement forT) must implement theComparableinterface. This ensures that any element can be compared to another element of the same type usingcompareTo.Without this bound, the compiler would not allow the code to call
item.compareTo(max)because it could not guarantee thatTobjects are comparable. Example:IntegerandDoubleimplementComparable, so they work withfindMax. A custom class would only work if it also implementsComparable.Without generics, you would have to write a separate method for every data type (e.g., one for
String[], one forInteger[], one forDouble[]). These methods would contain almost identical code, creating redundancy. Generics consolidate this into a single reusable method. Example: oneprintArraymethod works for all arrays, instead of three (or more) separate ones. This makes the code shorter, clearer, and easier to maintain.The compiler enforces type safety by ensuring that the method only works with the type specified when the method is called. For example:
ShippingContainer<Drone> droneContainer = new ShippingContainer<>(); // droneContainer.add(new Camera()); // Compile-time error!
This error would be caught before running the program, preventing runtime crashes.
In
findMax, the compiler also ensures that only comparable types are passed, preventing logical errors during execution.This approach improves code maintenance by reducing duplication—one generic method can handle many data types instead of writing separate methods for each. It also leverages compile-time checking, which prevents invalid type usage before the program runs. In large applications with many data types, this means fewer bugs, easier updates, and more scalable, reliable code.
Question 4
Write a static generic method called
printCenterthat prints the center element(s) of an array of any type.If the array has an odd number of elements, it should print the single middle element.
If the array has an even number of elements, it should print the two central elements.
/** * Prints the center element(s) of the given array. * <p> * If the array has an odd number of elements, it prints the single middle element. * If the array has an even number of elements, it prints the two central elements. * * @param <T> the type of elements in the array * @param array the array of elements */ public static <T> void printCenter(T[] array) { // Your code here... } // printCenter
Solution
public static <T> void printCenter(T[] array) { if (array.length == 0) { System.out.println("Array is empty."); } else { int mid = array.length / 2; if (array.length % 2 == 0) { System.out.println(array[mid - 1] + ", " + array[mid]); } else { System.out.println(array[mid]); } // if } // if } // printCenter
Assuming that we have a valid implementation of
printCenter, which of the following calls will compile and run correctly?String[] names = {"Alice", "Bob", "Charlie"}; Double[] gpas = {3.8, 3.4, 3.9, 4.0}; Utility.printCenter(names); // (a) Utility.printCenter(gpas); // (b) Utility.printCenter(42); // (c)
Solution
This call is valid. The type parameter
Tis inferred asString.This call is valid. The type parameter
Tis inferred asDouble.This call does not compile. The argument is an
intrather than an array, and the method requiresT[].
Write a static generic method called
bestOfTwothat returns the larger of two values. Require thatTimplementsComparable<T>./** * Returns the larger of two values. * * @param <T> the type of the values (must be comparable) * @param a the first value * @param b the second value * @return the larger value */ public static <T extends Comparable<T>> T bestOfTwo(T a, T b) { // Your code here... } // bestOfTwo
Solution
/** * Returns the larger of two values. * * @param <T> the type of the values (must be comparable) * @param a the first value * @param b the second value * @return the larger value */ public static <T extends Comparable<T>> T bestOfTwo(T a, T b) { if (a.compareTo(b) >= 0) { return a; } else { return b; } // if } // bestOfTwo
Assuming that we have a valid implementation of
bestOfTwo, which of the following calls will compile and run correctly?String s1 = "apple"; String s2 = "zebra"; Integer a = 17, b = 42; Object o1 = new Object(); Object o2 = new Object(); Utility.bestOfTwo(s1, s2); // (a) Utility.bestOfTwo(a, b); // (b) Utility.bestOfTwo(o1, o2); // (c)
Solution
This call is valid. The type parameter
TisString, which implementsComparable<String>.This call is valid. The type parameter
TisInteger, which implementsComparable<Integer>.This call does not compile. The type
Objectdoes not implementComparable<Object>, and therefore does not satisfy the boundT extends Comparable<T>.
Question 5
Assume you have access to the following method:
public static <T> T foo(int a, int b)
What is the return type of the following call?
String s = Utility.<String>foo(10, 20);
Solution
In this call, the type parameter
<T>is explicitly specified asString. This means that whereverTappears in the method signature, it is replaced byString. The compiler interprets the method as:public static String foo(int a, int b)
Because the return type is now
String, assigning the result to a variable of typeStringis valid. The compiler ensures that only aStringvalue can be returned for this call. This demonstrates how generic methods allow the same method to produce different types depending on the type parameter, avoiding the need to write separate methods for each type.What is the return type of the following call?
Double d = Utility.<Double>foo(5, 7);
Solution
Here, the type parameter
<T>is specified asDouble. The method signature is therefore treated as:public static Double foo(int a, int b)
The compiler now expects a return value of type
Double, and assigning it toDouble dis valid. This shows that by specifying the type parameter, we can safely use the same generic method to work with completely different types, while the compiler enforces type safety at compile time.Will the following line compile? If so, what type will
fooreturn?Camera c = Utility.<Camera>foo(1, 2);
Solution
In this example, the type parameter
<T>isCamera. The compiler views the method as:public static Camera foo(int a, int b)
Because the type parameter matches the variable type
Camera c, the assignment is valid and the code compiles successfully. If we tried to assign the result to a variable of a different type, the compiler would produce an error. This illustrates one of the key advantages of generic methods: type errors can be caught at compile time, rather than causing runtime failures.
Question 6
Assume you have access to the following code:
public static <T> int foo(String a, T b)
Will the following line compile? Why or why not?
Utility.<String>foo("test", "data");
Solution
Yes, this line compiles. The type parameter
<T>is specified asString, so the method signature becomes:public static int foo(String a, String b)
Both arguments match the expected types. The compiler confirms type safety, and the code runs successfully.
Will the following line compile? Why or why not?
Utility.<Integer>foo("grade", 95);
Solution
Yes, this line compiles. The type parameter
<T>isInteger, so the method signature becomes:public static int foo(String a, Integer b)
The first argument is a
Stringand the second is anInteger, which matches the expected types. The compiler allows this call and ensures type safety at compile time.Will the following line compile? Explain your reasoning.
Utility.<Camera>foo("check", new Drone(150.0));
Solution
No, this line will not compile. The type parameter
<T>isCamera, so the compiler expects the second argument to be of typeCamera. Since aDroneobject is provided instead, the types are incompatible. The compiler catches this mismatch at compile time, preventing a potential runtime error.Will this compile successfully?
String s = Utility.<String>foo("hello", "world");
Solution
No, this assignment is invalid. Although the type parameter is
String, the methodfoois declared to returnint. The compiler sees a mismatch: anintvalue cannot be assigned to a variable of typeString. This demonstrates that generic type parameters only affect formal parameters, not the declared return type, unless the return type itself uses the type parameter.
Question 7
Assume you have access to the following method:
public static <T> T foo(String a, T b)
What is the return type of the following call?
String s = Utility.<String>foo("note", "text");
Solution
The type parameter
<T>is specified asString, and the method signature is:public static <T> T foo(String a, T b)
By substituting
TwithString, the method becomes:public static String foo(String a, String b)
Therefore, the return type of this call is
String, which matches the variables. The code will compile successfully.Will the following line compile? Why or why not?
Integer i = Utility.<Integer>foo("score", 42);
Solution
Yes, this line compiles. The type parameter
<T>isInteger, so the method expects the second argument to be anInteger. The argument provided is42, which is automatically boxed into anIntegerby Java’s autoboxing feature. The return type is alsoInteger, which matches the variablei. The compiler ensures type safety.What will be the type of the returned value in this call?
Shape sh = Utility.<Shape>foo("shape", new Circle(3.5));
Solution
Here, the type parameter
Tis replaced byShape, so the method signature becomes:public static Shape foo(String a, Shape b)
The second argument is
new Circle(3.5), which is valid becauseCircleis a subclass ofShape. The return type isShape, so the variableshcorrectly holds the returned object. The compiler enforces this type relationship.
Question 8
Assume you have access to the following method:
public <T> T foo(String a, T b)
Will the following compile? If so, what is the return type?
Utility util = new Utility(); String s = util.<String>foo("A", "B");
Solution
Yes, this line compiles. The method
foois a non-static generic method with the signature:public <T> T foo(String a, T b)
Here, the type parameter
<T>is specified asString, sobmust be aString. Both the argumentBand the variablesare of typeString, so the call is valid. The return type isString.Will this line compile? If so, what is the return type?
Utility util = new Utility(); Drone d = new Drone(200.0); Drone d2 = util.<Drone>foo("drone", d);
Solution
Yes, this line compiles. By specifying
<Drone>, the method expects the second argumentbto be aDrone. The argument provided is indeed aDrone, and the returned value matches the typeDrone. Therefore, the return type isDrone, and the assignment tod2is valid.What happens if we try the following? Explain.
Utility util = new Utility(); Camera c = new Camera("Nikon"); Drone d = util.<Camera>foo("fail", c);
Solution
This will not compile. The method call specifies the type parameter as
<Camera>, so the second argumentbmust be aCamera. However, the left-hand side variable is of typeDrone. The compiler detects a mismatch between the specified type parameter and the assigned variable type. Java’s type checking prevents this code from compiling, avoiding a potential runtime error.
Question 9
Assume you have access to the following method:
public class SomeClass<T> {
public <R> T foo(T a, R b) {
// ...
} // foo
} // someClass
What is the return type of the following call?
SomeClass<Double> sc = new SomeClass<Double>(); Double d = sc.<String>foo(3.14, "pi");
Solution
The method
foohas the signature:public <R> T foo(T a, R b)
Here, the class-level type parameter
TisDouble, and the method-level type parameterRis specified asString. The first argumentais aDouble, matchingT, and the second argumentbis aString, matchingR. The return type is determined by the class-level type parameterT, which isDouble. Therefore, the variabledcorrectly receives aDouble.Will this compile? If so, what type is returned?
SomeClass<String> sc = new SomeClass<String>(); String s = sc.<Integer>foo("data", 99);
Solution
Yes, this compiles.
TisStringfor the class, andRis specified asIntegerfor the method. The first argumentdatamatchesTand the second argument99matchesR. The return type isT, which isString, so the assignment tosis valid.What is the return type here?
SomeClass<Camera> sc = new SomeClass<Camera>(); Camera c = sc.<Drone>foo(new Camera("Canon"), new Drone(180.0));
Solution
The class-level type parameter
TisCameraand the method-level parameterRisDrone. Argumentais aCamera, matchingT, and argumentbis aDrone, matchingR. The return type isT, which isCamera. Hence, the variableccorrectly receives aCameraobject.How does this example illustrate the interaction between the class-level type parameter
Tand the method-level type parameterR?Solution
In this example,
Tis defined at the class level and determines the type returned by all non-static methods that useT. The method-level type parameterRis independent ofTand only affects the method’s parameters or local logic. This separation allows the method to accept arguments of a different type than the class while still returning a value of the class-level type. It demonstrates how generic methods can coexist inside generic classes, providing flexibility in type handling without sacrificing type safety.
Question 10
Will this compile? If so, what is the return type?
SomeClass<Integer> sc = new SomeClass<Integer>(); Integer i = sc.<String>foo(10, "ten");
Solution
Yes, this line will compile. The class-level type parameter
TisInteger, so the first argument (10) matchesT. The method-level type parameterRisString, so the second argument (“ten”) matches R. The return type offooisT(Integer), so the assignment is valid.What happens in this scenario?
SomeClass<Double> sc = new SomeClass<Double>(); Double d = sc.<Double>foo(3.14, 2.718);
Solution
This will compile. The class-level type parameter T is Double, so the first argument (3.14) matches T. The method-level type parameter R is also specified as Double, matching the second argument (2.718). The method returns T, which is Double, so the assignment to
dis valid.Predict the behavior of this code:
SomeClass<String> sc = new SomeClass<String>(); String s = sc.<Character>foo("hello", 'A');
Solution
This will compile. T is String (class-level), so the first argument “hello” matches. R is Character (method-level), so the second argument ‘A’ matches. The method returns T, which is String, so the assignment to
sis valid.Will the following code compile? Why or why not?
SomeClass<Camera> sc = new SomeClass<Camera>(); Camera c = sc.<Drone>foo(new Drone(100.0), new Camera("Sony"));
Solution
This will not compile. The class-level type parameter T is Camera, so the first argument must be a Camera, but
new Drone(100.0)is a Drone. Method-level type parameter R is Drone, which matches the second argument type, but the mismatch for T prevents compilation.Suppose you have the following code. Will it compile?
SomeClass<Shape> sc = new SomeClass<Shape>(); Shape sh = sc.<Rectangle>foo(new Circle(5.0), new Square(3.0));
Solution
This will not compile. The class-level type parameter T is Shape, so the first argument must be a Shape.
new Circle(5.0)is a Circle, which is a subclass of Shape, so it is compatible. The method-level type parameter R is Rectangle, so the second argument must be a Rectangle.new Square(3.0)is a subclass of Rectangle (if assuming inheritance Square extends Rectangle), so it is compatible. The method returns T, which is Shape, so the return typeShape shis valid. Therefore, this line will compile, assuming the stated inheritance hierarchy.