Generics and Type Safety
Java Programming (4343203)
Lecture 18
Unit 4: Advanced Java - Type Safety
GTU Computer Engineering Semester 4
Learning Objectives
- Understand the need for generics in Java programming
- Create and use generic classes and interfaces
- Implement generic methods and constructors
- Master bounded type parameters and wildcards
- Apply type erasure concepts and limitations
- Utilize generic collections for type-safe programming
Focus: Writing type-safe, reusable code using Java's generics system for better compile-time error detection.
Why Do We Need Generics?
Problems Before Generics
// Pre-Java 5 code (without generics)
import java.util.*;
public class PreGenericsExample {
public static void main(String[] args) {
// Raw type ArrayList
ArrayList list = new ArrayList();
list.add("Hello");
list.add("World");
list.add(42); // Oops! Integer in String list
// No compile-time type checking
for (Object obj : list) {
String str = (String) obj; // ClassCastException!
System.out.println(str.toUpperCase());
}
}
}
Problems:
- No compile-time type checking
- Runtime ClassCastException
- Explicit casting required
- Code is not self-documenting
Solution with Generics
// Java 5+ code (with generics)
import java.util.*;
public class WithGenericsExample {
public static void main(String[] args) {
// Generic ArrayList
ArrayList list = new ArrayList();
list.add("Hello");
list.add("World");
// list.add(42); // Compile-time error!
// No explicit casting needed
for (String str : list) {
System.out.println(str.toUpperCase());
}
// Type-safe retrieval
String first = list.get(0); // No casting
}
}
Benefits:
- Compile-time type safety
- No ClassCastException
- No explicit casting
- Self-documenting code
- Better performance (no boxing/unboxing)
Generic Classes
Creating a Generic Class
// Generic Box class
public class Box {
private T content;
// Constructor
public Box(T content) {
this.content = content;
}
// Generic methods
public T getContent() {
return content;
}
public void setContent(T content) {
this.content = content;
}
// Method with generic parameter
public boolean isSameType(Box other) {
return this.content.getClass().equals(other.content.getClass());
}
@Override
public String toString() {
return "Box{content=" + content + ", type=" + content.getClass().getSimpleName() + "}";
}
}
// Usage examples
public class GenericClassDemo {
public static void main(String[] args) {
// Creating generic instances
Box stringBox = new Box<>("Hello Generics!");
Box integerBox = new Box<>(42);
Box doubleBox = new Box<>(3.14);
System.out.println("String box: " + stringBox);
System.out.println("Integer box: " + integerBox);
System.out.println("Double box: " + doubleBox);
// Type-safe operations
String str = stringBox.getContent(); // No casting needed
Integer num = integerBox.getContent();
// stringBox.setContent(123); // Compile-time error!
stringBox.setContent("Updated content"); // OK
// Comparing types
Box anotherStringBox = new Box<>("Another string");
System.out.println("Same type? " + stringBox.isSameType(anotherStringBox));
// Diamond operator (Java 7+)
Box diamondBox = new Box<>("Diamond operator");
System.out.println("Diamond box: " + diamondBox);
}
}
Multiple Type Parameters
// Generic class with multiple type parameters
public class Pair {
private T first;
private U second;
public Pair(T first, U second) {
this.first = first;
this.second = second;
}
// Getters and setters
public T getFirst() { return first; }
public void setFirst(T first) { this.first = first; }
public U getSecond() { return second; }
public void setSecond(U second) { this.second = second; }
// Utility method
public void swap() {
// This won't work because T and U might be different types
// We need bounded types or careful design
}
@Override
public String toString() {
return "Pair{first=" + first + ", second=" + second + "}";
}
// Static generic method
public static Pair of(T first, U second) {
return new Pair<>(first, second);
}
}
// Generic Triple class
public class Triple {
private T first;
private U second;
private V third;
public Triple(T first, U second, V third) {
this.first = first;
this.second = second;
this.third = third;
}
// Getters
public T getFirst() { return first; }
public U getSecond() { return second; }
public V getThird() { return third; }
@Override
public String toString() {
return String.format("Triple{%s, %s, %s}", first, second, third);
}
}
// Usage demonstration
public class MultipleTypeParametersDemo {
public static void main(String[] args) {
// Different type combinations
Pair nameAge = new Pair<>("Alice", 25);
Pair idName = new Pair<>(101, "Bob");
Pair scorePass = new Pair<>(85.5, true);
System.out.println("Name-Age: " + nameAge);
System.out.println("ID-Name: " + idName);
System.out.println("Score-Pass: " + scorePass);
// Using static factory method
Pair coordinates = Pair.of("X", "Y");
System.out.println("Coordinates: " + coordinates);
// Triple example
Triple studentRecord =
new Triple<>("Charlie", 22, 3.8);
System.out.println("Student: " + studentRecord);
// Nested generics
Pair> complexPair =
new Pair<>("Student", new Pair<>(20, 3.5));
System.out.println("Complex pair: " + complexPair);
}
}
Generic Methods
public class GenericMethods {
// Generic static method
public static void swap(T[] array, int i, int j) {
if (i >= 0 && j >= 0 && i < array.length && j < array.length) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
// Generic method with return type
public static T getMiddleElement(T[] array) {
if (array == null || array.length == 0) {
return null;
}
return array[array.length / 2];
}
// Generic method with multiple type parameters
public static boolean areEqual(T obj1, U obj2) {
return obj1 != null && obj1.equals(obj2);
}
// Generic method in non-generic class
public Box createBox(T content) {
return new Box<>(content);
}
// Generic method with bounded type parameter
public static double getAverage(T[] numbers) {
double sum = 0.0;
for (T num : numbers) {
sum += num.doubleValue();
}
return sum / numbers.length;
}
// Generic method with multiple bounds
public static > T findMax(T[] array) {
if (array == null || array.length == 0) {
return null;
}
T max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i].compareTo(max) > 0) {
max = array[i];
}
}
return max;
}
// Generic method for printing arrays
public static void printArray(String name, T[] array) {
System.out.print(name + ": [");
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]);
if (i < array.length - 1) System.out.print(", ");
}
System.out.println("]");
}
public static void main(String[] args) {
// Test generic methods
// String array operations
String[] names = {"Alice", "Bob", "Charlie", "Diana"};
printArray("Original names", names);
swap(names, 0, 2);
printArray("After swapping 0 and 2", names);
String middleName = getMiddleElement(names);
System.out.println("Middle name: " + middleName);
// Integer array operations
Integer[] numbers = {10, 20, 30, 40, 50};
printArray("Numbers", numbers);
Integer middleNumber = getMiddleElement(numbers);
System.out.println("Middle number: " + middleNumber);
double average = getAverage(numbers);
System.out.println("Average: " + average);
Integer maxNumber = findMax(numbers);
System.out.println("Max number: " + maxNumber);
// Double array
Double[] decimals = {3.14, 2.71, 1.41, 1.73};
Double maxDecimal = findMax(decimals);
System.out.println("Max decimal: " + maxDecimal);
// Equality testing
System.out.println("Are 'Hello' and 'Hello' equal? " + areEqual("Hello", "Hello"));
System.out.println("Are 42 and 42.0 equal? " + areEqual(42, 42.0));
// Creating boxes using generic method
GenericMethods gm = new GenericMethods();
Box stringBox = gm.createBox("Generic Method Created");
Box intBox = gm.createBox(100);
System.out.println("Created boxes:");
System.out.println(" " + stringBox);
System.out.println(" " + intBox);
}
}
Bounded Type Parameters
Upper Bounded Types
// Upper bounded with extends
class NumberBox {
private T value;
public NumberBox(T value) {
this.value = value;
}
public T getValue() {
return value;
}
// Can call Number methods
public double getDoubleValue() {
return value.doubleValue();
}
// Can call Number methods
public int getIntValue() {
return value.intValue();
}
// Comparison method
public boolean isGreaterThan(NumberBox other) {
return this.value.doubleValue() >
other.value.doubleValue();
}
}
// Usage
public class BoundedDemo {
public static void main(String[] args) {
NumberBox intBox =
new NumberBox<>(42);
NumberBox doubleBox =
new NumberBox<>(3.14);
System.out.println("Int value: " +
intBox.getDoubleValue());
System.out.println("Double value: " +
doubleBox.getIntValue());
// NumberBox stringBox =
// new NumberBox<>("Hello"); // Error!
}
}
Multiple Bounds
// Interface for comparison
interface Printable {
void print();
}
// Class implementing the interface
class PrintableNumber extends Number
implements Printable, Comparable {
private double value;
public PrintableNumber(double value) {
this.value = value;
}
@Override
public void print() {
System.out.println("Value: " + value);
}
@Override
public int compareTo(PrintableNumber other) {
return Double.compare(this.value, other.value);
}
@Override
public int intValue() { return (int) value; }
@Override
public long longValue() { return (long) value; }
@Override
public float floatValue() { return (float) value; }
@Override
public double doubleValue() { return value; }
@Override
public String toString() {
return String.valueOf(value);
}
}
// Generic class with multiple bounds
class AdvancedBox> {
private T content;
public AdvancedBox(T content) {
this.content = content;
}
public T getContent() {
return content;
}
public void printContent() {
content.print(); // From Printable
}
public double getNumericValue() {
return content.doubleValue(); // From Number
}
public boolean isGreaterThan(AdvancedBox other) {
return content.compareTo(other.content) > 0; // From Comparable
}
}
Wildcards in Generics
Unbounded Wildcards
import java.util.*;
public class WildcardsDemo {
// Unbounded wildcard - can accept any type
public static void printList(List list) {
for (Object item : list) {
System.out.print(item + " ");
}
System.out.println();
}
// Upper bounded wildcard - accepts Number and its subtypes
public static double sumNumbers(List numbers) {
double sum = 0.0;
for (Number num : numbers) {
sum += num.doubleValue();
}
return sum;
}
// Lower bounded wildcard - accepts Number and its supertypes
public static void addNumbers(List numbers) {
numbers.add(42);
numbers.add(3.14);
numbers.add(100L);
// numbers.add("Hello"); // Compile error - String is not Number or supertype
}
// Method demonstrating PECS principle
// Producer Extends, Consumer Super
public static void copy(List source, List destination) {
for (T item : source) {
destination.add(item);
}
}
public static void main(String[] args) {
System.out.println("=== Wildcards Demonstration ===");
// Different types of lists
List stringList = Arrays.asList("Hello", "World", "Generics");
List intList = Arrays.asList(1, 2, 3, 4, 5);
List doubleList = Arrays.asList(1.1, 2.2, 3.3);
// Unbounded wildcard usage
System.out.println("Printing different types of lists:");
printList(stringList);
printList(intList);
printList(doubleList);
// Upper bounded wildcard
System.out.println("Sum of integers: " + sumNumbers(intList));
System.out.println("Sum of doubles: " + sumNumbers(doubleList));
// System.out.println("Sum of strings: " + sumNumbers(stringList)); // Error!
// Lower bounded wildcard
List numberList = new ArrayList<>();
List Type Erasure and Limitations
What is Type Erasure?
- Generics are compile-time feature only
- Type information removed at runtime
- Ensures backward compatibility
- Raw types exist at runtime
// Before compilation
List stringList = new ArrayList();
List intList = new ArrayList();
// After type erasure (at runtime)
List stringList = new ArrayList();
List intList = new ArrayList();
// This is why this doesn't work:
public class ErasureExample {
// public void method(List list) { }
// public void method(List list) { }
// Compile error: same erasure!
// Workaround using bounded types
public void method(List list, String dummy) { }
public void method(List list, Integer dummy) { }
}
Limitations Due to Type Erasure
public class GenericLimitations {
// 1. Cannot create instance of type parameter
// T instance = new T(); // Error!
// 2. Cannot create array of generic type
// T[] array = new T[10]; // Error!
// 3. Cannot use primitive types
// List intList; // Error! Use List
// 4. Cannot use instanceof with parameterized types
public boolean checkType(Object obj) {
// return obj instanceof List; // Error!
return obj instanceof List; // OK, but loses type info
}
// 5. Cannot create generic exception classes
// class MyException extends Exception { } // Error!
// 6. Cannot have static fields of type T
// static T staticField; // Error!
// Workarounds
private Class clazz;
public GenericLimitations(Class clazz) {
this.clazz = clazz;
}
// Workaround for creating instances
public T createInstance() throws InstantiationException,
IllegalAccessException {
return clazz.newInstance();
}
// Workaround for arrays
@SuppressWarnings("unchecked")
public T[] createArray(int size) {
return (T[]) java.lang.reflect.Array.newInstance(clazz, size);
}
}
// Bridge methods example
class Parent {
public T getValue() { return null; }
public void setValue(T value) { }
}
class Child extends Parent {
@Override
public String getValue() { return "child"; }
@Override
public void setValue(String value) { }
// Compiler generates bridge methods:
// public Object getValue() { return getValue(); }
// public void setValue(Object value) { setValue((String) value); }
}
Generic Collections Best Practices
import java.util.*;
public class GenericCollectionsBestPractices {
public static void main(String[] args) {
demonstrateListGenerics();
demonstrateMapGenerics();
demonstrateSetGenerics();
demonstrateCollectionUtilities();
}
private static void demonstrateListGenerics() {
System.out.println("=== List Generics ===");
// Prefer interface types
List list = new ArrayList<>(); // Diamond operator
// Type-safe operations
list.add("Java");
list.add("Generics");
list.add("Collections");
// No casting needed
for (String item : list) {
System.out.println(item.toUpperCase());
}
// Generic methods with lists
Collections.sort(list);
String first = Collections.min(list);
System.out.println("First alphabetically: " + first);
}
private static void demonstrateMapGenerics() {
System.out.println("\n=== Map Generics ===");
Map> studentGrades = new HashMap<>();
// Adding data
studentGrades.put("Alice", Arrays.asList(85, 90, 88));
studentGrades.put("Bob", Arrays.asList(78, 82, 85));
studentGrades.put("Charlie", Arrays.asList(92, 95, 90));
// Type-safe iteration
for (Map.Entry> entry : studentGrades.entrySet()) {
String student = entry.getKey();
List grades = entry.getValue();
double average = grades.stream().mapToInt(Integer::intValue).average().orElse(0.0);
System.out.printf("%s: %s (avg: %.2f)%n", student, grades, average);
}
// Generic method calls
Set students = studentGrades.keySet();
Collection> allGrades = studentGrades.values();
System.out.println("Students: " + students);
System.out.println("Total grade lists: " + allGrades.size());
}
private static void demonstrateSetGenerics() {
System.out.println("\n=== Set Generics ===");
Set hashSet = new HashSet<>();
Set treeSet = new TreeSet<>();
String[] words = {"banana", "apple", "cherry", "date", "apple"};
Collections.addAll(hashSet, words);
Collections.addAll(treeSet, words);
System.out.println("HashSet (no order): " + hashSet);
System.out.println("TreeSet (sorted): " + treeSet);
// Set operations
Set vowelWords = new HashSet<>();
vowelWords.add("apple");
vowelWords.add("orange");
Set intersection = new HashSet<>(hashSet);
intersection.retainAll(vowelWords);
System.out.println("Intersection: " + intersection);
}
private static void demonstrateCollectionUtilities() {
System.out.println("\n=== Collection Utilities ===");
// Generic utility methods
List numbers = Arrays.asList(5, 2, 8, 1, 9, 3);
// Sorting
List sortedNumbers = new ArrayList<>(numbers);
Collections.sort(sortedNumbers);
System.out.println("Sorted: " + sortedNumbers);
// Reverse sorting
Collections.sort(sortedNumbers, Collections.reverseOrder());
System.out.println("Reverse sorted: " + sortedNumbers);
// Binary search
Collections.sort(sortedNumbers);
int index = Collections.binarySearch(sortedNumbers, 5);
System.out.println("Index of 5: " + index);
// Custom comparator with generics
List names = Arrays.asList("Alice", "Bob", "Charlie", "David");
Collections.sort(names, Comparator.comparing(String::length));
System.out.println("Sorted by length: " + names);
// Frequency counting
List letters = Arrays.asList("a", "b", "a", "c", "b", "a");
int frequency = Collections.frequency(letters, "a");
System.out.println("Frequency of 'a': " + frequency);
// Type-safe empty collections
List emptyList = Collections.emptyList();
Set emptySet = Collections.emptySet();
Map emptyMap = Collections.emptyMap();
// Immutable collections (Java 9+) or using Collections.unmodifiableXxx
List immutableList = Collections.unmodifiableList(names);
System.out.println("Immutable list: " + immutableList);
}
}
GTU Previous Year Question (Summer 2022)
Q: Write a Java program to demonstrate the use of generics. Create a generic class 'Container' that can store any type of object. Implement methods for adding, removing, and retrieving objects. Also demonstrate bounded type parameters with an example.
Solution:
import java.util.*;
// Generic Container class
class Container {
private List items;
private int maxSize;
// Constructor
public Container(int maxSize) {
this.maxSize = maxSize;
this.items = new ArrayList<>();
}
// Default constructor with unlimited size
public Container() {
this(-1); // -1 indicates no size limit
}
// Add item to container
public boolean add(T item) {
if (maxSize > 0 && items.size() >= maxSize) {
System.out.println("Container is full! Cannot add: " + item);
return false;
}
items.add(item);
System.out.println("Added: " + item);
return true;
}
// Add item at specific index
public boolean add(int index, T item) {
if (maxSize > 0 && items.size() >= maxSize) {
System.out.println("Container is full! Cannot add: " + item);
return false;
}
if (index < 0 || index > items.size()) {
System.out.println("Invalid index: " + index);
return false;
}
items.add(index, item);
System.out.println("Added at index " + index + ": " + item);
return true;
}
// Remove item by object
public boolean remove(T item) {
boolean removed = items.remove(item);
if (removed) {
System.out.println("Removed: " + item);
} else {
System.out.println("Item not found: " + item);
}
return removed;
}
// Remove item by index
public T remove(int index) {
if (index < 0 || index >= items.size()) {
System.out.println("Invalid index: " + index);
return null;
}
T removedItem = items.remove(index);
System.out.println("Removed from index " + index + ": " + removedItem);
return removedItem;
}
// Retrieve item by index
public T get(int index) {
if (index < 0 || index >= items.size()) {
System.out.println("Invalid index: " + index);
return null;
}
return items.get(index);
}
// Check if container contains item
public boolean contains(T item) {
return items.contains(item);
}
// Get size
public int size() {
return items.size();
}
// Check if empty
public boolean isEmpty() {
return items.isEmpty();
}
// Clear all items
public void clear() {
items.clear();
System.out.println("Container cleared");
}
// Get all items
public List getAllItems() {
return new ArrayList<>(items); // Return copy for safety
}
// Display all items
public void display() {
if (items.isEmpty()) {
System.out.println("Container is empty");
} else {
System.out.println("Container contents: " + items);
}
}
// Generic method to find first item matching predicate
public T findFirst(Predicate predicate) {
for (T item : items) {
if (predicate.test(item)) {
return item;
}
}
return null;
}
@Override
public String toString() {
return "Container{size=" + items.size() +
", maxSize=" + (maxSize > 0 ? maxSize : "unlimited") +
", items=" + items + "}";
}
}
// Functional interface for demonstration
@FunctionalInterface
interface Predicate {
boolean test(T item);
}
Bounded Type Parameters Example:
// Bounded container for numeric types only
class NumericContainer {
private List numbers;
public NumericContainer() {
this.numbers = new ArrayList<>();
}
// Add number
public void add(T number) {
numbers.add(number);
System.out.println("Added number: " + number);
}
// Calculate sum using Number methods
public double getSum() {
double sum = 0.0;
for (T number : numbers) {
sum += number.doubleValue(); // Can call Number methods
}
return sum;
}
// Calculate average
public double getAverage() {
if (numbers.isEmpty()) return 0.0;
return getSum() / numbers.size();
}
// Find maximum value
public T getMax() {
if (numbers.isEmpty()) return null;
T max = numbers.get(0);
for (T number : numbers) {
if (number.doubleValue() > max.doubleValue()) {
max = number;
}
}
return max;
}
// Find minimum value
public T getMin() {
if (numbers.isEmpty()) return null;
T min = numbers.get(0);
for (T number : numbers) {
if (number.doubleValue() < min.doubleValue()) {
min = number;
}
}
return min;
}
// Get all numbers
public List getNumbers() {
return new ArrayList<>(numbers);
}
// Display statistics
public void displayStatistics() {
System.out.println("=== Numeric Container Statistics ===");
System.out.println("Count: " + numbers.size());
if (!numbers.isEmpty()) {
System.out.printf("Sum: %.2f%n", getSum());
System.out.printf("Average: %.2f%n", getAverage());
System.out.println("Maximum: " + getMax());
System.out.println("Minimum: " + getMin());
}
System.out.println("Numbers: " + numbers);
}
@Override
public String toString() {
return "NumericContainer{count=" + numbers.size() + ", numbers=" + numbers + "}";
}
}
// Comparable container for sortable types
class SortableContainer> {
private List items;
public SortableContainer() {
this.items = new ArrayList<>();
}
// Add item
public void add(T item) {
items.add(item);
System.out.println("Added to sortable container: " + item);
}
// Sort items using natural ordering
public void sort() {
Collections.sort(items);
System.out.println("Container sorted");
}
// Get sorted items without modifying original
public List getSorted() {
List sorted = new ArrayList<>(items);
Collections.sort(sorted);
return sorted;
}
// Find item using binary search (requires sorted container)
public int binarySearch(T item) {
sort(); // Ensure sorted first
return Collections.binarySearch(items, item);
}
// Get items
public List getItems() {
return new ArrayList<>(items);
}
@Override
public String toString() {
return "SortableContainer{items=" + items + "}";
}
}
Complete Demonstration:
public class GenericContainerDemo {
public static void main(String[] args) {
System.out.println("=== Generic Container Demonstration ===\n");
// Demonstrate basic generic container
demonstrateBasicContainer();
// Demonstrate bounded numeric container
demonstrateNumericContainer();
// Demonstrate sortable container
demonstrateSortableContainer();
}
private static void demonstrateBasicContainer() {
System.out.println("--- Basic Generic Container ---");
// String container
Container stringContainer = new Container<>(5); // Max 5 items
stringContainer.add("Hello");
stringContainer.add("Generics");
stringContainer.add("World");
stringContainer.add(1, "Java"); // Insert at index 1
stringContainer.display();
System.out.println("Contains 'Java': " + stringContainer.contains("Java"));
System.out.println("Item at index 2: " + stringContainer.get(2));
stringContainer.remove("World");
stringContainer.display();
// Integer container
Container intContainer = new Container<>(); // Unlimited size
intContainer.add(10);
intContainer.add(20);
intContainer.add(30);
intContainer.add(40);
System.out.println("Integer container: " + intContainer);
// Find first even number
Integer firstEven = intContainer.findFirst(num -> num % 2 == 0);
System.out.println("First even number: " + firstEven);
// Student container (custom objects)
Container studentContainer = new Container<>();
studentContainer.add(new Student("Alice", 85));
studentContainer.add(new Student("Bob", 92));
studentContainer.add(new Student("Charlie", 78));
System.out.println("Student container:");
studentContainer.display();
System.out.println();
}
private static void demonstrateNumericContainer() {
System.out.println("--- Bounded Numeric Container ---");
// Integer numeric container
NumericContainer intNumbers = new NumericContainer<>();
intNumbers.add(10);
intNumbers.add(25);
intNumbers.add(15);
intNumbers.add(30);
intNumbers.displayStatistics();
// Double numeric container
NumericContainer doubleNumbers = new NumericContainer<>();
doubleNumbers.add(3.14);
doubleNumbers.add(2.71);
doubleNumbers.add(1.41);
doubleNumbers.displayStatistics();
// Cannot create NumericContainer - compile error!
// NumericContainer stringNumbers = new NumericContainer<>(); // Error!
System.out.println();
}
private static void demonstrateSortableContainer() {
System.out.println("--- Sortable Container ---");
// String sortable container
SortableContainer words = new SortableContainer<>();
words.add("Zebra");
words.add("Apple");
words.add("Mango");
words.add("Banana");
System.out.println("Before sorting: " + words.getItems());
System.out.println("Sorted items: " + words.getSorted());
words.sort();
System.out.println("After sorting: " + words.getItems());
int index = words.binarySearch("Mango");
System.out.println("Index of 'Mango': " + index);
// Integer sortable container
SortableContainer numbers = new SortableContainer<>();
numbers.add(42);
numbers.add(17);
numbers.add(35);
numbers.add(8);
System.out.println("Numbers before sorting: " + numbers.getItems());
numbers.sort();
System.out.println("Numbers after sorting: " + numbers.getItems());
System.out.println();
}
}
// Helper class for demonstration
class Student {
private String name;
private int grade;
public Student(String name, int grade) {
this.name = name;
this.grade = grade;
}
public String getName() { return name; }
public int getGrade() { return grade; }
@Override
public String toString() {
return "Student{name='" + name + "', grade=" + grade + "}";
}
}
Solution Features:
- Complete generic Container class with type safety
- Bounded NumericContainer for Number subtypes only
- SortableContainer for Comparable types
- Comprehensive CRUD operations and utility methods
- Real-world examples with different data types
- Demonstrates compile-time type checking benefits
🧪 Hands-on Lab Exercise
Lab 18: Generic Data Processing Pipeline
Task: Create a generic data processing pipeline that can transform, filter, and aggregate data of any type using generics, bounded parameters, and wildcards.
Requirements:
- Create a generic
Pipeline<T>class with transformation methods - Implement generic
Transformer<T, R>andPredicate<T>interfaces - Use bounded type parameters for numeric aggregations
- Implement wildcard methods for pipeline composition
- Create a generic cache system with type-safe operations
- Demonstrate pipeline chaining with different data types
Challenge: Implement a generic repository pattern with CRUD operations that maintains type safety across different entity types.
📚 Lecture Summary
Key Concepts Covered
- Generic classes and interfaces
- Generic methods and constructors
- Bounded type parameters (extends)
- Wildcards (?, extends, super)
- Type erasure and limitations
- Generic collections best practices
Best Practices
- Use generics for type safety
- Prefer bounded types when constraints are needed
- Apply PECS principle for wildcards
- Use diamond operator for cleaner code
- Avoid raw types in new code
- Understand type erasure limitations
🎯 Next Lecture Preview
Lecture 19: Input/Output and File Handling
- Stream classes and I/O hierarchy
- File and directory operations
- Character vs byte streams
- Serialization and deserialization

