Table of Contents
- What are Generics?
- Why Use Generics?
- Generic Classes
- Generic Methods
- Generic Interfaces
- Type Parameters Naming Conventions
- Bounded Type Parameters
- Wildcards
- Type Erasure
- Best Practices
- Common Examples
- Complete Examples
What are Generics?
Generics enable types (classes and interfaces) to be parameters when defining classes, interfaces, and methods. They provide stronger type checks at compile time and eliminate the need for casting.
Before Generics (Java 1.4 and earlier):
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // Requires explicit casting
With Generics (Java 5+):
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // No casting needed
Why Use Generics?
Benefits:
- Type Safety: Compile-time type checking
- Eliminates Casting: No need for explicit type casting
- Code Reusability: Write once, use with different types
- Better Readability: Clear about what type of objects are stored
Problem Without Generics:
import java.util.*;
public class WithoutGenerics {
public static void main(String[] args) {
// Without generics - can add any type
List list = new ArrayList();
list.add("String");
list.add(123); // Integer
list.add(new Date()); // Date object
// Runtime ClassCastException possible
String str = (String) list.get(1); // Throws ClassCastException!
}
}
Solution With Generics:
import java.util.*;
public class WithGenerics {
public static void main(String[] args) {
// With generics - type safe
List<String> list = new ArrayList<>();
list.add("String");
// list.add(123); // Compile-time error!
// list.add(new Date()); // Compile-time error!
String str = list.get(0); // No casting needed
}
}
Generic Classes
Basic Generic Class:
// Generic class with single type parameter
class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
@Override
public String toString() {
return "Box containing: " + content;
}
}
public class GenericClassExample {
public static void main(String[] args) {
// Box for String
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello Generics!");
System.out.println(stringBox);
// Box for Integer
Box<Integer> integerBox = new Box<>();
integerBox.setContent(42);
System.out.println(integerBox);
// Box for custom object
Box<Date> dateBox = new Box<>();
dateBox.setContent(new Date());
System.out.println(dateBox);
}
}
Output:
Box containing: Hello Generics! Box containing: 42 Box containing: Wed Jan 10 14:30:00 IST 2024
Multiple Type Parameters:
// Generic class with multiple type parameters
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
public void setKey(K key) { this.key = key; }
public void setValue(V value) { this.value = value; }
@Override
public String toString() {
return "Pair{key=" + key + ", value=" + value + "}";
}
}
public class MultipleTypeParameters {
public static void main(String[] args) {
// String-Integer pair
Pair<String, Integer> nameAge = new Pair<>("Alice", 25);
System.out.println(nameAge);
// Integer-String pair
Pair<Integer, String> idName = new Pair<>(101, "Bob");
System.out.println(idName);
// String-String pair
Pair<String, String> countryCapital = new Pair<>("France", "Paris");
System.out.println(countryCapital);
// Custom types
Pair<String, List<Integer>> scores = new Pair<>("Math", Arrays.asList(85, 90, 78));
System.out.println(scores);
}
}
Output:
Pair{key=Alice, value=25}
Pair{key=101, value=Bob}
Pair{key=France, value=Paris}
Pair{key=Math, value=[85, 90, 78]}
Generic Methods
Basic Generic Methods:
public class GenericMethods {
// Generic method in non-generic class
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
// Generic method with multiple type parameters
public static <T, U> void printPair(T first, U second) {
System.out.println("First: " + first + ", Second: " + second);
}
// Generic method returning generic type
public static <T> T getFirstElement(T[] array) {
if (array == null || array.length == 0) {
return null;
}
return array[0];
}
public static void main(String[] args) {
// Using generic methods with different types
Integer[] intArray = {1, 2, 3, 4, 5};
String[] strArray = {"A", "B", "C", "D"};
Double[] doubleArray = {1.1, 2.2, 3.3};
System.out.print("Integer array: ");
printArray(intArray);
System.out.print("String array: ");
printArray(strArray);
System.out.print("Double array: ");
printArray(doubleArray);
// Using multiple type parameters
printPair("Hello", 42);
printPair(3.14, "Pi");
printPair(true, new Date());
// Getting first element
System.out.println("First string: " + getFirstElement(strArray));
System.out.println("First integer: " + getFirstElement(intArray));
}
}
Output:
Integer array: 1 2 3 4 5 String array: A B C D Double array: 1.1 2.2 3.3 First: Hello, Second: 42 First: 3.14, Second: Pi First: true, Second: Wed Jan 10 14:30:00 IST 2024 First string: A First integer: 1
Generic Interfaces
Generic Interface Examples:
// Generic interface
interface Repository<T> {
void save(T entity);
T findById(int id);
List<T> findAll();
void delete(int id);
}
// Implementation for User
class UserRepository implements Repository<User> {
private List<User> users = new ArrayList<>();
private int nextId = 1;
@Override
public void save(User user) {
user.setId(nextId++);
users.add(user);
}
@Override
public User findById(int id) {
return users.stream()
.filter(user -> user.getId() == id)
.findFirst()
.orElse(null);
}
@Override
public List<User> findAll() {
return new ArrayList<>(users);
}
@Override
public void delete(int id) {
users.removeIf(user -> user.getId() == id);
}
}
// Implementation for Product
class ProductRepository implements Repository<Product> {
private Map<Integer, Product> products = new HashMap<>();
private int nextId = 1;
@Override
public void save(Product product) {
product.setId(nextId);
products.put(nextId++, product);
}
@Override
public Product findById(int id) {
return products.get(id);
}
@Override
public List<Product> findAll() {
return new ArrayList<>(products.values());
}
@Override
public void delete(int id) {
products.remove(id);
}
}
// Supporting classes
class User {
private int id;
private String name;
private String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
// Getters and setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public String getEmail() { return email; }
@Override
public String toString() {
return String.format("User{id=%d, name='%s', email='%s'}", id, name, email);
}
}
class Product {
private int id;
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
// Getters and setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public double getPrice() { return price; }
@Override
public String toString() {
return String.format("Product{id=%d, name='%s', price=$%.2f}", id, name, price);
}
}
public class GenericInterfaceExample {
public static void main(String[] args) {
// User repository
Repository<User> userRepo = new UserRepository();
userRepo.save(new User("Alice", "[email protected]"));
userRepo.save(new User("Bob", "[email protected]"));
System.out.println("All users:");
userRepo.findAll().forEach(System.out::println);
// Product repository
Repository<Product> productRepo = new ProductRepository();
productRepo.save(new Product("Laptop", 999.99));
productRepo.save(new Product("Mouse", 25.99));
System.out.println("\nAll products:");
productRepo.findAll().forEach(System.out::println);
// Find by ID
System.out.println("\nUser with ID 1: " + userRepo.findById(1));
System.out.println("Product with ID 2: " + productRepo.findById(2));
}
}
Output:
All users:
User{id=1, name='Alice', email='[email protected]'}
User{id=2, name='Bob', email='[email protected]'}
All products:
Product{id=1, name='Laptop', price=$999.99}
Product{id=2, name='Mouse', price=$25.99}
User with ID 1: User{id=1, name='Alice', email='[email protected]'}
Product with ID 2: Product{id=2, name='Mouse', price=$25.99}
Type Parameters Naming Conventions
Standard Naming Conventions:
- E - Element (used extensively by Java Collections Framework)
- K - Key
- N - Number
- T - Type
- V - Value
- S, U, V - 2nd, 3rd, 4th types
Examples:
// Following naming conventions
class Container<T> { } // Single type
class Pair<K, V> { } // Key-Value pair
class Calculator<N extends Number> { } // Number type
class Processor<E> { } // Element type
// Self-referential generic type
class Node<T> {
private T data;
private Node<T> next; // Same type parameter
public Node(T data) {
this.data = data;
}
public T getData() { return data; }
public Node<T> getNext() { return next; }
public void setNext(Node<T> next) { this.next = next; }
}
Bounded Type Parameters
Upper Bounded Type Parameters:
import java.util.*;
// Bounded type parameter - only accepts Number and its subclasses
class Statistics<T extends Number> {
private List<T> numbers;
public Statistics() {
numbers = new ArrayList<>();
}
public void addNumber(T number) {
numbers.add(number);
}
public double getAverage() {
if (numbers.isEmpty()) {
return 0.0;
}
double sum = 0.0;
for (T number : numbers) {
sum += number.doubleValue(); // Can call Number methods
}
return sum / numbers.size();
}
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;
}
public List<T> getNumbers() {
return new ArrayList<>(numbers);
}
}
public class BoundedTypesExample {
public static void main(String[] args) {
// Works with Integer (subclass of Number)
Statistics<Integer> intStats = new Statistics<>();
intStats.addNumber(10);
intStats.addNumber(20);
intStats.addNumber(30);
System.out.println("Integer stats: " + intStats.getNumbers());
System.out.println("Average: " + intStats.getAverage());
System.out.println("Max: " + intStats.getMax());
// Works with Double (subclass of Number)
Statistics<Double> doubleStats = new Statistics<>();
doubleStats.addNumber(15.5);
doubleStats.addNumber(25.7);
doubleStats.addNumber(35.2);
System.out.println("\nDouble stats: " + doubleStats.getNumbers());
System.out.println("Average: " + doubleStats.getAverage());
System.out.println("Max: " + doubleStats.getMax());
// Won't compile - String is not a subclass of Number
// Statistics<String> stringStats = new Statistics<>(); // Compile error!
}
}
Output:
Integer stats: [10, 20, 30] Average: 20.0 Max: 30 Double stats: [15.5, 25.7, 35.2] Average: 25.466666666666665 Max: 35.2
Multiple Bounds:
interface Loggable {
void log();
}
interface Serializable {
void serialize();
}
class Document implements Loggable, Serializable {
private String content;
public Document(String content) {
this.content = content;
}
@Override
public void log() {
System.out.println("Logging document: " + content);
}
@Override
public void serialize() {
System.out.println("Serializing document: " + content);
}
@Override
public String toString() {
return "Document: " + content;
}
}
// Multiple bounds - T must implement both interfaces
class Processor<T extends Loggable & Serializable> {
private T item;
public Processor(T item) {
this.item = item;
}
public void process() {
item.log();
item.serialize();
System.out.println("Processing: " + item);
}
}
public class MultipleBoundsExample {
public static void main(String[] args) {
Document doc = new Document("Important data");
Processor<Document> processor = new Processor<>(doc);
processor.process();
}
}
Output:
Logging document: Important data Serializing document: Important data Processing: Document: Important data
Wildcards
Upper Bounded Wildcards:
import java.util.*;
public class UpperBoundedWildcards {
// Accepts List of any type that is Number or subclass of Number
public static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number number : list) {
sum += number.doubleValue();
}
return sum;
}
public static void main(String[] args) {
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5);
List<Float> floats = Arrays.asList(1.1f, 2.2f, 3.3f);
System.out.println("Sum of integers: " + sumOfList(integers));
System.out.println("Sum of doubles: " + sumOfList(doubles));
System.out.println("Sum of floats: " + sumOfList(floats));
// Won't compile - String is not a Number
// List<String> strings = Arrays.asList("A", "B", "C");
// System.out.println(sumOfList(strings)); // Compile error!
}
}
Output:
Sum of integers: 15.0 Sum of doubles: 16.5 Sum of floats: 6.6000001430511475
Lower Bounded Wildcards:
import java.util.*;
public class LowerBoundedWildcards {
// Accepts List of Integer or superclasses of Integer
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 5; i++) {
list.add(i);
}
}
public static void main(String[] args) {
List<Integer> integerList = new ArrayList<>();
List<Number> numberList = new ArrayList<>();
List<Object> objectList = new ArrayList<>();
addNumbers(integerList);
addNumbers(numberList);
addNumbers(objectList);
System.out.println("Integer list: " + integerList);
System.out.println("Number list: " + numberList);
System.out.println("Object list: " + objectList);
// Won't compile - String is not a superclass of Integer
// List<String> stringList = new ArrayList<>();
// addNumbers(stringList); // Compile error!
}
}
Output:
Integer list: [1, 2, 3, 4, 5] Number list: [1, 2, 3, 4, 5] Object list: [1, 2, 3, 4, 5]
Unbounded Wildcards:
import java.util.*;
public class UnboundedWildcards {
// Accepts List of any type
public static void printList(List<?> list) {
for (Object element : list) {
System.out.print(element + " ");
}
System.out.println();
}
public static int countElements(List<?> list) {
return list.size();
}
public static void main(String[] args) {
List<String> strings = Arrays.asList("A", "B", "C");
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
System.out.print("Strings: ");
printList(strings);
System.out.print("Integers: ");
printList(integers);
System.out.print("Doubles: ");
printList(doubles);
System.out.println("Count of strings: " + countElements(strings));
System.out.println("Count of integers: " + countElements(integers));
System.out.println("Count of doubles: " + countElements(doubles));
}
}
Output:
Strings: A B C Integers: 1 2 3 4 5 Doubles: 1.1 2.2 3.3 Count of strings: 3 Count of integers: 5 Count of doubles: 3
Type Erasure
How Generics Work at Runtime:
import java.util.*;
import java.lang.reflect.*;
public class TypeErasureExample {
public static void main(String[] args) {
// At compile time - different types
List<String> stringList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
// At runtime - both are just List (type erasure)
System.out.println("stringList class: " + stringList.getClass());
System.out.println("integerList class: " + integerList.getClass());
System.out.println("Same class? " +
(stringList.getClass() == integerList.getClass()));
// Type information is not available at runtime
try {
Method addMethod = ArrayList.class.getMethod("add", Object.class);
System.out.println("Method parameter type: " +
addMethod.getParameterTypes()[0]);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Output:
stringList class: class java.util.ArrayList integerList class: class java.util.ArrayList Same class? true Method parameter type: class java.lang.Object
Best Practices
1. Use Generic Types Instead of Raw Types:
// ✅ Good - type safe List<String> names = new ArrayList<>(); Map<String, Integer> scores = new HashMap<>(); // ❌ Bad - raw types List names = new ArrayList(); // Raw type warning Map scores = new HashMap(); // Raw type warning
2. Prefer Generic Methods:
// ✅ Good - generic method
public static <T> T getFirst(List<T> list) {
return list.isEmpty() ? null : list.get(0);
}
// ❌ Bad - uses raw types
public static Object getFirstRaw(List list) {
return list.isEmpty() ? null : list.get(0);
}
3. Use Bounded Wildcards for Maximum Flexibility:
// ✅ Good - accepts any List of Number or subclass
public static double sum(List<? extends Number> numbers) {
return numbers.stream().mapToDouble(Number::doubleValue).sum();
}
// ❌ Less flexible - only accepts exact List<Number>
public static double sumExact(List<Number> numbers) {
return numbers.stream().mapToDouble(Number::doubleValue).sum();
}
4. Avoid Using Generic Array Creation:
// ❌ Bad - generic array creation
// T[] array = new T[10]; // Compile error!
// ✅ Good - use ArrayList or other collection
List<T> list = new ArrayList<>();
// ✅ Alternative - use Array.newInstance with Class<T>
public static <T> T[] createArray(Class<T> clazz, int size) {
@SuppressWarnings("unchecked")
T[] array = (T[]) Array.newInstance(clazz, size);
return array;
}
Common Examples
1. Generic Stack Implementation:
import java.util.*;
class GenericStack<T> {
private List<T> elements;
public GenericStack() {
elements = new ArrayList<>();
}
public void push(T element) {
elements.add(element);
}
public T pop() {
if (isEmpty()) {
throw new EmptyStackException();
}
return elements.remove(elements.size() - 1);
}
public T peek() {
if (isEmpty()) {
throw new EmptyStackException();
}
return elements.get(elements.size() - 1);
}
public boolean isEmpty() {
return elements.isEmpty();
}
public int size() {
return elements.size();
}
@Override
public String toString() {
return "Stack: " + elements;
}
}
public class StackExample {
public static void main(String[] args) {
// String stack
GenericStack<String> stringStack = new GenericStack<>();
stringStack.push("First");
stringStack.push("Second");
stringStack.push("Third");
System.out.println(stringStack);
System.out.println("Popped: " + stringStack.pop());
System.out.println("Top: " + stringStack.peek());
System.out.println(stringStack);
// Integer stack
GenericStack<Integer> intStack = new GenericStack<>();
intStack.push(10);
intStack.push(20);
intStack.push(30);
System.out.println("\n" + intStack);
System.out.println("Popped: " + intStack.pop());
System.out.println("Size: " + intStack.size());
}
}
Output:
Stack: [First, Second, Third] Popped: Third Top: Second Stack: [First, Second] Stack: [10, 20, 30] Popped: 30 Size: 2
2. Generic Utility Methods:
import java.util.*;
public class GenericUtilities {
// Swap elements in an array
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
// Find maximum element in collection
public static <T extends Comparable<T>> T findMax(Collection<T> collection) {
if (collection.isEmpty()) {
throw new IllegalArgumentException("Collection is empty");
}
T max = null;
for (T element : collection) {
if (max == null || element.compareTo(max) > 0) {
max = element;
}
}
return max;
}
// Convert array to list
public static <T> List<T> arrayToList(T[] array) {
return new ArrayList<>(Arrays.asList(array));
}
// Check if array contains element
public static <T> boolean contains(T[] array, T element) {
for (T item : array) {
if (Objects.equals(item, element)) {
return true;
}
}
return false;
}
public static void main(String[] args) {
// Swap test
Integer[] numbers = {1, 2, 3, 4, 5};
System.out.println("Before swap: " + Arrays.toString(numbers));
swap(numbers, 1, 3);
System.out.println("After swap: " + Arrays.toString(numbers));
// Find max test
List<Integer> numberList = Arrays.asList(5, 2, 8, 1, 9, 3);
System.out.println("Max number: " + findMax(numberList));
List<String> stringList = Arrays.asList("apple", "zebra", "banana", "cherry");
System.out.println("Max string: " + findMax(stringList));
// Array to list test
String[] stringArray = {"A", "B", "C"};
List<String> convertedList = arrayToList(stringArray);
System.out.println("Converted list: " + convertedList);
// Contains test
System.out.println("Contains 'B': " + contains(stringArray, "B"));
System.out.println("Contains 'X': " + contains(stringArray, "X"));
}
}
Output:
Before swap: [1, 2, 3, 4, 5] After swap: [1, 4, 3, 2, 5] Max number: 9 Max string: zebra Converted list: [A, B, C] Contains 'B': true Contains 'X': false
Complete Examples
Complete Working Example:
import java.util.*;
import java.util.function.Predicate;
public class GenericsCompleteExample {
public static void main(String[] args) {
System.out.println("=== Generics Complete Example ===\n");
// Example 1: Generic Container
genericContainerExample();
// Example 2: Generic Repository Pattern
genericRepositoryExample();
// Example 3: Generic Algorithms
genericAlgorithmsExample();
// Example 4: Advanced - Generic Builder
genericBuilderExample();
}
public static void genericContainerExample() {
System.out.println("1. Generic Container Example:");
// Generic container for different types
Container<String> stringContainer = new Container<>("Hello World");
Container<Integer> intContainer = new Container<>(42);
Container<Date> dateContainer = new Container<>(new Date());
System.out.println("String container: " + stringContainer);
System.out.println("Integer container: " + intContainer);
System.out.println("Date container: " + dateContainer);
// Generic pair
Pair<String, Integer> nameAge = new Pair<>("Alice", 25);
Pair<Integer, String> idName = new Pair<>(101, "Bob");
System.out.println("Name-Age pair: " + nameAge);
System.out.println("ID-Name pair: " + idName);
System.out.println();
}
public static void genericRepositoryExample() {
System.out.println("2. Generic Repository Pattern:");
// User repository
CrudRepository<User> userRepo = new InMemoryCrudRepository<>();
userRepo.save(new User(1, "Alice", "[email protected]"));
userRepo.save(new User(2, "Bob", "[email protected]"));
userRepo.save(new User(3, "Charlie", "[email protected]"));
System.out.println("All users:");
userRepo.findAll().forEach(System.out::println);
System.out.println("User with ID 2: " + userRepo.findById(2));
// Product repository
CrudRepository<Product> productRepo = new InMemoryCrudRepository<>();
productRepo.save(new Product(1, "Laptop", 999.99));
productRepo.save(new Product(2, "Mouse", 25.99));
System.out.println("\nAll products:");
productRepo.findAll().forEach(System.out::println);
System.out.println();
}
public static void genericAlgorithmsExample() {
System.out.println("3. Generic Algorithms Example:");
// Filter example
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenNumbers = filter(numbers, n -> n % 2 == 0);
System.out.println("Even numbers: " + evenNumbers);
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
List<String> longNames = filter(names, name -> name.length() > 4);
System.out.println("Long names: " + longNames);
// Map example
List<String> upperCaseNames = map(names, String::toUpperCase);
System.out.println("Uppercase names: " + upperCaseNames);
List<Integer> squaredNumbers = map(numbers, n -> n * n);
System.out.println("Squared numbers: " + squaredNumbers);
System.out.println();
}
public static void genericBuilderExample() {
System.out.println("4. Generic Builder Example:");
// Build different types of objects
Person person = new PersonBuilder()
.withName("John Doe")
.withAge(30)
.withEmail("[email protected]")
.build();
Product product = new ProductBuilder()
.withName("Smartphone")
.withPrice(699.99)
.withCategory("Electronics")
.build();
System.out.println("Built person: " + person);
System.out.println("Built product: " + product);
}
// Generic filter method
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for (T item : list) {
if (predicate.test(item)) {
result.add(item);
}
}
return result;
}
// Generic map method
public static <T, R> List<R> map(List<T> list, java.util.function.Function<T, R> mapper) {
List<R> result = new ArrayList<>();
for (T item : list) {
result.add(mapper.apply(item));
}
return result;
}
}
// Supporting generic classes and interfaces
// 1. Generic Container
class Container<T> {
private T value;
public Container(T value) {
this.value = value;
}
public T getValue() { return value; }
public void setValue(T value) { this.value = value; }
@Override
public String toString() {
return "Container{" + value + "}";
}
}
// 2. Generic Pair
class Pair<K, V> {
private K key;
private V value;
public 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 "Pair{" + key + "=" + value + "}";
}
}
// 3. Generic Repository
interface CrudRepository<T> {
void save(T entity);
T findById(int id);
List<T> findAll();
void delete(int id);
}
class InMemoryCrudRepository<T> implements CrudRepository<T> {
private Map<Integer, T> storage = new HashMap<>();
private int nextId = 1;
@Override
public void save(T entity) {
// In real implementation, we'd use reflection to set ID
// For demo, we'll assume entity has setId method
storage.put(nextId++, entity);
}
@Override
public T findById(int id) {
return storage.get(id);
}
@Override
public List<T> findAll() {
return new ArrayList<>(storage.values());
}
@Override
public void delete(int id) {
storage.remove(id);
}
}
// 4. Entity classes
class User {
private int id;
private String name;
private String email;
public User(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
// Getters
public int getId() { return id; }
public String getName() { return name; }
public String getEmail() { return email; }
@Override
public String toString() {
return String.format("User{id=%d, name='%s', email='%s'}", id, name, email);
}
}
class Product {
private int id;
private String name;
private double price;
private String category;
public Product(int id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
}
public Product(int id, String name, double price, String category) {
this.id = id;
this.name = name;
this.price = price;
this.category = category;
}
// Getters
public int getId() { return id; }
public String getName() { return name; }
public double getPrice() { return price; }
public String getCategory() { return category; }
@Override
public String toString() {
return String.format("Product{id=%d, name='%s', price=$%.2f, category='%s'}",
id, name, price, category);
}
}
// 5. Generic Builder pattern
class Person {
private String name;
private int age;
private String email;
private Person(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
@Override
public String toString() {
return String.format("Person{name='%s', age=%d, email='%s'}", name, age, email);
}
// Builder
static class PersonBuilder {
private String name;
private int age;
private String email;
public PersonBuilder withName(String name) {
this.name = name;
return this;
}
public PersonBuilder withAge(int age) {
this.age = age;
return this;
}
public PersonBuilder withEmail(String email) {
this.email = email;
return this;
}
public Person build() {
return new Person(name, age, email);
}
}
}
class ProductBuilder {
private String name;
private double price;
private String category;
public ProductBuilder withName(String name) {
this.name = name;
return this;
}
public ProductBuilder withPrice(double price) {
this.price = price;
return this;
}
public ProductBuilder withCategory(String category) {
this.category = category;
return this;
}
public Product build() {
return new Product(0, name, price, category); // ID would be set by repository
}
}
Expected Output:
=== Generics Complete Example ===
1. Generic Container Example:
String container: Container{Hello World}
Integer container: Container{42}
Date container: Container{Wed Jan 10 14:30:00 IST 2024}
Name-Age pair: Pair{Alice=25}
ID-Name pair: Pair{101=Bob}
2. Generic Repository Pattern:
All users:
User{id=1, name='Alice', email='[email protected]'}
User{id=2, name='Bob', email='[email protected]'}
User{id=3, name='Charlie', email='[email protected]'}
User with ID 2: User{id=2, name='Bob', email='[email protected]'}
All products:
Product{id=1, name='Laptop', price=$999.99, category='null'}
Product{id=2, name='Mouse', price=$25.99, category='null'}
3. Generic Algorithms Example:
Even numbers: [2, 4, 6, 8, 10]
Long names: [Alice, Charlie, David]
Uppercase names: [ALICE, BOB, CHARLIE, DAVID, EVE]
Squared numbers: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
4. Generic Builder Example:
Built person: Person{name='John Doe', age=30, email='[email protected]'}
Built product: Product{id=0, name='Smartphone', price=$699.99, category='Electronics'}
Key Takeaways
- Type Safety: Generics provide compile-time type checking
- Code Reusability: Write generic code that works with different types
- No Casting: Eliminate explicit type casting
- Bounded Types: Restrict types that can be used with generics
- Wildcards: Provide flexibility with
? extendsand? super - Type Erasure: Generic type information is removed at runtime
- Best Practices: Use generic types, avoid raw types, prefer bounded wildcards
Generics are fundamental to modern Java programming and are extensively used in Collections Framework, making code safer, more readable, and reusable.