Chapter 20 Advanced 42 Questions

Practice Questions — Generics in Java

← Back to Notes
8 Easy
9 Medium
8 Hard

Topic-Specific Questions

Question 1
Easy
What is the output?
class Box<T> {
    T value;
    Box(T v) { this.value = v; }
    T get() { return value; }
}
public class Main {
    public static void main(String[] args) {
        Box<String> b = new Box<>("Hello");
        System.out.println(b.get());
        System.out.println(b.get().length());
    }
}
T is String. get() returns a String. No cast needed.
Hello
5
Question 2
Easy
What is the output?
class Pair<A, B> {
    A first;
    B second;
    Pair(A a, B b) { first = a; second = b; }
}
public class Main {
    public static void main(String[] args) {
        Pair<String, Integer> p = new Pair<>("Arjun", 20);
        System.out.println(p.first);
        System.out.println(p.second);
        System.out.println(p.first.getClass().getSimpleName());
        System.out.println(p.second.getClass().getSimpleName());
    }
}
A=String, B=Integer. getClass().getSimpleName() returns the class name.
Arjun
20
String
Integer
Question 3
Easy
Does this code compile?
ArrayList<int> list = new ArrayList<>();
Can generics use primitive types?
No, it does not compile. Compilation error: type argument cannot be a primitive type.
Question 4
Easy
What is the output?
public class Main {
    static <T> T getFirst(T[] array) {
        return array[0];
    }
    public static void main(String[] args) {
        Integer[] ints = {10, 20, 30};
        String[] strs = {"A", "B", "C"};
        System.out.println(getFirst(ints));
        System.out.println(getFirst(strs));
    }
}
The generic method infers T from the array type.
10
A
Question 5
Easy
What is the output?
ArrayList<String> a = new ArrayList<>();
ArrayList<Integer> b = new ArrayList<>();
System.out.println(a.getClass() == b.getClass());
Due to type erasure, what are these at runtime?
true
Question 6
Medium
What is the output?
class MathBox<T extends Number> {
    T value;
    MathBox(T v) { value = v; }
    double doubleVal() { return value.doubleValue(); }
}
public class Main {
    public static void main(String[] args) {
        MathBox<Integer> a = new MathBox<>(42);
        MathBox<Double> b = new MathBox<>(3.14);
        System.out.println(a.doubleVal());
        System.out.println(b.doubleVal());
    }
}
T extends Number, so doubleValue() is available.
42.0
3.14
Question 7
Medium
Does this code compile?
class Box<T> {
    static T staticField;  // Is this valid?
}
Static fields belong to the class, not to instances. But each instance can have different T.
No, it does not compile. Compilation error: cannot make a static reference to the non-static type T.
Question 8
Medium
What is the output?
import java.util.List;
public class Main {
    static double sum(List<? extends Number> list) {
        double total = 0;
        for (Number n : list) total += n.doubleValue();
        return total;
    }
    public static void main(String[] args) {
        System.out.println(sum(List.of(1, 2, 3)));
        System.out.println(sum(List.of(1.5, 2.5)));
    }
}
? extends Number accepts List and List.
6.0
4.0
Question 9
Medium
Does this code compile?
List<? extends Number> list = new ArrayList<Integer>();
list.add(42);
Can you add elements to a List?
No, it does not compile. Compilation error: cannot add Integer to List.
Question 10
Medium
What is the output?
import java.util.ArrayList;
import java.util.List;
public class Main {
    static void addInts(List<? super Integer> list) {
        list.add(10);
        list.add(20);
    }
    public static void main(String[] args) {
        List<Number> nums = new ArrayList<>();
        addInts(nums);
        System.out.println(nums);

        List<Object> objs = new ArrayList<>();
        addInts(objs);
        System.out.println(objs);
    }
}
? super Integer accepts List, List, and List.
[10, 20]
[10, 20]
Question 11
Hard
What is the output?
public class Main {
    static <T extends Comparable<T>> T max(T a, T b, T c) {
        T result = a;
        if (b.compareTo(result) > 0) result = b;
        if (c.compareTo(result) > 0) result = c;
        return result;
    }
    public static void main(String[] args) {
        System.out.println(max(3, 7, 2));
        System.out.println(max("apple", "banana", "cherry"));
        System.out.println(max(3.14, 2.71, 1.41));
    }
}
The method works for any Comparable type. String comparison is lexicographic.
7
cherry
3.14
Question 12
Hard
Does this code compile?
class Container<T> {
    T create() {
        return new T();  // ?
    }
}
What does T become at runtime due to type erasure?
No, it does not compile. Compilation error: cannot instantiate the type T.
Question 13
Hard
What is the output?
import java.util.*;
public class Main {
    static <T> void copy(List<? extends T> src, List<? super T> dst) {
        for (T item : src) {
            dst.add(item);
        }
    }
    public static void main(String[] args) {
        List<Integer> ints = List.of(1, 2, 3);
        List<Object> objs = new ArrayList<>();
        copy(ints, objs);
        System.out.println(objs);
    }
}
src is a producer (extends), dst is a consumer (super). T is inferred.
[1, 2, 3]
Question 14
Hard
What is the output?
import java.util.*;
public class Main {
    public static void main(String[] args) {
        List<String> strings = new ArrayList<>();
        strings.add("A");
        strings.add("B");

        List raw = strings;   // Raw type
        raw.add(100);          // No compile error!

        System.out.println(strings.size());

        for (Object o : strings) {
            System.out.println(o);
        }
    }
}
Raw types bypass generic type checking. What happens when iterating?
3
A
B
100
Question 15
Hard
What is the output?
class Wrapper<T extends Comparable<T>> {
    T value;
    Wrapper(T v) { value = v; }

    boolean isGreater(Wrapper<T> other) {
        return value.compareTo(other.value) > 0;
    }
}
public class Main {
    public static void main(String[] args) {
        Wrapper<Integer> a = new Wrapper<>(10);
        Wrapper<Integer> b = new Wrapper<>(5);
        Wrapper<String> c = new Wrapper<>("Hello");
        Wrapper<String> d = new Wrapper<>("World");

        System.out.println(a.isGreater(b));
        System.out.println(b.isGreater(a));
        System.out.println(c.isGreater(d));
    }
}
compareTo returns positive if this > other. String comparison is lexicographic.
true
false
false

Mixed & Application Questions

Question 1
Easy
What is the purpose of generics in Java?
Think about type safety and code reuse.
Generics provide compile-time type safety and eliminate the need for explicit casting. They allow writing classes and methods that work with any type while catching type errors at compile time instead of runtime. A single generic class (Box<T>) replaces multiple type-specific classes.
Question 2
Easy
What is type erasure?
Think about what happens to generic type information at runtime.
Type erasure is the process by which the Java compiler removes all generic type information during compilation. At runtime, ArrayList<String> and ArrayList<Integer> are both just ArrayList. Generics are a compile-time-only feature.
Question 3
Easy
What is the output?
class Container<T> {
    T value;
    Container(T v) { value = v; }
    String getType() { return value.getClass().getSimpleName(); }
}
public class Main {
    public static void main(String[] args) {
        Container<Integer> a = new Container<>(42);
        Container<String> b = new Container<>("Hi");
        System.out.println(a.getType());
        System.out.println(b.getType());
    }
}
getClass() works on the actual object at runtime.
Integer
String
Question 4
Medium
What is the difference between List<Object> and List<?>?
One accepts any object for add. The other is read-only.
List<Object> is a list that holds Objects. You can add any object to it. List<?> is a list of unknown type. You can only read from it (as Object) and cannot add to it (except null). List<String> IS-NOT a List<Object>, but it IS a List<?>.
Question 5
Medium
Explain the PECS principle with an example.
Producer Extends, Consumer Super.
PECS stands for Producer Extends, Consumer Super. If a collection produces items (you read from it), use ? extends T. If it consumes items (you write to it), use ? super T. Example: void copy(List<? extends T> source, List<? super T> dest). Source produces items (extends), dest consumes them (super).
Question 6
Medium
Does this compile?
class Pair<T> {
    T first, second;
    Pair(T a, T b) { first = a; second = b; }
}

Pair<Number> p = new Pair<>(1, 2.5);
1 is Integer, 2.5 is Double. Both are Number subclasses.
Yes, it compiles. T is Number. Integer (1) and Double (2.5) are both subtypes of Number.
Question 7
Hard
What is the output?
import java.util.*;
public class Main {
    static <T> List<T> merge(List<? extends T> a, List<? extends T> b) {
        List<T> result = new ArrayList<>();
        result.addAll(a);
        result.addAll(b);
        return result;
    }
    public static void main(String[] args) {
        List<Integer> ints = List.of(1, 2);
        List<Double> dbls = List.of(3.0, 4.0);
        List<Number> merged = merge(ints, dbls);
        System.out.println(merged);
    }
}
T is inferred as Number (common supertype of Integer and Double).
[1, 2, 3.0, 4.0]
Question 8
Hard
Why can you not create generic arrays like new T[10]?
Think about the relationship between arrays and generics at runtime.
Arrays carry runtime type information (they are reified and covariant). Generics are erased at runtime. If new T[10] were allowed, the array would be Object[] at runtime, violating the array's type guarantee. This could lead to ArrayStoreException being missed, creating type-unsafe situations.
Question 9
Hard
What is the output?
import java.util.*;
public class Main {
    static void addToList(List<? super Integer> list) {
        list.add(1);
        list.add(2);
    }
    public static void main(String[] args) {
        List<Number> nums = new ArrayList<>();
        nums.add(0.5);  // Double
        addToList(nums);
        System.out.println(nums);
        System.out.println(nums.get(0).getClass().getSimpleName());
        System.out.println(nums.get(1).getClass().getSimpleName());
    }
}
List contains a Double and two Integers after addToList.
[0.5, 1, 2]
Double
Integer
Question 10
Medium
What is the difference between a bounded type parameter (<T extends Number>) and an upper bounded wildcard (<? extends Number>)?
One is used in class/method declarations, the other in method parameters.
Bounded type parameters (<T extends Number>) are used in class or method declarations. They give you a named type T that you can reference throughout the class/method. Upper bounded wildcards (<? extends Number>) are used in method parameters when you do not need to reference the specific type. Wildcards provide more flexibility but less control.

Multiple Choice Questions

MCQ 1
What does represent in a generic class?
  • A. A specific type called T
  • B. A type parameter placeholder that is replaced by an actual type
  • C. The Thread class
  • D. The Throwable class
Answer: B
B is correct. T is a type parameter (placeholder) that gets replaced by an actual type (like String, Integer) when the generic class is instantiated.
MCQ 2
Which of these is a valid generic declaration?
  • A. class Box<int> { }
  • B. class Box<T> { }
  • C. class Box(T) { }
  • D. class Box{T} { }
Answer: B
B is correct. Generic type parameters are declared in angle brackets: class Box<T>. Primitive types (int) cannot be used. Parentheses and braces are invalid syntax.
MCQ 3
Why were generics introduced in Java?
  • A. To make code run faster
  • B. To provide compile-time type safety and eliminate casting
  • C. To allow multiple inheritance
  • D. To replace arrays
Answer: B
B is correct. Generics provide compile-time type safety (catching type errors before runtime) and eliminate the need for explicit casting when retrieving elements from collections.
MCQ 4
What happens to generic type information at runtime?
  • A. It is preserved for instanceof checks
  • B. It is erased (type erasure)
  • C. It is stored in a metadata file
  • D. It is converted to primitive types
Answer: B
B is correct. Java uses type erasure: all generic type parameters are replaced by Object (or the bound) at compile time. No generic type information exists at runtime.
MCQ 5
What does ? extends Number mean in a method parameter?
  • A. Exactly Number
  • B. Number or any subclass of Number
  • C. Number or any superclass of Number
  • D. Any type that has a method called extends
Answer: B
B is correct. ? extends Number is an upper bounded wildcard. It accepts Number or any subclass (Integer, Double, Long, etc.). You can read from such a collection but not write to it.
MCQ 6
What does ? super Integer mean?
  • A. Exactly Integer
  • B. Integer or any subclass of Integer
  • C. Integer or any superclass of Integer
  • D. Any type except Integer
Answer: C
C is correct. ? super Integer is a lower bounded wildcard. It accepts Integer or any superclass (Number, Object). You can write Integers to such a collection.
MCQ 7
What does PECS stand for?
  • A. Polymorphism Encapsulation Class Subtype
  • B. Producer Extends Consumer Super
  • C. Parameter Extension Casting System
  • D. Private Enum Class Static
Answer: B
B is correct. PECS: Producer Extends, Consumer Super. Use extends when reading from a collection (it produces items). Use super when writing to it (it consumes items).
MCQ 8
Which of these is NOT allowed with generics?
  • A. Generic classes
  • B. Generic methods
  • C. Generic arrays (new T[])
  • D. Generic interfaces
Answer: C
C is correct. Generic array creation (new T[]) is not allowed because arrays carry runtime type information (reified) while generics are erased. This would create a type safety hole.
MCQ 9
Is List<String> a subtype of List<Object>?
  • A. Yes, because String extends Object
  • B. No, generics are invariant
  • C. Only with wildcards
  • D. Depends on the JVM version
Answer: B
B is correct. Generics are invariant. List<String> is NOT a List<Object>, even though String extends Object. If it were, you could add an Integer to a String list through the Object reference. Use List<? extends Object> for covariance.
MCQ 10
What is the purpose of <T extends Number & Comparable<T>>?
  • A. T must extend Number OR implement Comparable
  • B. T must extend Number AND implement Comparable
  • C. T must be exactly Number
  • D. This is invalid syntax
Answer: B
B is correct. Multiple bounds use & (not |). T must satisfy ALL bounds: extend Number AND implement Comparable. The class bound must come first, followed by interface bounds.
MCQ 11
Can you overload methods that differ only in generic type parameters?
  • A. Yes, they are different methods
  • B. No, due to type erasure they become the same method
  • C. Only if the return types differ
  • D. Only in Java 17+
Answer: B
B is correct. void process(List<String> list) and void process(List<Integer> list) cannot coexist. After type erasure, both become void process(List list), which is a duplicate method definition.
MCQ 12
Which is a valid restriction of generics?
  • A. Cannot use generic types in loops
  • B. Cannot create instances of type parameters (new T())
  • C. Cannot have more than one type parameter
  • D. Cannot use generics with interfaces
Answer: B
B is correct. new T() is illegal because the JVM does not know what T is at runtime (type erasure). Multiple type parameters (Map<K,V>) and generic interfaces are fully supported.

Coding Challenges

Challenge 1: Generic Stack

Easy
Implement a generic Stack using an ArrayList internally. It should support push(T), pop(), peek(), isEmpty(), and size(). Test with Integer and String stacks.
Sample Input
Push 10, 20, 30. Pop twice.
Sample Output
Stack: [10, 20, 30] Pop: 30 Pop: 20 Peek: 10 Size: 1
Use generics. Handle empty stack with IllegalStateException.
import java.util.ArrayList;

class Stack<T> {
    private ArrayList<T> items = new ArrayList<>();
    void push(T item) { items.add(item); }
    T pop() {
        if (isEmpty()) throw new IllegalStateException("Stack is empty");
        return items.remove(items.size() - 1);
    }
    T peek() {
        if (isEmpty()) throw new IllegalStateException("Stack is empty");
        return items.get(items.size() - 1);
    }
    boolean isEmpty() { return items.isEmpty(); }
    int size() { return items.size(); }
    @Override
    public String toString() { return items.toString(); }
}

public class Main {
    public static void main(String[] args) {
        Stack<Integer> stack = new Stack<>();
        stack.push(10);
        stack.push(20);
        stack.push(30);
        System.out.println("Stack: " + stack);
        System.out.println("Pop: " + stack.pop());
        System.out.println("Pop: " + stack.pop());
        System.out.println("Peek: " + stack.peek());
        System.out.println("Size: " + stack.size());
    }
}

Challenge 2: Generic Pair with Swap

Easy
Create a generic Pair class with methods getFirst(), getSecond(), and swap() that returns a new Pair with swapped types. Test with Pair.
Sample Input
Pair('Arjun', 20)
Sample Output
Original: (Arjun, 20) Swapped: (20, Arjun)
swap() must return a new Pair with swapped type parameters.
class Pair<A, B> {
    A first;
    B second;
    Pair(A a, B b) { first = a; second = b; }
    A getFirst() { return first; }
    B getSecond() { return second; }
    Pair<B, A> swap() { return new Pair<>(second, first); }
    @Override
    public String toString() { return "(" + first + ", " + second + ")"; }
}

public class Main {
    public static void main(String[] args) {
        Pair<String, Integer> p = new Pair<>("Arjun", 20);
        System.out.println("Original: " + p);
        Pair<Integer, String> swapped = p.swap();
        System.out.println("Swapped: " + swapped);
    }
}

Challenge 3: Generic Min/Max Finder

Medium
Write generic methods min() and max() that find the minimum and maximum elements in a List>. Also write a range() method that returns a Pair of (min, max). Test with Integer and String lists.
Sample Input
List: [30, 10, 50, 20, 40]
Sample Output
Min: 10, Max: 50 Range: (10, 50) String min: Apple, max: Cherry
Use bounded type parameters with Comparable.
import java.util.List;

class Pair<A, B> {
    A first; B second;
    Pair(A a, B b) { first = a; second = b; }
    @Override
    public String toString() { return "(" + first + ", " + second + ")"; }
}

public class Main {
    static <T extends Comparable<T>> T min(List<T> list) {
        T result = list.get(0);
        for (T item : list) {
            if (item.compareTo(result) < 0) result = item;
        }
        return result;
    }
    static <T extends Comparable<T>> T max(List<T> list) {
        T result = list.get(0);
        for (T item : list) {
            if (item.compareTo(result) > 0) result = item;
        }
        return result;
    }
    static <T extends Comparable<T>> Pair<T, T> range(List<T> list) {
        return new Pair<>(min(list), max(list));
    }
    public static void main(String[] args) {
        var nums = List.of(30, 10, 50, 20, 40);
        System.out.println("Min: " + min(nums) + ", Max: " + max(nums));
        System.out.println("Range: " + range(nums));
        var strs = List.of("Banana", "Apple", "Cherry");
        System.out.println("String min: " + min(strs) + ", max: " + max(strs));
    }
}

Challenge 4: Generic Filter Method

Medium
Write a generic method filter(List list, Predicate condition) that returns a new list containing only elements that satisfy the condition. Test with filtering even numbers and strings longer than 3 characters.
Sample Input
Numbers: [1,2,3,4,5,6], Strings: ['Hi','Hello','Hey','Howdy']
Sample Output
Even numbers: [2, 4, 6] Long strings: [Hello, Howdy]
Use generics and Predicate functional interface.
import java.util.*;
import java.util.function.Predicate;

public class Main {
    static <T> List<T> filter(List<T> list, Predicate<T> condition) {
        List<T> result = new ArrayList<>();
        for (T item : list) {
            if (condition.test(item)) result.add(item);
        }
        return result;
    }
    public static void main(String[] args) {
        List<Integer> nums = List.of(1, 2, 3, 4, 5, 6);
        System.out.println("Even numbers: " + filter(nums, n -> n % 2 == 0));

        List<String> strs = List.of("Hi", "Hello", "Hey", "Howdy");
        System.out.println("Long strings: " + filter(strs, s -> s.length() > 3));
    }
}

Challenge 5: Generic Transform (Map) Method

Hard
Write a generic method transform(List list, Function mapper) that applies a transformation function to each element and returns a List. Test with: numbers to their squares, strings to their lengths, students to their names.
Sample Input
Numbers: [1,2,3,4], Strings: ['Java','Go','Python']
Sample Output
Squares: [1, 4, 9, 16] Lengths: [4, 2, 6]
Use two type parameters T and R. Use Function<T,R> interface.
import java.util.*;
import java.util.function.Function;

public class Main {
    static <T, R> List<R> transform(List<T> list, Function<T, R> mapper) {
        List<R> result = new ArrayList<>();
        for (T item : list) {
            result.add(mapper.apply(item));
        }
        return result;
    }
    public static void main(String[] args) {
        List<Integer> nums = List.of(1, 2, 3, 4);
        System.out.println("Squares: " + transform(nums, n -> n * n));

        List<String> strs = List.of("Java", "Go", "Python");
        System.out.println("Lengths: " + transform(strs, String::length));

        System.out.println("Uppercase: " + transform(strs, String::toUpperCase));
    }
}

Need to Review the Concepts?

Go back to the detailed notes for this chapter.

Read Chapter Notes

Want to learn Java with a live mentor?

Explore our Java Masterclass