Generics Introduction in Java

Table of Contents

  1. What are Generics?
  2. Why Use Generics?
  3. Generic Classes
  4. Generic Methods
  5. Generic Interfaces
  6. Type Parameters Naming Conventions
  7. Bounded Type Parameters
  8. Wildcards
  9. Type Erasure
  10. Best Practices
  11. Common Examples
  12. 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:

  1. Type Safety: Compile-time type checking
  2. Eliminates Casting: No need for explicit type casting
  3. Code Reusability: Write once, use with different types
  4. 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

  1. Type Safety: Generics provide compile-time type checking
  2. Code Reusability: Write generic code that works with different types
  3. No Casting: Eliminate explicit type casting
  4. Bounded Types: Restrict types that can be used with generics
  5. Wildcards: Provide flexibility with ? extends and ? super
  6. Type Erasure: Generic type information is removed at runtime
  7. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper