Your generic method needs to call compareTo(). But T could be anything - the compiler won't allow it. With <T extends Comparable<T>>, you guarantee T has compareTo(), and the compiler lets you use it.

Upper bounds with extends

Restrict T to a type or its subtypes.

UpperBounds.java
public class UpperBounds {
    static class NumberBox<T extends Number> {
        private T value;
        
        public NumberBox(T value) {
            this.value = value;
        }
        
        public T getValue() {
            return value;
        }
        
        public double getDoubleValue() {
            return value.doubleValue(); // Can call Number methods
        }
        
        public boolean isPositive() {
            return value.doubleValue() > 0;
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Upper bounds:\n");
        
        NumberBox<Integer> intBox = new NumberBox<>(42);
        System.out.println("  Integer: " + intBox.getValue());
        System.out.println("  As double: " + intBox.getDoubleValue());
        System.out.println("  Positive: " + intBox.isPositive());
        
        NumberBox<Double> doubleBox = new NumberBox<>(3.14);
        System.out.println("  Double: " + doubleBox.getValue());
        System.out.println("  Positive: " + doubleBox.isPositive());
        
        // NumberBox<String> strBox = new NumberBox<>("text"); // Compile error
        
        // <T extends Type> means T must be Type or subclass/implementer
        // Enables calling methods from the bounded type
        // Number bound allows: intValue(), doubleValue(), etc.
        // Compile error if type doesn't satisfy bound
        
        System.out.println("\nComparable bound:");
        
        class Sorter<T extends Comparable<T>> {
            public T max(T a, T b) {
                return a.compareTo(b) > 0 ? a : b;
            }
            
            public T min(T a, T b) {
                return a.compareTo(b) < 0 ? a : b;
            }
        }
        
        Sorter<Integer> intSorter = new Sorter<>();
        System.out.println("  Max(5, 10): " + intSorter.max(5, 10));
        System.out.println("  Min(5, 10): " + intSorter.min(5, 10));
        
        Sorter<String> strSorter = new Sorter<>();
        System.out.println("  Max(apple, banana): " + strSorter.max("apple", "banana"));
        
        System.out.println("\nStatistics:");
        
        class Stats<T extends Number> {
            private java.util.List<T> numbers;
            
            public Stats(java.util.List<T> numbers) {
                this.numbers = numbers;
            }
            
            public double average() {
                double sum = 0;
                for (T num : numbers) {
                    sum += num.doubleValue();
                }
                return sum / numbers.size();
            }
            
            public double sum() {
                double total = 0;
                for (T num : numbers) {
                    total += num.doubleValue();
                }
                return total;
            }
        }
        
        Stats<Integer> intStats = new Stats<>(java.util.Arrays.asList(10, 20, 30, 40));
        System.out.println("  Average: " + intStats.average());
        System.out.println("  Sum: " + intStats.sum());
        
        System.out.println("\nRange:");
        
        class Range<T extends Comparable<T>> {
            private T min;
            private T max;
            
            public Range(T min, T max) {
                this.min = min;
                this.max = max;
            }
            
            public boolean contains(T value) {
                return value.compareTo(min) >= 0 && value.compareTo(max) <= 0;
            }
            
            public String toString() {
                return "[" + min + " - " + max + "]";
            }
        }
        
        Range<Integer> ageRange = new Range<>(18, 65);
        System.out.println("  Range: " + ageRange);
        int ageToCheck = ;
        System.out.println("  Contains " + ageToCheck + ": " +
                         ageRange.contains(ageToCheck));
        System.out.println("  Contains 70: " + ageRange.contains(70));
        
        Range<String> nameRange = new Range<>("A", "M");
        System.out.println("  Name range: " + nameRange);
        System.out.println("  Contains Alice: " + nameRange.contains("Alice"));
        System.out.println("  Contains Steve: " + nameRange.contains("Steve"));
    }
}
public class UpperBounds {
    static class NumberBox<T extends Number> {
        private T value;
        
        public NumberBox(T value) {
            this.value = value;
        }
        
        public T getValue() {
            return value;
        }
        
        public double getDoubleValue() {
            return value.doubleValue(); // Can call Number methods
        }
        
        public boolean isPositive() {
            return value.doubleValue() > 0;
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Upper bounds:\n");
        
        NumberBox<Integer> intBox = new NumberBox<>(42);
        System.out.println("  Integer: " + intBox.getValue());
        System.out.println("  As double: " + intBox.getDoubleValue());
        System.out.println("  Positive: " + intBox.isPositive());
        
        NumberBox<Double> doubleBox = new NumberBox<>(3.14);
        System.out.println("  Double: " + doubleBox.getValue());
        System.out.println("  Positive: " + doubleBox.isPositive());
        
        // NumberBox<String> strBox = new NumberBox<>("text"); // Compile error
        
        // <T extends Type> means T must be Type or subclass/implementer
        // Enables calling methods from the bounded type
        // Number bound allows: intValue(), doubleValue(), etc.
        // Compile error if type doesn't satisfy bound
        
        System.out.println("\nComparable bound:");
        
        class Sorter<T extends Comparable<T>> {
            public T max(T a, T b) {
                return a.compareTo(b) > 0 ? a : b;
            }
            
            public T min(T a, T b) {
                return a.compareTo(b) < 0 ? a : b;
            }
        }
        
        Sorter<Integer> intSorter = new Sorter<>();
        System.out.println("  Max(5, 10): " + intSorter.max(5, 10));
        System.out.println("  Min(5, 10): " + intSorter.min(5, 10));
        
        Sorter<String> strSorter = new Sorter<>();
        System.out.println("  Max(apple, banana): " + strSorter.max("apple", "banana"));
        
        System.out.println("\nStatistics:");
        
        class Stats<T extends Number> {
            private java.util.List<T> numbers;
            
            public Stats(java.util.List<T> numbers) {
                this.numbers = numbers;
            }
            
            public double average() {
                double sum = 0;
                for (T num : numbers) {
                    sum += num.doubleValue();
                }
                return sum / numbers.size();
            }
            
            public double sum() {
                double total = 0;
                for (T num : numbers) {
                    total += num.doubleValue();
                }
                return total;
            }
        }
        
        Stats<Integer> intStats = new Stats<>(java.util.Arrays.asList(10, 20, 30, 40));
        System.out.println("  Average: " + intStats.average());
        System.out.println("  Sum: " + intStats.sum());
        
        System.out.println("\nRange:");
        
        class Range<T extends Comparable<T>> {
            private T min;
            private T max;
            
            public Range(T min, T max) {
                this.min = min;
                this.max = max;
            }
            
            public boolean contains(T value) {
                return value.compareTo(min) >= 0 && value.compareTo(max) <= 0;
            }
            
            public String toString() {
                return "[" + min + " - " + max + "]";
            }
        }
        
        Range<Integer> ageRange = new Range<>(18, 65);
        System.out.println("  Range: " + ageRange);
        int ageToCheck = ;
        System.out.println("  Contains " + ageToCheck + ": " +
                         ageRange.contains(ageToCheck));
        System.out.println("  Contains 70: " + ageRange.contains(70));
        
        Range<String> nameRange = new Range<>("A", "M");
        System.out.println("  Name range: " + nameRange);
        System.out.println("  Contains Alice: " + nameRange.contains("Alice"));
        System.out.println("  Contains Steve: " + nameRange.contains("Steve"));
    }
}
public class UpperBounds {
    static class NumberBox<T extends Number> {
        private T value;
        
        public NumberBox(T value) {
            this.value = value;
        }
        
        public T getValue() {
            return value;
        }
        
        public double getDoubleValue() {
            return value.doubleValue(); // Can call Number methods
        }
        
        public boolean isPositive() {
            return value.doubleValue() > 0;
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Upper bounds:\n");
        
        NumberBox<Integer> intBox = new NumberBox<>(42);
        System.out.println("  Integer: " + intBox.getValue());
        System.out.println("  As double: " + intBox.getDoubleValue());
        System.out.println("  Positive: " + intBox.isPositive());
        
        NumberBox<Double> doubleBox = new NumberBox<>(3.14);
        System.out.println("  Double: " + doubleBox.getValue());
        System.out.println("  Positive: " + doubleBox.isPositive());
        
        // NumberBox<String> strBox = new NumberBox<>("text"); // Compile error
        
        // <T extends Type> means T must be Type or subclass/implementer
        // Enables calling methods from the bounded type
        // Number bound allows: intValue(), doubleValue(), etc.
        // Compile error if type doesn't satisfy bound
        
        System.out.println("\nComparable bound:");
        
        class Sorter<T extends Comparable<T>> {
            public T max(T a, T b) {
                return a.compareTo(b) > 0 ? a : b;
            }
            
            public T min(T a, T b) {
                return a.compareTo(b) < 0 ? a : b;
            }
        }
        
        Sorter<Integer> intSorter = new Sorter<>();
        System.out.println("  Max(5, 10): " + intSorter.max(5, 10));
        System.out.println("  Min(5, 10): " + intSorter.min(5, 10));
        
        Sorter<String> strSorter = new Sorter<>();
        System.out.println("  Max(apple, banana): " + strSorter.max("apple", "banana"));
        
        System.out.println("\nStatistics:");
        
        class Stats<T extends Number> {
            private java.util.List<T> numbers;
            
            public Stats(java.util.List<T> numbers) {
                this.numbers = numbers;
            }
            
            public double average() {
                double sum = 0;
                for (T num : numbers) {
                    sum += num.doubleValue();
                }
                return sum / numbers.size();
            }
            
            public double sum() {
                double total = 0;
                for (T num : numbers) {
                    total += num.doubleValue();
                }
                return total;
            }
        }
        
        Stats<Integer> intStats = new Stats<>(java.util.Arrays.asList(10, 20, 30, 40));
        System.out.println("  Average: " + intStats.average());
        System.out.println("  Sum: " + intStats.sum());
        
        System.out.println("\nRange:");
        
        class Range<T extends Comparable<T>> {
            private T min;
            private T max;
            
            public Range(T min, T max) {
                this.min = min;
                this.max = max;
            }
            
            public boolean contains(T value) {
                return value.compareTo(min) >= 0 && value.compareTo(max) <= 0;
            }
            
            public String toString() {
                return "[" + min + " - " + max + "]";
            }
        }
        
        Range<Integer> ageRange = new Range<>(18, 65);
        System.out.println("  Range: " + ageRange);
        int ageToCheck = ;
        System.out.println("  Contains " + ageToCheck + ": " +
                         ageRange.contains(ageToCheck));
        System.out.println("  Contains 70: " + ageRange.contains(70));
        
        Range<String> nameRange = new Range<>("A", "M");
        System.out.println("  Name range: " + nameRange);
        System.out.println("  Contains Alice: " + nameRange.contains("Alice"));
        System.out.println("  Contains Steve: " + nameRange.contains("Steve"));
    }
}

<T extends Number> - T must be Number or subclass. Can call Number methods.

upper bound `extends` constraint: `<T extends Type>`. T must be Type or subtype.

Multiple bounds

Combine class and interface constraints.

MultipleBounds.java
import java.io.Serializable;

public class MultipleBounds {
    static class Calculator<T extends Number & Comparable<T>> {
        public T max(T a, T b) {
            return a.compareTo(b) > 0 ? a : b;
        }
        
        public double add(T a, T b) {
            return a.doubleValue() + b.doubleValue();
        }
        
        public boolean inRange(T value, T min, T max) {
            return value.compareTo(min) >= 0 && value.compareTo(max) <= 0;
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Multiple bounds:\n");
        
        Calculator<Integer> intCalc = new Calculator<>();
        System.out.println("  Max(5, 10): " + intCalc.max(5, 10));
        System.out.println("  Add(5, 10): " + intCalc.add(5, 10));
        int rangeValue = ;
        System.out.println("  InRange(" + rangeValue + ", 5, 10): " +
                         intCalc.inRange(rangeValue, 5, 10));
        
        Calculator<Double> doubleCalc = new Calculator<>();
        System.out.println("  Max(3.14, 2.71): " + doubleCalc.max(3.14, 2.71));
        
        // Multiple bounds: <T extends Type1 & Type2 & Type3>
        // Class bound must come first (can only have one class)
        // Interface bounds follow, separated by &
        // T must satisfy ALL bounds
        
        System.out.println("\nSerializable + Comparable:");
        
        class Storage<T extends Serializable & Comparable<T>> {
            private T value;
            
            public void store(T value) {
                this.value = value;
                System.out.println("  Stored (serializable): " + value);
            }
            
            public T retrieve() {
                return value;
            }
            
            public boolean isGreaterThan(T other) {
                return value != null && value.compareTo(other) > 0;
            }
        }
        
        Storage<String> strStorage = new Storage<>();
        strStorage.store("Hello");
        System.out.println("  Greater than 'Apple': " + 
                         strStorage.isGreaterThan("Apple"));
        
        Storage<Integer> intStorage = new Storage<>();
        intStorage.store(42);
        System.out.println("  Greater than 30: " + 
                         intStorage.isGreaterThan(30));
        
        System.out.println("\nCustom interfaces:");
        
        interface Nameable {
            String getName();
        }
        
        interface Identifiable {
            long getId();
        }
        
        class Entity<T extends Nameable & Identifiable> {
            private T item;
            
            public Entity(T item) {
                this.item = item;
            }
            
            public void display() {
                System.out.println("  ID: " + item.getId() + 
                                 ", Name: " + item.getName());
            }
        }
        
        class User implements Nameable, Identifiable {
            private long id;
            private String name;
            
            User(long id, String name) {
                this.id = id;
                this.name = name;
            }
            
            @Override
            public String getName() { return name; }
            
            @Override
            public long getId() { return id; }
        }
        
        Entity<User> userEntity = new Entity<>(new User(1, "Alice"));
        userEntity.display();
        
        System.out.println("\nThree bounds:");
        
        interface Readable {
            String read();
        }
        
        interface Writable {
            void write(String data);
        }
        
        interface Closeable {
            void close();
        }
        
        class Resource<T extends Readable & Writable & Closeable> {
            private T resource;
            
            public Resource(T resource) {
                this.resource = resource;
            }
            
            public void process() {
                String data = resource.read();
                System.out.println("  Read: " + data);
                resource.write("Processed: " + data);
                resource.close();
            }
        }
        
        class File implements Readable, Writable, Closeable {
            private String content = "File content";
            
            @Override
            public String read() { return content; }
            
            @Override
            public void write(String data) { 
                System.out.println("  Writing: " + data);
            }
            
            @Override
            public void close() { 
                System.out.println("  Closed");
            }
        }
        
        Resource<File> fileResource = new Resource<>(new File());
        fileResource.process();
    }
}
import java.io.Serializable;

public class MultipleBounds {
    static class Calculator<T extends Number & Comparable<T>> {
        public T max(T a, T b) {
            return a.compareTo(b) > 0 ? a : b;
        }
        
        public double add(T a, T b) {
            return a.doubleValue() + b.doubleValue();
        }
        
        public boolean inRange(T value, T min, T max) {
            return value.compareTo(min) >= 0 && value.compareTo(max) <= 0;
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Multiple bounds:\n");
        
        Calculator<Integer> intCalc = new Calculator<>();
        System.out.println("  Max(5, 10): " + intCalc.max(5, 10));
        System.out.println("  Add(5, 10): " + intCalc.add(5, 10));
        int rangeValue = ;
        System.out.println("  InRange(" + rangeValue + ", 5, 10): " +
                         intCalc.inRange(rangeValue, 5, 10));
        
        Calculator<Double> doubleCalc = new Calculator<>();
        System.out.println("  Max(3.14, 2.71): " + doubleCalc.max(3.14, 2.71));
        
        // Multiple bounds: <T extends Type1 & Type2 & Type3>
        // Class bound must come first (can only have one class)
        // Interface bounds follow, separated by &
        // T must satisfy ALL bounds
        
        System.out.println("\nSerializable + Comparable:");
        
        class Storage<T extends Serializable & Comparable<T>> {
            private T value;
            
            public void store(T value) {
                this.value = value;
                System.out.println("  Stored (serializable): " + value);
            }
            
            public T retrieve() {
                return value;
            }
            
            public boolean isGreaterThan(T other) {
                return value != null && value.compareTo(other) > 0;
            }
        }
        
        Storage<String> strStorage = new Storage<>();
        strStorage.store("Hello");
        System.out.println("  Greater than 'Apple': " + 
                         strStorage.isGreaterThan("Apple"));
        
        Storage<Integer> intStorage = new Storage<>();
        intStorage.store(42);
        System.out.println("  Greater than 30: " + 
                         intStorage.isGreaterThan(30));
        
        System.out.println("\nCustom interfaces:");
        
        interface Nameable {
            String getName();
        }
        
        interface Identifiable {
            long getId();
        }
        
        class Entity<T extends Nameable & Identifiable> {
            private T item;
            
            public Entity(T item) {
                this.item = item;
            }
            
            public void display() {
                System.out.println("  ID: " + item.getId() + 
                                 ", Name: " + item.getName());
            }
        }
        
        class User implements Nameable, Identifiable {
            private long id;
            private String name;
            
            User(long id, String name) {
                this.id = id;
                this.name = name;
            }
            
            @Override
            public String getName() { return name; }
            
            @Override
            public long getId() { return id; }
        }
        
        Entity<User> userEntity = new Entity<>(new User(1, "Alice"));
        userEntity.display();
        
        System.out.println("\nThree bounds:");
        
        interface Readable {
            String read();
        }
        
        interface Writable {
            void write(String data);
        }
        
        interface Closeable {
            void close();
        }
        
        class Resource<T extends Readable & Writable & Closeable> {
            private T resource;
            
            public Resource(T resource) {
                this.resource = resource;
            }
            
            public void process() {
                String data = resource.read();
                System.out.println("  Read: " + data);
                resource.write("Processed: " + data);
                resource.close();
            }
        }
        
        class File implements Readable, Writable, Closeable {
            private String content = "File content";
            
            @Override
            public String read() { return content; }
            
            @Override
            public void write(String data) { 
                System.out.println("  Writing: " + data);
            }
            
            @Override
            public void close() { 
                System.out.println("  Closed");
            }
        }
        
        Resource<File> fileResource = new Resource<>(new File());
        fileResource.process();
    }
}
import java.io.Serializable;

public class MultipleBounds {
    static class Calculator<T extends Number & Comparable<T>> {
        public T max(T a, T b) {
            return a.compareTo(b) > 0 ? a : b;
        }
        
        public double add(T a, T b) {
            return a.doubleValue() + b.doubleValue();
        }
        
        public boolean inRange(T value, T min, T max) {
            return value.compareTo(min) >= 0 && value.compareTo(max) <= 0;
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Multiple bounds:\n");
        
        Calculator<Integer> intCalc = new Calculator<>();
        System.out.println("  Max(5, 10): " + intCalc.max(5, 10));
        System.out.println("  Add(5, 10): " + intCalc.add(5, 10));
        int rangeValue = ;
        System.out.println("  InRange(" + rangeValue + ", 5, 10): " +
                         intCalc.inRange(rangeValue, 5, 10));
        
        Calculator<Double> doubleCalc = new Calculator<>();
        System.out.println("  Max(3.14, 2.71): " + doubleCalc.max(3.14, 2.71));
        
        // Multiple bounds: <T extends Type1 & Type2 & Type3>
        // Class bound must come first (can only have one class)
        // Interface bounds follow, separated by &
        // T must satisfy ALL bounds
        
        System.out.println("\nSerializable + Comparable:");
        
        class Storage<T extends Serializable & Comparable<T>> {
            private T value;
            
            public void store(T value) {
                this.value = value;
                System.out.println("  Stored (serializable): " + value);
            }
            
            public T retrieve() {
                return value;
            }
            
            public boolean isGreaterThan(T other) {
                return value != null && value.compareTo(other) > 0;
            }
        }
        
        Storage<String> strStorage = new Storage<>();
        strStorage.store("Hello");
        System.out.println("  Greater than 'Apple': " + 
                         strStorage.isGreaterThan("Apple"));
        
        Storage<Integer> intStorage = new Storage<>();
        intStorage.store(42);
        System.out.println("  Greater than 30: " + 
                         intStorage.isGreaterThan(30));
        
        System.out.println("\nCustom interfaces:");
        
        interface Nameable {
            String getName();
        }
        
        interface Identifiable {
            long getId();
        }
        
        class Entity<T extends Nameable & Identifiable> {
            private T item;
            
            public Entity(T item) {
                this.item = item;
            }
            
            public void display() {
                System.out.println("  ID: " + item.getId() + 
                                 ", Name: " + item.getName());
            }
        }
        
        class User implements Nameable, Identifiable {
            private long id;
            private String name;
            
            User(long id, String name) {
                this.id = id;
                this.name = name;
            }
            
            @Override
            public String getName() { return name; }
            
            @Override
            public long getId() { return id; }
        }
        
        Entity<User> userEntity = new Entity<>(new User(1, "Alice"));
        userEntity.display();
        
        System.out.println("\nThree bounds:");
        
        interface Readable {
            String read();
        }
        
        interface Writable {
            void write(String data);
        }
        
        interface Closeable {
            void close();
        }
        
        class Resource<T extends Readable & Writable & Closeable> {
            private T resource;
            
            public Resource(T resource) {
                this.resource = resource;
            }
            
            public void process() {
                String data = resource.read();
                System.out.println("  Read: " + data);
                resource.write("Processed: " + data);
                resource.close();
            }
        }
        
        class File implements Readable, Writable, Closeable {
            private String content = "File content";
            
            @Override
            public String read() { return content; }
            
            @Override
            public void write(String data) { 
                System.out.println("  Writing: " + data);
            }
            
            @Override
            public void close() { 
                System.out.println("  Closed");
            }
        }
        
        Resource<File> fileResource = new Resource<>(new File());
        fileResource.process();
    }
}

<T extends Number & Comparable<T>> - class first, then interfaces with &.

Bounded methods

Apply bounds to method-level type parameters.

BoundedMethods.java
import java.util.*;

public class BoundedMethods {
    public static <T extends Comparable<T>> T findMax(List<T> list) {
        if (list.isEmpty()) {
            return null;
        }
        
        T max = list.get(0);
        for (T item : list) {
            if (item.compareTo(max) > 0) {
                max = item;
            }
        }
        return max;
    }
    
    public static void main(String[] args) {
        System.out.println("Bounded methods:\n");
        
        List<Integer> numbers = Arrays.asList(5, 2, 9, 1, 7);
        Integer maxNum = findMax(numbers);
        System.out.println("  Max number: " + maxNum);
        
        List<String> words = Arrays.asList("apple", "zebra", "banana");
        String maxWord = findMax(words);
        System.out.println("  Max word: " + maxWord);
        
        // Method can declare its own type parameters with bounds
        // <T extends Type> before return type
        // Bound applies only to that method
        // Enables calling bounded type's methods
        
        System.out.println("\nNumeric operations:");
        
        class MathUtils {
            public static <T extends Number> double sum(List<T> numbers) {
                double total = 0;
                for (T num : numbers) {
                    total += num.doubleValue();
                }
                return total;
            }
            
            public static <T extends Number> double average(List<T> numbers) {
                return sum(numbers) / numbers.size();
            }
        }
        
        List<Integer> intList = Arrays.asList(10, 20, 30);
        System.out.println("  Sum: " + MathUtils.sum(intList));
        System.out.println("  Average: " + MathUtils.average(intList));
        
        List<Double> doubleList = Arrays.asList(1.5, 2.5, 3.5);
        System.out.println("  Sum: " + MathUtils.sum(doubleList));
        
        System.out.println("\nCount matching:");
        
        class Counter {
            public static <T> int countMatching(List<T> list, T target) {
                int count = 0;
                for (T item : list) {
                    if (item.equals(target)) {
                        count++;
                    }
                }
                return count;
            }
            
            public static <T extends Comparable<T>> int countGreater(
                    List<T> list, T threshold) {
                int count = 0;
                for (T item : list) {
                    if (item.compareTo(threshold) > 0) {
                        count++;
                    }
                }
                return count;
            }
        }
        
        List<Integer> nums = Arrays.asList(5, 10, 3, 10, 7);
        System.out.println("  Count 10s: " + Counter.countMatching(nums, 10));
        System.out.println("  Count > 5: " + Counter.countGreater(nums, 5));
        
        System.out.println("\nCopy method:");
        
        class Copier {
            public static <T> void copy(List<T> source, List<T> dest) {
                dest.clear();
                dest.addAll(source);
            }
            
            public static <T extends Comparable<T>> void copyGreater(
                    List<T> source, List<T> dest, T threshold) {
                dest.clear();
                for (T item : source) {
                    if (item.compareTo(threshold) > 0) {
                        dest.add(item);
                    }
                }
            }
        }
        
        List<Integer> src = Arrays.asList(1, 5, 3, 8, 2, 9);
        List<Integer> dst = new ArrayList<>();
        
        int threshold = ;
        Copier.copyGreater(src, dst, threshold);
        System.out.println("  Copied > " + threshold + ": " + dst);
        
        System.out.println("\nMin/Max finder:");
        
        class MinMax {
            public static <T extends Comparable<T>> T min(T a, T b) {
                return a.compareTo(b) < 0 ? a : b;
            }
            
            public static <T extends Comparable<T>> T max(T a, T b) {
                return a.compareTo(b) > 0 ? a : b;
            }
            
            public static <T extends Comparable<T>> T clamp(
                    T value, T min, T max) {
                if (value.compareTo(min) < 0) return min;
                if (value.compareTo(max) > 0) return max;
                return value;
            }
        }
        
        System.out.println("  min(5, 10): " + MinMax.min(5, 10));
        System.out.println("  max(5, 10): " + MinMax.max(5, 10));
        System.out.println("  clamp(15, 0, 10): " + MinMax.clamp(15, 0, 10));
        System.out.println("  clamp(-5, 0, 10): " + MinMax.clamp(-5, 0, 10));
    }
}
import java.util.*;

public class BoundedMethods {
    public static <T extends Comparable<T>> T findMax(List<T> list) {
        if (list.isEmpty()) {
            return null;
        }
        
        T max = list.get(0);
        for (T item : list) {
            if (item.compareTo(max) > 0) {
                max = item;
            }
        }
        return max;
    }
    
    public static void main(String[] args) {
        System.out.println("Bounded methods:\n");
        
        List<Integer> numbers = Arrays.asList(5, 2, 9, 1, 7);
        Integer maxNum = findMax(numbers);
        System.out.println("  Max number: " + maxNum);
        
        List<String> words = Arrays.asList("apple", "zebra", "banana");
        String maxWord = findMax(words);
        System.out.println("  Max word: " + maxWord);
        
        // Method can declare its own type parameters with bounds
        // <T extends Type> before return type
        // Bound applies only to that method
        // Enables calling bounded type's methods
        
        System.out.println("\nNumeric operations:");
        
        class MathUtils {
            public static <T extends Number> double sum(List<T> numbers) {
                double total = 0;
                for (T num : numbers) {
                    total += num.doubleValue();
                }
                return total;
            }
            
            public static <T extends Number> double average(List<T> numbers) {
                return sum(numbers) / numbers.size();
            }
        }
        
        List<Integer> intList = Arrays.asList(10, 20, 30);
        System.out.println("  Sum: " + MathUtils.sum(intList));
        System.out.println("  Average: " + MathUtils.average(intList));
        
        List<Double> doubleList = Arrays.asList(1.5, 2.5, 3.5);
        System.out.println("  Sum: " + MathUtils.sum(doubleList));
        
        System.out.println("\nCount matching:");
        
        class Counter {
            public static <T> int countMatching(List<T> list, T target) {
                int count = 0;
                for (T item : list) {
                    if (item.equals(target)) {
                        count++;
                    }
                }
                return count;
            }
            
            public static <T extends Comparable<T>> int countGreater(
                    List<T> list, T threshold) {
                int count = 0;
                for (T item : list) {
                    if (item.compareTo(threshold) > 0) {
                        count++;
                    }
                }
                return count;
            }
        }
        
        List<Integer> nums = Arrays.asList(5, 10, 3, 10, 7);
        System.out.println("  Count 10s: " + Counter.countMatching(nums, 10));
        System.out.println("  Count > 5: " + Counter.countGreater(nums, 5));
        
        System.out.println("\nCopy method:");
        
        class Copier {
            public static <T> void copy(List<T> source, List<T> dest) {
                dest.clear();
                dest.addAll(source);
            }
            
            public static <T extends Comparable<T>> void copyGreater(
                    List<T> source, List<T> dest, T threshold) {
                dest.clear();
                for (T item : source) {
                    if (item.compareTo(threshold) > 0) {
                        dest.add(item);
                    }
                }
            }
        }
        
        List<Integer> src = Arrays.asList(1, 5, 3, 8, 2, 9);
        List<Integer> dst = new ArrayList<>();
        
        int threshold = ;
        Copier.copyGreater(src, dst, threshold);
        System.out.println("  Copied > " + threshold + ": " + dst);
        
        System.out.println("\nMin/Max finder:");
        
        class MinMax {
            public static <T extends Comparable<T>> T min(T a, T b) {
                return a.compareTo(b) < 0 ? a : b;
            }
            
            public static <T extends Comparable<T>> T max(T a, T b) {
                return a.compareTo(b) > 0 ? a : b;
            }
            
            public static <T extends Comparable<T>> T clamp(
                    T value, T min, T max) {
                if (value.compareTo(min) < 0) return min;
                if (value.compareTo(max) > 0) return max;
                return value;
            }
        }
        
        System.out.println("  min(5, 10): " + MinMax.min(5, 10));
        System.out.println("  max(5, 10): " + MinMax.max(5, 10));
        System.out.println("  clamp(15, 0, 10): " + MinMax.clamp(15, 0, 10));
        System.out.println("  clamp(-5, 0, 10): " + MinMax.clamp(-5, 0, 10));
    }
}
import java.util.*;

public class BoundedMethods {
    public static <T extends Comparable<T>> T findMax(List<T> list) {
        if (list.isEmpty()) {
            return null;
        }
        
        T max = list.get(0);
        for (T item : list) {
            if (item.compareTo(max) > 0) {
                max = item;
            }
        }
        return max;
    }
    
    public static void main(String[] args) {
        System.out.println("Bounded methods:\n");
        
        List<Integer> numbers = Arrays.asList(5, 2, 9, 1, 7);
        Integer maxNum = findMax(numbers);
        System.out.println("  Max number: " + maxNum);
        
        List<String> words = Arrays.asList("apple", "zebra", "banana");
        String maxWord = findMax(words);
        System.out.println("  Max word: " + maxWord);
        
        // Method can declare its own type parameters with bounds
        // <T extends Type> before return type
        // Bound applies only to that method
        // Enables calling bounded type's methods
        
        System.out.println("\nNumeric operations:");
        
        class MathUtils {
            public static <T extends Number> double sum(List<T> numbers) {
                double total = 0;
                for (T num : numbers) {
                    total += num.doubleValue();
                }
                return total;
            }
            
            public static <T extends Number> double average(List<T> numbers) {
                return sum(numbers) / numbers.size();
            }
        }
        
        List<Integer> intList = Arrays.asList(10, 20, 30);
        System.out.println("  Sum: " + MathUtils.sum(intList));
        System.out.println("  Average: " + MathUtils.average(intList));
        
        List<Double> doubleList = Arrays.asList(1.5, 2.5, 3.5);
        System.out.println("  Sum: " + MathUtils.sum(doubleList));
        
        System.out.println("\nCount matching:");
        
        class Counter {
            public static <T> int countMatching(List<T> list, T target) {
                int count = 0;
                for (T item : list) {
                    if (item.equals(target)) {
                        count++;
                    }
                }
                return count;
            }
            
            public static <T extends Comparable<T>> int countGreater(
                    List<T> list, T threshold) {
                int count = 0;
                for (T item : list) {
                    if (item.compareTo(threshold) > 0) {
                        count++;
                    }
                }
                return count;
            }
        }
        
        List<Integer> nums = Arrays.asList(5, 10, 3, 10, 7);
        System.out.println("  Count 10s: " + Counter.countMatching(nums, 10));
        System.out.println("  Count > 5: " + Counter.countGreater(nums, 5));
        
        System.out.println("\nCopy method:");
        
        class Copier {
            public static <T> void copy(List<T> source, List<T> dest) {
                dest.clear();
                dest.addAll(source);
            }
            
            public static <T extends Comparable<T>> void copyGreater(
                    List<T> source, List<T> dest, T threshold) {
                dest.clear();
                for (T item : source) {
                    if (item.compareTo(threshold) > 0) {
                        dest.add(item);
                    }
                }
            }
        }
        
        List<Integer> src = Arrays.asList(1, 5, 3, 8, 2, 9);
        List<Integer> dst = new ArrayList<>();
        
        int threshold = ;
        Copier.copyGreater(src, dst, threshold);
        System.out.println("  Copied > " + threshold + ": " + dst);
        
        System.out.println("\nMin/Max finder:");
        
        class MinMax {
            public static <T extends Comparable<T>> T min(T a, T b) {
                return a.compareTo(b) < 0 ? a : b;
            }
            
            public static <T extends Comparable<T>> T max(T a, T b) {
                return a.compareTo(b) > 0 ? a : b;
            }
            
            public static <T extends Comparable<T>> T clamp(
                    T value, T min, T max) {
                if (value.compareTo(min) < 0) return min;
                if (value.compareTo(max) > 0) return max;
                return value;
            }
        }
        
        System.out.println("  min(5, 10): " + MinMax.min(5, 10));
        System.out.println("  max(5, 10): " + MinMax.max(5, 10));
        System.out.println("  clamp(15, 0, 10): " + MinMax.clamp(15, 0, 10));
        System.out.println("  clamp(-5, 0, 10): " + MinMax.clamp(-5, 0, 10));
    }
}

<T extends Comparable<T>> T max(T a, T b) - can compare a and b.

Recursive bounds

Type parameter references itself in bound.

RecursiveBounds.java
import java.util.*;

public class RecursiveBounds {
    static class SortedList<T extends Comparable<T>> {
        private List<T> items = new ArrayList<>();
        
        public void add(T item) {
            items.add(item);
            Collections.sort(items);
            System.out.println("  Added and sorted: " + item);
        }
        
        public T getMin() {
            return items.isEmpty() ? null : items.get(0);
        }
        
        public T getMax() {
            return items.isEmpty() ? null : items.get(items.size() - 1);
        }
        
        public List<T> getAll() {
            return new ArrayList<>(items);
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Recursive type bounds:\n");
        
        SortedList<Integer> numbers = new SortedList<>();
        numbers.add(5);
        numbers.add(2);
        numbers.add(8);
        numbers.add(1);
        
        System.out.println("  Min: " + numbers.getMin());
        System.out.println("  Max: " + numbers.getMax());
        System.out.println("  All: " + numbers.getAll());
        
        // <T extends Comparable<T>> is a recursive bound
        // T must be comparable to itself
        // Common pattern for sortable types
        // Ensures type-safe comparison
        
        System.out.println("\nWith enums:");
        
        enum Priority implements Comparable<Priority> {
            LOW, MEDIUM, HIGH
        }
        
        SortedList<Priority> priorities = new SortedList<>();
        priorities.add(Priority.HIGH);
        priorities.add(Priority.LOW);
        priorities.add(Priority.MEDIUM);
        
        System.out.println("  Sorted: " + priorities.getAll());
        
        System.out.println("\nCustom comparable:");
        
        class Person implements Comparable<Person> {
            String name;
            int age;
            
            Person(String name, int age) {
                this.name = name;
                this.age = age;
            }
            
            @Override
            public int compareTo(Person other) {
                return Integer.compare(this.age, other.age);
            }
            
            @Override
            public String toString() {
                return name + "(" + age + ")";
            }
        }
        
        SortedList<Person> people = new SortedList<>();
        people.add(new Person("Alice", 30));
        people.add(new Person("Bob", 25));
        people.add(new Person("Charlie", 35));
        
        System.out.println("  By age: " + people.getAll());
        
        System.out.println("\nMax finder:");
        
        class MaxFinder {
            public static <T extends Comparable<T>> T max(T a, T b, T c) {
                T temp = a.compareTo(b) > 0 ? a : b;
                return temp.compareTo(c) > 0 ? temp : c;
            }
            
            public static <T extends Comparable<T>> T maxOf(List<T> items) {
                if (items.isEmpty()) {
                    return null;
                }
                T max = items.get(0);
                for (T item : items) {
                    if (item.compareTo(max) > 0) {
                        max = item;
                    }
                }
                return max;
            }
        }
        
        Integer maxNum = MaxFinder.max(5, 10, 3);
        System.out.println("  Max(5, 10, 3): " + maxNum);
        
        String maxStr = MaxFinder.max("apple", "zebra", "banana");
        System.out.println("  Max strings: " + maxStr);
        
        List<Double> doubles = Arrays.asList(3.14, 2.71, 1.41, 1.73);
        Double maxDouble = MaxFinder.maxOf(doubles);
        System.out.println("  Max double: " + maxDouble);
        
        System.out.println("\nBST node:");
        
        class TreeNode<T extends Comparable<T>> {
            T value;
            TreeNode<T> left;
            TreeNode<T> right;
            
            TreeNode(T value) {
                this.value = value;
            }
            
            public void insert(T newValue) {
                if (newValue.compareTo(value) < 0) {
                    if (left == null) {
                        left = new TreeNode<>(newValue);
                        System.out.println("  Inserted left: " + newValue);
                    } else {
                        left.insert(newValue);
                    }
                } else {
                    if (right == null) {
                        right = new TreeNode<>(newValue);
                        System.out.println("  Inserted right: " + newValue);
                    } else {
                        right.insert(newValue);
                    }
                }
            }
            
            public boolean contains(T target) {
                if (value.equals(target)) {
                    return true;
                } else if (target.compareTo(value) < 0) {
                    return left != null && left.contains(target);
                } else {
                    return right != null && right.contains(target);
                }
            }
        }
        
        TreeNode<Integer> root = new TreeNode<>(50);
        root.insert(30);
        root.insert(70);
        root.insert(20);
        root.insert(40);
        
        int searchTarget = ;
        System.out.println("  Contains " + searchTarget + ": " +
                         root.contains(searchTarget));
        System.out.println("  Contains 60: " + root.contains(60));
    }
}
import java.util.*;

public class RecursiveBounds {
    static class SortedList<T extends Comparable<T>> {
        private List<T> items = new ArrayList<>();
        
        public void add(T item) {
            items.add(item);
            Collections.sort(items);
            System.out.println("  Added and sorted: " + item);
        }
        
        public T getMin() {
            return items.isEmpty() ? null : items.get(0);
        }
        
        public T getMax() {
            return items.isEmpty() ? null : items.get(items.size() - 1);
        }
        
        public List<T> getAll() {
            return new ArrayList<>(items);
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Recursive type bounds:\n");
        
        SortedList<Integer> numbers = new SortedList<>();
        numbers.add(5);
        numbers.add(2);
        numbers.add(8);
        numbers.add(1);
        
        System.out.println("  Min: " + numbers.getMin());
        System.out.println("  Max: " + numbers.getMax());
        System.out.println("  All: " + numbers.getAll());
        
        // <T extends Comparable<T>> is a recursive bound
        // T must be comparable to itself
        // Common pattern for sortable types
        // Ensures type-safe comparison
        
        System.out.println("\nWith enums:");
        
        enum Priority implements Comparable<Priority> {
            LOW, MEDIUM, HIGH
        }
        
        SortedList<Priority> priorities = new SortedList<>();
        priorities.add(Priority.HIGH);
        priorities.add(Priority.LOW);
        priorities.add(Priority.MEDIUM);
        
        System.out.println("  Sorted: " + priorities.getAll());
        
        System.out.println("\nCustom comparable:");
        
        class Person implements Comparable<Person> {
            String name;
            int age;
            
            Person(String name, int age) {
                this.name = name;
                this.age = age;
            }
            
            @Override
            public int compareTo(Person other) {
                return Integer.compare(this.age, other.age);
            }
            
            @Override
            public String toString() {
                return name + "(" + age + ")";
            }
        }
        
        SortedList<Person> people = new SortedList<>();
        people.add(new Person("Alice", 30));
        people.add(new Person("Bob", 25));
        people.add(new Person("Charlie", 35));
        
        System.out.println("  By age: " + people.getAll());
        
        System.out.println("\nMax finder:");
        
        class MaxFinder {
            public static <T extends Comparable<T>> T max(T a, T b, T c) {
                T temp = a.compareTo(b) > 0 ? a : b;
                return temp.compareTo(c) > 0 ? temp : c;
            }
            
            public static <T extends Comparable<T>> T maxOf(List<T> items) {
                if (items.isEmpty()) {
                    return null;
                }
                T max = items.get(0);
                for (T item : items) {
                    if (item.compareTo(max) > 0) {
                        max = item;
                    }
                }
                return max;
            }
        }
        
        Integer maxNum = MaxFinder.max(5, 10, 3);
        System.out.println("  Max(5, 10, 3): " + maxNum);
        
        String maxStr = MaxFinder.max("apple", "zebra", "banana");
        System.out.println("  Max strings: " + maxStr);
        
        List<Double> doubles = Arrays.asList(3.14, 2.71, 1.41, 1.73);
        Double maxDouble = MaxFinder.maxOf(doubles);
        System.out.println("  Max double: " + maxDouble);
        
        System.out.println("\nBST node:");
        
        class TreeNode<T extends Comparable<T>> {
            T value;
            TreeNode<T> left;
            TreeNode<T> right;
            
            TreeNode(T value) {
                this.value = value;
            }
            
            public void insert(T newValue) {
                if (newValue.compareTo(value) < 0) {
                    if (left == null) {
                        left = new TreeNode<>(newValue);
                        System.out.println("  Inserted left: " + newValue);
                    } else {
                        left.insert(newValue);
                    }
                } else {
                    if (right == null) {
                        right = new TreeNode<>(newValue);
                        System.out.println("  Inserted right: " + newValue);
                    } else {
                        right.insert(newValue);
                    }
                }
            }
            
            public boolean contains(T target) {
                if (value.equals(target)) {
                    return true;
                } else if (target.compareTo(value) < 0) {
                    return left != null && left.contains(target);
                } else {
                    return right != null && right.contains(target);
                }
            }
        }
        
        TreeNode<Integer> root = new TreeNode<>(50);
        root.insert(30);
        root.insert(70);
        root.insert(20);
        root.insert(40);
        
        int searchTarget = ;
        System.out.println("  Contains " + searchTarget + ": " +
                         root.contains(searchTarget));
        System.out.println("  Contains 60: " + root.contains(60));
    }
}
import java.util.*;

public class RecursiveBounds {
    static class SortedList<T extends Comparable<T>> {
        private List<T> items = new ArrayList<>();
        
        public void add(T item) {
            items.add(item);
            Collections.sort(items);
            System.out.println("  Added and sorted: " + item);
        }
        
        public T getMin() {
            return items.isEmpty() ? null : items.get(0);
        }
        
        public T getMax() {
            return items.isEmpty() ? null : items.get(items.size() - 1);
        }
        
        public List<T> getAll() {
            return new ArrayList<>(items);
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Recursive type bounds:\n");
        
        SortedList<Integer> numbers = new SortedList<>();
        numbers.add(5);
        numbers.add(2);
        numbers.add(8);
        numbers.add(1);
        
        System.out.println("  Min: " + numbers.getMin());
        System.out.println("  Max: " + numbers.getMax());
        System.out.println("  All: " + numbers.getAll());
        
        // <T extends Comparable<T>> is a recursive bound
        // T must be comparable to itself
        // Common pattern for sortable types
        // Ensures type-safe comparison
        
        System.out.println("\nWith enums:");
        
        enum Priority implements Comparable<Priority> {
            LOW, MEDIUM, HIGH
        }
        
        SortedList<Priority> priorities = new SortedList<>();
        priorities.add(Priority.HIGH);
        priorities.add(Priority.LOW);
        priorities.add(Priority.MEDIUM);
        
        System.out.println("  Sorted: " + priorities.getAll());
        
        System.out.println("\nCustom comparable:");
        
        class Person implements Comparable<Person> {
            String name;
            int age;
            
            Person(String name, int age) {
                this.name = name;
                this.age = age;
            }
            
            @Override
            public int compareTo(Person other) {
                return Integer.compare(this.age, other.age);
            }
            
            @Override
            public String toString() {
                return name + "(" + age + ")";
            }
        }
        
        SortedList<Person> people = new SortedList<>();
        people.add(new Person("Alice", 30));
        people.add(new Person("Bob", 25));
        people.add(new Person("Charlie", 35));
        
        System.out.println("  By age: " + people.getAll());
        
        System.out.println("\nMax finder:");
        
        class MaxFinder {
            public static <T extends Comparable<T>> T max(T a, T b, T c) {
                T temp = a.compareTo(b) > 0 ? a : b;
                return temp.compareTo(c) > 0 ? temp : c;
            }
            
            public static <T extends Comparable<T>> T maxOf(List<T> items) {
                if (items.isEmpty()) {
                    return null;
                }
                T max = items.get(0);
                for (T item : items) {
                    if (item.compareTo(max) > 0) {
                        max = item;
                    }
                }
                return max;
            }
        }
        
        Integer maxNum = MaxFinder.max(5, 10, 3);
        System.out.println("  Max(5, 10, 3): " + maxNum);
        
        String maxStr = MaxFinder.max("apple", "zebra", "banana");
        System.out.println("  Max strings: " + maxStr);
        
        List<Double> doubles = Arrays.asList(3.14, 2.71, 1.41, 1.73);
        Double maxDouble = MaxFinder.maxOf(doubles);
        System.out.println("  Max double: " + maxDouble);
        
        System.out.println("\nBST node:");
        
        class TreeNode<T extends Comparable<T>> {
            T value;
            TreeNode<T> left;
            TreeNode<T> right;
            
            TreeNode(T value) {
                this.value = value;
            }
            
            public void insert(T newValue) {
                if (newValue.compareTo(value) < 0) {
                    if (left == null) {
                        left = new TreeNode<>(newValue);
                        System.out.println("  Inserted left: " + newValue);
                    } else {
                        left.insert(newValue);
                    }
                } else {
                    if (right == null) {
                        right = new TreeNode<>(newValue);
                        System.out.println("  Inserted right: " + newValue);
                    } else {
                        right.insert(newValue);
                    }
                }
            }
            
            public boolean contains(T target) {
                if (value.equals(target)) {
                    return true;
                } else if (target.compareTo(value) < 0) {
                    return left != null && left.contains(target);
                } else {
                    return right != null && right.contains(target);
                }
            }
        }
        
        TreeNode<Integer> root = new TreeNode<>(50);
        root.insert(30);
        root.insert(70);
        root.insert(20);
        root.insert(40);
        
        int searchTarget = ;
        System.out.println("  Contains " + searchTarget + ": " +
                         root.contains(searchTarget));
        System.out.println("  Contains 60: " + root.contains(60));
    }
}

<T extends Comparable<T>> - T comparable to itself. Common for sorting.

recursive bound Self-referential bound: `<T extends Comparable<T>>`. Enables self-comparison.

Bounds and inheritance

How bounded generics interact with class hierarchies.

BoundsInheritance.java
import java.util.*;

public class BoundsInheritance {
    static abstract class Entity {
        private Long id;
        
        public Long getId() { return id; }
        public void setId(Long id) { this.id = id; }
    }
    
    static class Repository<T extends Entity> {
        private Map<Long, T> store = new LinkedHashMap<>();
        private Long nextId = 1L;
        
        public void save(T entity) {
            if (entity.getId() == null) {
                entity.setId(nextId++);
            }
            store.put(entity.getId(), entity);
            System.out.println("  Saved: " + entity.getClass().getSimpleName() + 
                             " id=" + entity.getId());
        }
        
        public T findById(Long id) {
            return store.get(id);
        }
        
        public List<T> findAll() {
            return new ArrayList<>(store.values());
        }
        
        public int count() {
            return store.size();
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Bounds with inheritance:\n");
        
        class User extends Entity {
            String name;
            
            User(String name) {
                this.name = name;
            }
            
            @Override
            public String toString() {
                return "User(" + getId() + ", " + name + ")";
            }
        }
        
        Repository<User> userRepo = new Repository<>();
        
        User user1 = new User("Alice");
        User user2 = new User("Bob");
        
        userRepo.save(user1);
        userRepo.save(user2);
        
        System.out.println("  All users: " + userRepo.findAll());
        System.out.println("  Count: " + userRepo.count());
        
        // Bounded type allows accessing members of bound class
        // All entities have getId/setId methods
        // Type parameter must extend (or implement) the bound
        // Enables polymorphic behavior with type safety
        
        System.out.println("\nMultiple entity types:");
        
        class Product extends Entity {
            String name;
            double price;
            
            Product(String name, double price) {
                this.name = name;
                this.price = price;
            }
            
            @Override
            public String toString() {
                return "Product(" + getId() + ", " + name + ", $" + price + ")";
            }
        }
        
        Repository<Product> productRepo = new Repository<>();
        productRepo.save(new Product("Laptop", 999.99));
        productRepo.save(new Product("Mouse", 29.99));
        
        System.out.println("  All products: " + productRepo.findAll());
        
        System.out.println("\nService layer:");
        
        class Service<T extends Entity> {
            private Repository<T> repository;
            
            public Service(Repository<T> repository) {
                this.repository = repository;
            }
            
            public T create(T entity) {
                repository.save(entity);
                System.out.println("  Created: " + entity);
                return entity;
            }
            
            public T update(T entity) {
                if (entity.getId() == null) {
                    throw new IllegalArgumentException("Cannot update without ID");
                }
                repository.save(entity);
                System.out.println("  Updated: " + entity);
                return entity;
            }
            
            public T getById(Long id) {
                T entity = repository.findById(id);
                if (entity == null) {
                    System.out.println("  Not found: " + id);
                }
                return entity;
            }
        }
        
        Service<User> userService = new Service<>(userRepo);
        User newUser = userService.create(new User("Charlie"));
        User found = userService.getById(newUser.getId());
        System.out.println("  Retrieved: " + found);
        
        System.out.println("\nAuditable entities:");
        
        abstract class Auditable extends Entity {
            private long createdAt = 1000L;
            
            public long getCreatedAt() { return createdAt; }
        }
        
        class AuditableRepository<T extends Auditable> extends Repository<T> {
            @Override
            public void save(T entity) {
                super.save(entity);
                System.out.println("  Created at: " + entity.getCreatedAt());
            }
        }
        
        class Order extends Auditable {
            String product;
            int quantity;
            
            Order(String product, int quantity) {
                this.product = product;
                this.quantity = quantity;
            }
            
            @Override
            public String toString() {
                return "Order(" + getId() + ", " + product + " x" + quantity + ")";
            }
        }
        
        AuditableRepository<Order> orderRepo = new AuditableRepository<>();
        orderRepo.save(new Order("Laptop", 1));
        orderRepo.save(new Order("Mouse", 2));
        
        System.out.println("\nType hierarchy:");
        
        abstract class Animal {
            abstract String speak();
        }
        
        class Dog extends Animal {
            @Override
            String speak() { return "Woof"; }
        }
        
        class Cat extends Animal {
            @Override
            String speak() { return "Meow"; }
        }
        
        class AnimalShelter<T extends Animal> {
            private List<T> animals = new ArrayList<>();
            
            public void add(T animal) {
                animals.add(animal);
                System.out.println("  Added: " + animal.speak());
            }
            
            public void makeAllSpeak() {
                for (T animal : animals) {
                    System.out.println("  " + animal.speak());
                }
            }
        }
        
        AnimalShelter<Dog> dogShelter = new AnimalShelter<>();
        dogShelter.add(new Dog());
        dogShelter.add(new Dog());
        dogShelter.makeAllSpeak();
    }
}

Subclasses satisfy superclass bounds. Integer extends Number satisfies <T extends Number>.

Exercise: Practical.java

Build a bounded generic collection for numbers