Bounded Type Parameters in Java

Bounded type parameters restrict the types that can be used as generic type arguments. They allow you to specify that a type parameter must be a subtype of certain classes or implement certain interfaces.

1. Upper Bounded Type Parameters

Basic Upper Bound Syntax

import java.util.*;
public class UpperBoundBasics {
// Upper bounded type parameter - T must be Number or its subclass
public static <T extends Number> double sumOfList(List<T> list) {
double sum = 0.0;
for (T number : list) {
sum += number.doubleValue(); // Can call Number methods
}
return sum;
}
// Multiple bounds - T must implement both Comparable and Serializable
public static <T extends Comparable<T> & Serializable> T findMax(T[] array) {
if (array == null || array.length == 0) return null;
T max = array[0];
for (T element : array) {
if (element.compareTo(max) > 0) {
max = element;
}
}
return max;
}
// Upper bound with wildcards
public static double sumOfNumbers(List<? extends Number> list) {
double sum = 0.0;
for (Number number : list) {
sum += number.doubleValue();
}
return sum;
}
public static void main(String[] args) {
System.out.println("=== Upper Bounded Type Parameters ===");
// Test with Integer list (Integer extends Number)
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
System.out.println("Sum of integers: " + sumOfList(integers));
// Test with Double list (Double extends Number)
List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
System.out.println("Sum of doubles: " + sumOfList(doubles));
// Test with wildcard method
System.out.println("Sum using wildcard: " + sumOfNumbers(integers));
System.out.println("Sum using wildcard: " + sumOfNumbers(doubles));
// Multiple bounds example
String[] names = {"Alice", "Bob", "Charlie"};
String maxName = findMax(names);
System.out.println("Max name: " + maxName);
demonstrateClassHierarchy();
}
public static void demonstrateClassHierarchy() {
System.out.println("\n=== Class Hierarchy with Bounds ===");
class Animal {
String name;
Animal(String name) { this.name = name; }
void eat() { System.out.println(name + " is eating"); }
}
class Mammal extends Animal {
Mammal(String name) { super(name); }
void walk() { System.out.println(name + " is walking"); }
}
class Dog extends Mammal {
Dog(String name) { super(name); }
void bark() { System.out.println(name + " is barking"); }
}
class Cat extends Mammal {
Cat(String name) { super(name); }
void meow() { System.out.println(name + " is meowing"); }
}
// Generic method with upper bound
public static <T extends Mammal> void performMammalActions(List<T> mammals) {
for (T mammal : mammals) {
mammal.eat();    // From Animal
mammal.walk();   // From Mammal
// mammal.bark(); // Compile error - not all mammals can bark
}
}
List<Dog> dogs = Arrays.asList(new Dog("Buddy"), new Dog("Max"));
List<Cat> cats = Arrays.asList(new Cat("Whiskers"), new Cat("Fluffy"));
System.out.println("Dog actions:");
performMammalActions(dogs);
System.out.println("\nCat actions:");
performMammalActions(cats);
}
}

Upper Bounds with Custom Classes

import java.util.*;
public class UpperBoundCustomClasses {
// Custom hierarchy for demonstration
static abstract class Shape implements Comparable<Shape> {
protected String name;
protected double area;
public Shape(String name, double area) {
this.name = name;
this.area = area;
}
public abstract void draw();
@Override
public int compareTo(Shape other) {
return Double.compare(this.area, other.area);
}
@Override
public String toString() {
return String.format("%s (area: %.2f)", name, area);
}
}
static class Circle extends Shape {
private double radius;
public Circle(String name, double radius) {
super(name, Math.PI * radius * radius);
this.radius = radius;
}
@Override
public void draw() {
System.out.println("Drawing circle: " + name + " with radius " + radius);
}
}
static class Rectangle extends Shape {
private double width, height;
public Rectangle(String name, double width, double height) {
super(name, width * height);
this.width = width;
this.height = height;
}
@Override
public void draw() {
System.out.println("Drawing rectangle: " + name + " " + width + "x" + height);
}
}
static class Triangle extends Shape {
private double base, height;
public Triangle(String name, double base, double height) {
super(name, 0.5 * base * height);
this.base = base;
this.height = height;
}
@Override
public void draw() {
System.out.println("Drawing triangle: " + name + " base=" + base + " height=" + height);
}
}
// Generic methods with upper bounds
public static <T extends Shape> void drawAllShapes(List<T> shapes) {
for (T shape : shapes) {
shape.draw();
}
}
public static <T extends Shape> T findLargestShape(List<T> shapes) {
if (shapes.isEmpty()) return null;
T largest = shapes.get(0);
for (T shape : shapes) {
if (shape.compareTo(largest) > 0) {
largest = shape;
}
}
return largest;
}
public static <T extends Shape & Comparable<Shape>> void sortShapes(List<T> shapes) {
Collections.sort(shapes);
}
// Wildcard version
public static void processShapes(List<? extends Shape> shapes) {
for (Shape shape : shapes) {
System.out.println("Processing: " + shape);
}
}
public static void main(String[] args) {
System.out.println("=== Upper Bounds with Custom Classes ===");
List<Circle> circles = Arrays.asList(
new Circle("Small Circle", 5.0),
new Circle("Large Circle", 15.0)
);
List<Rectangle> rectangles = Arrays.asList(
new Rectangle("Small Rect", 4.0, 3.0),
new Rectangle("Large Rect", 10.0, 8.0)
);
List<Triangle> triangles = Arrays.asList(
new Triangle("Small Triangle", 3.0, 4.0),
new Triangle("Large Triangle", 6.0, 8.0)
);
// Draw all shapes
System.out.println("Drawing circles:");
drawAllShapes(circles);
System.out.println("\nDrawing rectangles:");
drawAllShapes(rectangles);
// Find largest shapes
System.out.println("\nLargest circle: " + findLargestShape(circles));
System.out.println("Largest rectangle: " + findLargestShape(rectangles));
// Process shapes using wildcard
System.out.println("\nProcessing circles:");
processShapes(circles);
System.out.println("\nProcessing triangles:");
processShapes(triangles);
// Sort and display
List<Shape> mixedShapes = new ArrayList<>();
mixedShapes.add(new Circle("Medium Circle", 10.0));
mixedShapes.add(new Rectangle("Medium Rect", 6.0, 8.0));
mixedShapes.add(new Triangle("Medium Triangle", 5.0, 12.0));
System.out.println("\nBefore sorting:");
mixedShapes.forEach(System.out::println);
Collections.sort(mixedShapes);
System.out.println("\nAfter sorting by area:");
mixedShapes.forEach(System.out::println);
}
}

2. Lower Bounded Type Parameters

Lower Bound Syntax and Usage

import java.util.*;
public class LowerBoundExamples {
// Lower bounded wildcard - can accept List<Integer>, List<Number>, List<Object>
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 5; i++) {
list.add(i); // Can add Integer to any supertype of Integer
}
}
// Lower bound in generic method
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (T element : src) {
dest.add(element);
}
}
// Multiple operations with lower bounds
public static <T> void addAll(List<? super T> destination, T... elements) {
for (T element : elements) {
destination.add(element);
}
}
public static void main(String[] args) {
System.out.println("=== Lower Bounded Type Parameters ===");
// Test with different list types
List<Integer> integerList = new ArrayList<>();
addNumbers(integerList);
System.out.println("Integer list: " + integerList);
List<Number> numberList = new ArrayList<>();
addNumbers(numberList);
System.out.println("Number list: " + numberList);
List<Object> objectList = new ArrayList<>();
addNumbers(objectList);
System.out.println("Object list: " + objectList);
// Copy operation demonstration
demonstrateCopyOperation();
// PECS principle demonstration
demonstratePECSPrinciple();
}
public static void demonstrateCopyOperation() {
System.out.println("\n=== Copy Operation with Bounds ===");
// Source list (producer - uses extends)
List<Integer> source = Arrays.asList(1, 2, 3, 4, 5);
// Destination lists (consumer - uses super)
List<Integer> intDest = new ArrayList<>();
List<Number> numDest = new ArrayList<>();
List<Object> objDest = new ArrayList<>();
// Copy from source to different destinations
copy(intDest, source);
copy(numDest, source);
copy(objDest, source);
System.out.println("Source: " + source);
System.out.println("Integer destination: " + intDest);
System.out.println("Number destination: " + numDest);
System.out.println("Object destination: " + objDest);
}
public static void demonstratePECSPrinciple() {
System.out.println("\n=== PECS Principle (Producer Extends, Consumer Super) ===");
class Fruit {
String name;
Fruit(String name) { this.name = name; }
@Override public String toString() { return name; }
}
class Apple extends Fruit {
Apple(String name) { super(name); }
}
class Banana extends Fruit {
Banana(String name) { super(name); }
}
// Producer - read only (uses extends)
List<Apple> apples = Arrays.asList(new Apple("Red Apple"), new Apple("Green Apple"));
List<? extends Fruit> fruitBasket = apples; // Can read as Fruit
System.out.println("Reading from producer:");
for (Fruit fruit : fruitBasket) {
System.out.println(" - " + fruit);
}
// Consumer - write only (uses super)
List<Fruit> fruitBowl = new ArrayList<>();
List<? super Apple> appleSink = fruitBowl; // Can write Apples
appleSink.add(new Apple("Fuji Apple"));
appleSink.add(new Apple("Gala Apple"));
// appleSink.add(new Fruit("Generic Fruit")); // Compile error!
System.out.println("Consumer contains: " + fruitBowl);
}
}

3. Multiple Bounds

Multiple Bounds Syntax

import java.util.*;
import java.io.Serializable;
import java.time.LocalDateTime;
public class MultipleBounds {
// Interface definitions
interface Identifiable {
String getId();
void setId(String id);
}
interface Timestamped {
LocalDateTime getCreatedAt();
void setCreatedAt(LocalDateTime time);
}
interface Auditable extends Serializable {
String getCreatedBy();
void setCreatedBy(String user);
}
// Class implementing multiple interfaces
static class Document implements Identifiable, Timestamped, Auditable {
private String id;
private String content;
private LocalDateTime createdAt;
private String createdBy;
public Document(String id, String content, String createdBy) {
this.id = id;
this.content = content;
this.createdAt = LocalDateTime.now();
this.createdBy = createdBy;
}
@Override public String getId() { return id; }
@Override public void setId(String id) { this.id = id; }
@Override public LocalDateTime getCreatedAt() { return createdAt; }
@Override public void setCreatedAt(LocalDateTime time) { this.createdAt = time; }
@Override public String getCreatedBy() { return createdBy; }
@Override public void setCreatedBy(String user) { this.createdBy = user; }
@Override
public String toString() {
return String.format("Document{id='%s', content='%s', createdBy='%s', at=%s}", 
id, content, createdBy, createdAt);
}
}
static class Product implements Identifiable, Serializable {
private String id;
private String name;
private double price;
public Product(String id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
}
@Override public String getId() { return id; }
@Override public void setId(String id) { this.id = id; }
@Override
public String toString() {
return String.format("Product{id='%s', name='%s', price=%.2f}", id, name, price);
}
}
// Generic methods with multiple bounds
public static <T extends Identifiable & Serializable> void saveToDatabase(T entity) {
System.out.println("Saving to database: " + entity.getId());
// Serialization and database logic would go here
}
public static <T extends Identifiable & Timestamped & Auditable> void auditEntity(T entity) {
System.out.printf("Audit: %s created by %s at %s%n",
entity.getId(), entity.getCreatedBy(), entity.getCreatedAt());
}
public static <T extends Comparable<T> & Serializable> T findMax(T[] array) {
if (array == null || array.length == 0) return null;
T max = array[0];
for (T element : array) {
if (element.compareTo(max) > 0) {
max = element;
}
}
return max;
}
// Multiple bounds with class and interfaces
public static <T extends Number & Comparable<T> & Serializable> 
void processNumber(T number) {
System.out.println("Processing number: " + number);
System.out.println("Double value: " + number.doubleValue());
}
public static void main(String[] args) {
System.out.println("=== Multiple Bounds ===");
// Test with Document (implements all interfaces)
Document doc = new Document("DOC-001", "Important content", "john_doe");
auditEntity(doc);
saveToDatabase(doc);
// Test with Product (implements Identifiable & Serializable)
Product product = new Product("PROD-001", "Laptop", 999.99);
saveToDatabase(product);
// auditEntity(product); // Compile error - Product doesn't implement Timestamped & Auditable
// Test with numbers
Integer[] numbers = {5, 2, 8, 1, 9};
Integer maxNumber = findMax(numbers);
System.out.println("Max number: " + maxNumber);
processNumber(42);
processNumber(3.14);
demonstrateComplexBounds();
}
public static void demonstrateComplexBounds() {
System.out.println("\n=== Complex Multiple Bounds ===");
abstract class Entity implements Comparable<Entity>, Serializable {
protected String id;
public Entity(String id) {
this.id = id;
}
public String getId() { return id; }
@Override
public int compareTo(Entity other) {
return this.id.compareTo(other.id);
}
@Override
public abstract String toString();
}
class User extends Entity {
private String username;
public User(String id, String username) {
super(id);
this.username = username;
}
@Override
public String toString() {
return String.format("User{id='%s', username='%s'}", id, username);
}
}
class Order extends Entity {
private double amount;
public Order(String id, double amount) {
super(id);
this.amount = amount;
}
@Override
public String toString() {
return String.format("Order{id='%s', amount=%.2f}", id, amount);
}
}
// Method with complex bounds
public static <T extends Entity & Comparable<Entity> & Serializable> 
void processEntity(T entity) {
System.out.println("Processing entity: " + entity);
System.out.println("Entity ID: " + entity.getId());
}
User user = new User("USR-001", "alice");
Order order = new Order("ORD-001", 199.99);
processEntity(user);
processEntity(order);
// Collection with bounded wildcard
List<? extends Entity> entities = Arrays.asList(user, order);
System.out.println("\nAll entities:");
for (Entity entity : entities) {
System.out.println(" - " + entity);
}
}
}

4. Bounded Type Parameters in Generic Classes

Generic Classes with Bounds

import java.util.*;
import java.time.LocalDateTime;
public class BoundedGenericClasses {
// Generic class with upper bound
static class NumberContainer<T extends Number> {
private T number;
public NumberContainer(T number) {
this.number = number;
}
public T getNumber() {
return number;
}
public void setNumber(T number) {
this.number = number;
}
public double getDoubleValue() {
return number.doubleValue();
}
public int getIntValue() {
return number.intValue();
}
@Override
public String toString() {
return String.format("NumberContainer{value=%s, double=%.2f, int=%d}", 
number, getDoubleValue(), getIntValue());
}
}
// Generic class with multiple bounds
static class AuditableContainer<T extends Comparable<T> & Serializable> {
private T value;
private LocalDateTime createdAt;
private String createdBy;
public AuditableContainer(T value, String createdBy) {
this.value = value;
this.createdAt = LocalDateTime.now();
this.createdBy = createdBy;
}
public T getValue() { return value; }
public LocalDateTime getCreatedAt() { return createdAt; }
public String getCreatedBy() { return createdBy; }
public boolean isGreaterThan(T other) {
return value.compareTo(other) > 0;
}
@Override
public String toString() {
return String.format("AuditableContainer{value=%s, createdBy='%s', at=%s}", 
value, createdBy, createdAt);
}
}
// Generic class with lower bound (less common, usually used in methods)
static class ConsumerContainer<T> {
private List<? super T> items;
public ConsumerContainer() {
this.items = new ArrayList<>();
}
public void addItem(T item) {
items.add(item);
}
@SuppressWarnings("unchecked")
public T getItem(int index) {
// Need to cast since we only know it's supertype of T
return (T) items.get(index);
}
public int size() {
return items.size();
}
@Override
public String toString() {
return "ConsumerContainer{items=" + items + "}";
}
}
public static void main(String[] args) {
System.out.println("=== Bounded Type Parameters in Generic Classes ===");
// NumberContainer examples
NumberContainer<Integer> intContainer = new NumberContainer<>(42);
NumberContainer<Double> doubleContainer = new NumberContainer<>(3.14159);
// NumberContainer<String> stringContainer = new NumberContainer<>("hello"); // Compile error!
System.out.println("Integer container: " + intContainer);
System.out.println("Double container: " + doubleContainer);
// AuditableContainer examples
AuditableContainer<String> stringContainer = 
new AuditableContainer<>("Important Data", "admin");
AuditableContainer<Integer> integerContainer = 
new AuditableContainer<>(100, "system");
System.out.println("\nString container: " + stringContainer);
System.out.println("Integer container: " + integerContainer);
System.out.println("Is 100 > 50? " + integerContainer.isGreaterThan(50));
// ConsumerContainer examples
ConsumerContainer<Integer> consumer = new ConsumerContainer<>();
consumer.addItem(1);
consumer.addItem(2);
consumer.addItem(3);
System.out.println("\nConsumer container: " + consumer);
System.out.println("Item at index 1: " + consumer.getItem(1));
demonstrateAdvancedGenericClass();
}
public static void demonstrateAdvancedGenericClass() {
System.out.println("\n=== Advanced Generic Class with Bounds ===");
interface Validatable {
boolean isValid();
}
interface Persistable {
String getStorageKey();
}
class DataEntity implements Validatable, Persistable, Comparable<DataEntity> {
private String id;
private String data;
public DataEntity(String id, String data) {
this.id = id;
this.data = data;
}
@Override
public boolean isValid() {
return id != null && !id.trim().isEmpty() && 
data != null && !data.trim().isEmpty();
}
@Override
public String getStorageKey() {
return "entity_" + id;
}
@Override
public int compareTo(DataEntity other) {
return this.id.compareTo(other.id);
}
@Override
public String toString() {
return String.format("DataEntity{id='%s', data='%s', valid=%b}", 
id, data, isValid());
}
}
// Generic repository with multiple bounds
class Repository<T extends Validatable & Persistable & Comparable<T>> {
private Map<String, T> storage = new HashMap<>();
public void save(T entity) {
if (entity.isValid()) {
storage.put(entity.getStorageKey(), entity);
System.out.println("Saved: " + entity);
} else {
System.out.println("Cannot save invalid entity: " + entity);
}
}
public T findById(String key) {
return storage.get(key);
}
public List<T> findAllSorted() {
List<T> entities = new ArrayList<>(storage.values());
Collections.sort(entities);
return entities;
}
public int size() {
return storage.size();
}
}
Repository<DataEntity> repo = new Repository<>();
DataEntity validEntity = new DataEntity("001", "Valid data");
DataEntity invalidEntity = new DataEntity("", "Invalid data");
DataEntity anotherValid = new DataEntity("002", "More data");
repo.save(validEntity);
repo.save(invalidEntity); // Won't be saved
repo.save(anotherValid);
System.out.println("\nAll sorted entities:");
repo.findAllSorted().forEach(System.out::println);
System.out.println("Repository size: " + repo.size());
}
}

5. Real-World Examples

Data Validation Framework

import java.util.*;
import java.time.LocalDate;
import java.time.Period;
public class ValidationFramework {
// Validation interfaces
interface Validatable {
boolean isValid();
List<String> getValidationErrors();
}
interface Auditable {
String getCreatedBy();
LocalDate getCreatedDate();
String getLastModifiedBy();
LocalDate getLastModifiedDate();
}
interface BusinessEntity extends Validatable, Auditable {
String getId();
String getName();
}
// Concrete entity classes
static class Customer implements BusinessEntity {
private String id;
private String name;
private String email;
private LocalDate birthDate;
private String createdBy;
private LocalDate createdDate;
public Customer(String id, String name, String email, LocalDate birthDate, String createdBy) {
this.id = id;
this.name = name;
this.email = email;
this.birthDate = birthDate;
this.createdBy = createdBy;
this.createdDate = LocalDate.now();
}
@Override public String getId() { return id; }
@Override public String getName() { return name; }
public String getEmail() { return email; }
public LocalDate getBirthDate() { return birthDate; }
@Override public String getCreatedBy() { return createdBy; }
@Override public LocalDate getCreatedDate() { return createdDate; }
@Override public String getLastModifiedBy() { return createdBy; }
@Override public LocalDate getLastModifiedDate() { return createdDate; }
@Override
public boolean isValid() {
return getValidationErrors().isEmpty();
}
@Override
public List<String> getValidationErrors() {
List<String> errors = new ArrayList<>();
if (id == null || id.trim().isEmpty()) {
errors.add("ID is required");
}
if (name == null || name.trim().isEmpty()) {
errors.add("Name is required");
}
if (email == null || !email.contains("@")) {
errors.add("Valid email is required");
}
if (birthDate != null) {
int age = Period.between(birthDate, LocalDate.now()).getYears();
if (age < 18) {
errors.add("Customer must be at least 18 years old");
}
}
return errors;
}
@Override
public String toString() {
return String.format("Customer{id='%s', name='%s', email='%s', valid=%b}", 
id, name, email, isValid());
}
}
static class Product implements BusinessEntity {
private String id;
private String name;
private double price;
private int stock;
private String createdBy;
private LocalDate createdDate;
public Product(String id, String name, double price, int stock, String createdBy) {
this.id = id;
this.name = name;
this.price = price;
this.stock = stock;
this.createdBy = createdBy;
this.createdDate = LocalDate.now();
}
@Override public String getId() { return id; }
@Override public String getName() { return name; }
public double getPrice() { return price; }
public int getStock() { return stock; }
@Override public String getCreatedBy() { return createdBy; }
@Override public LocalDate getCreatedDate() { return createdDate; }
@Override public String getLastModifiedBy() { return createdBy; }
@Override public LocalDate getLastModifiedDate() { return createdDate; }
@Override
public boolean isValid() {
return getValidationErrors().isEmpty();
}
@Override
public List<String> getValidationErrors() {
List<String> errors = new ArrayList<>();
if (id == null || id.trim().isEmpty()) {
errors.add("Product ID is required");
}
if (name == null || name.trim().isEmpty()) {
errors.add("Product name is required");
}
if (price < 0) {
errors.add("Price cannot be negative");
}
if (stock < 0) {
errors.add("Stock cannot be negative");
}
return errors;
}
@Override
public String toString() {
return String.format("Product{id='%s', name='%s', price=%.2f, stock=%d, valid=%b}", 
id, name, price, stock, isValid());
}
}
// Generic validation service with bounded type parameters
static class ValidationService<T extends BusinessEntity> {
public ValidationResult validate(T entity) {
List<String> errors = entity.getValidationErrors();
boolean isValid = errors.isEmpty();
return new ValidationResult(entity, isValid, errors);
}
public List<ValidationResult> validateAll(List<T> entities) {
List<ValidationResult> results = new ArrayList<>();
for (T entity : entities) {
results.add(validate(entity));
}
return results;
}
public <U extends T> void processValidEntities(List<U> entities, EntityProcessor<U> processor) {
for (U entity : entities) {
if (entity.isValid()) {
processor.process(entity);
} else {
System.out.println("Skipping invalid entity: " + entity);
}
}
}
}
// Support classes
static class ValidationResult {
private final BusinessEntity entity;
private final boolean valid;
private final List<String> errors;
public ValidationResult(BusinessEntity entity, boolean valid, List<String> errors) {
this.entity = entity;
this.valid = valid;
this.errors = errors;
}
public BusinessEntity getEntity() { return entity; }
public boolean isValid() { return valid; }
public List<String> getErrors() { return errors; }
@Override
public String toString() {
return String.format("ValidationResult{entity=%s, valid=%b, errors=%s}", 
entity, valid, errors);
}
}
interface EntityProcessor<T extends BusinessEntity> {
void process(T entity);
}
public static void main(String[] args) {
System.out.println("=== Data Validation Framework ===");
// Create sample data
Customer validCustomer = new Customer("CUST001", "John Doe", "[email protected]", 
LocalDate.of(1990, 1, 1), "admin");
Customer invalidCustomer = new Customer("", "Jane Smith", "invalid-email", 
LocalDate.of(2010, 1, 1), "admin");
Product validProduct = new Product("PROD001", "Laptop", 999.99, 10, "admin");
Product invalidProduct = new Product("", "", -10, -5, "admin");
// Validate customers
ValidationService<Customer> customerService = new ValidationService<>();
System.out.println("Customer validation:");
System.out.println(customerService.validate(validCustomer));
System.out.println(customerService.validate(invalidCustomer));
// Validate products
ValidationService<Product> productService = new ValidationService<>();
System.out.println("\nProduct validation:");
System.out.println(productService.validate(validProduct));
System.out.println(productService.validate(invalidProduct));
// Process valid entities
List<Customer> customers = Arrays.asList(validCustomer, invalidCustomer);
System.out.println("\nProcessing valid customers:");
customerService.processValidEntities(customers, 
customer -> System.out.println("Processing: " + customer.getName()));
// Batch validation
List<Product> products = Arrays.asList(validProduct, invalidProduct);
List<ValidationResult> productResults = productService.validateAll(products);
System.out.println("\nBatch product validation results:");
productResults.forEach(System.out::println);
}
}

Summary

Key Concepts:

  1. Upper Bounds (extends):
  • Restrict type parameter to be a subtype of specific class/interface
  • <T extends Number> - T must be Number or subclass
  • Allows access to methods of the bound type
  1. Lower Bounds (super):
  • Restrict type parameter to be a supertype of specific class
  • <? super Integer> - Can be Integer, Number, or Object
  • Used for write operations (PECS principle)
  1. Multiple Bounds:
  • <T extends ClassA & InterfaceB & InterfaceC>
  • Class must come first, then interfaces
  • Type must satisfy all bounds
  1. Wildcard Bounds:
  • <? extends T> - Upper bounded wildcard (producer)
  • <? super T> - Lower bounded wildcard (consumer)

Best Practices:

  1. Use upper bounds when you need to call specific methods
  2. Use PECS principle: Producer Extends, Consumer Super
  3. Prefer wildcards for more flexibility in method parameters
  4. Use multiple bounds for complex constraints
  5. Keep bounds minimal - only specify what's actually needed

Common Use Cases:

  1. Collections framework - sorting, copying with bounds
  2. Validation frameworks - entities with multiple interfaces
  3. Repository patterns - generic DAOs with constraints
  4. Utility methods - working with number types, comparable objects
  5. API design - enforcing contracts through bounds

Bounded type parameters provide type safety while maintaining flexibility in generic programming, making them essential for robust Java applications.

Leave a Reply

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


Macro Nepal Helper