Day 17: Generics
Generics allow you to receive types as parameters when defining classes or methods. Think of it as a delivery box that “can hold anything,” but once designated as a “book box,” only books can go in. It checks types at compile time to prevent runtime errors.
Generic Classes
Create classes that handle various types using the type parameter <T>.
// Generic class: T is the type parameter
class Box<T> {
private T item;
public void put(T item) {
this.item = item;
System.out.println(item.getClass().getSimpleName() + " stored: " + item);
}
public T get() {
return item;
}
public boolean isEmpty() {
return item == null;
}
}
// Multiple type parameters
class Pair<K, V> {
private final K key;
private final V value;
Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
@Override
public String toString() {
return key + " = " + value;
}
}
public class GenericClassExample {
public static void main(String[] args) {
// String type Box
Box<String> stringBox = new Box<>();
stringBox.put("Hello");
String message = stringBox.get(); // No casting needed!
System.out.println("Retrieved: " + message);
// Integer type Box
Box<Integer> intBox = new Box<>();
intBox.put(42);
int number = intBox.get(); // Auto-unboxing
System.out.println("Retrieved: " + number);
// Using Pair
Pair<String, Integer> nameAge = new Pair<>("Alice", 25);
Pair<String, String> config = new Pair<>("host", "localhost");
System.out.println(nameAge);
System.out.println(config);
// Diamond operator (<>): Java 7+
// Type on the right side can be omitted (inferred)
Box<Double> doubleBox = new Box<>();
doubleBox.put(3.14);
}
}
Generic Methods
Declare type parameters at the method level to handle various types.
import java.util.Arrays;
import java.util.List;
public class GenericMethodExample {
// Generic method: declare <T> before the return type
static <T> void printArray(T[] array) {
System.out.print("[");
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]);
if (i < array.length - 1) System.out.print(", ");
}
System.out.println("]");
}
// Return the greater of two values
static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
// Find a specific value in an array
static <T> int indexOf(T[] array, T target) {
for (int i = 0; i < array.length; i++) {
if (array[i].equals(target)) {
return i;
}
}
return -1;
}
// Convert array to List
static <T> List<T> arrayToList(T[] array) {
return Arrays.asList(array);
}
public static void main(String[] args) {
Integer[] intArr = {1, 2, 3, 4, 5};
String[] strArr = {"Java", "Python", "Go"};
Double[] dblArr = {1.1, 2.2, 3.3};
printArray(intArr);
printArray(strArr);
printArray(dblArr);
System.out.println("Greater: " + max(10, 20));
System.out.println("Greater: " + max("apple", "banana"));
System.out.println("Python index: " + indexOf(strArr, "Python"));
List<String> list = arrayToList(strArr);
System.out.println("List: " + list);
}
}
Bounded Type Parameters
Restrict type parameters with upper/lower bounds to allow only certain types.
// Upper bound: only Number or its subclasses allowed
class MathBox<T extends Number> {
private T value;
MathBox(T value) {
this.value = value;
}
double doubleValue() {
return value.doubleValue();
}
boolean isPositive() {
return value.doubleValue() > 0;
}
// Sum of two MathBoxes
<U extends Number> double add(MathBox<U> other) {
return this.doubleValue() + other.doubleValue();
}
}
// Multiple bounds: must implement multiple interfaces
class SortableBox<T extends Comparable<T> & java.io.Serializable> {
private T item;
SortableBox(T item) {
this.item = item;
}
boolean isGreaterThan(SortableBox<T> other) {
return this.item.compareTo(other.item) > 0;
}
T getItem() {
return item;
}
}
public class BoundedTypeExample {
public static void main(String[] args) {
MathBox<Integer> intMath = new MathBox<>(42);
MathBox<Double> dblMath = new MathBox<>(3.14);
System.out.println("As double: " + intMath.doubleValue());
System.out.println("Positive? " + intMath.isPositive());
System.out.println("Sum: " + intMath.add(dblMath));
// MathBox<String> strMath = new MathBox<>("hello"); // Compile error!
SortableBox<String> box1 = new SortableBox<>("apple");
SortableBox<String> box2 = new SortableBox<>("banana");
System.out.println("box1 > box2? " + box1.isGreaterThan(box2));
}
}
Wildcards
The ? symbol for flexible use of generic types.
import java.util.ArrayList;
import java.util.List;
public class WildcardExample {
// Unbounded wildcard: accepts all types (read-only)
static void printList(List<?> list) {
for (Object item : list) {
System.out.print(item + " ");
}
System.out.println();
}
// Upper bounded wildcard: accepts Number and below (for reading)
static double sumOfList(List<? extends Number> list) {
double sum = 0;
for (Number num : list) {
sum += num.doubleValue();
}
return sum;
}
// Lower bounded wildcard: accepts Integer and above (for writing)
static void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
list.add(3);
}
// PECS principle: Producer Extends, Consumer Super
// When you take out (produce) data -> extends
// When you put in (consume) data -> super
static <T> void copy(List<? extends T> source, List<? super T> dest) {
for (T item : source) {
dest.add(item);
}
}
public static void main(String[] args) {
List<Integer> intList = List.of(1, 2, 3, 4, 5);
List<Double> dblList = List.of(1.1, 2.2, 3.3);
List<String> strList = List.of("A", "B", "C");
printList(intList);
printList(strList);
System.out.println("Integer sum: " + sumOfList(intList));
System.out.println("Double sum: " + sumOfList(dblList));
List<Number> numberList = new ArrayList<>();
addNumbers(numberList);
System.out.println("Added numbers: " + numberList);
// Copy
List<Number> dest = new ArrayList<>();
copy(intList, dest);
System.out.println("Copy result: " + dest);
}
}
Today’s Exercises
-
Generic Stack: Implement a
GenericStack<T>class withpush(T),pop(),peek(),isEmpty(), andsize()methods. Use anArrayListinternally. -
Generic Utilities: Implement the following generic methods:
swap(T[] arr, int i, int j)- swap two elements in an array,reverse(List<T> list)- reverse a list,filter(List<T> list, Predicate<T> pred)- return only elements matching the condition. -
Comparable Usage: Create a
Statistics<T>class using the<T extends Comparable<T>>bound that returns the maximum, minimum, and sorted list from a given list.