Your Box class stores an Object. When you get it out, you cast to String - but what if someone put an Integer in? Generics let you say Box<String> - the compiler ensures only Strings go in, no casting needed.

Generic class

Define a class with a type parameter.

GenericClass.java
public class GenericClass {
    static class Box<T> {
        private T value;
        
        public void set(T value) {
            this.value = value;
        }
        
        public T get() {
            return value;
        }
        
        public boolean hasValue() {
            return value != null;
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Generic class:\n");
        
        // Box for Integer
        Box<Integer> intBox = new Box<>();
        intBox.set(42);
        System.out.println("  Integer box: " + intBox.get());
        
        // Box for String
        Box<String> strBox = new Box<>();
        strBox.set("Hello");
        System.out.println("  String box: " + strBox.get());
        
        // Box for Double
        Box<Double> doubleBox = new Box<>();
        doubleBox.set(3.14);
        System.out.println("  Double box: " + doubleBox.get());
        
        // T is a type parameter (placeholder for actual type)
        // Specified at instantiation: Box<Integer>
        // Provides compile-time type safety
        // No casting needed when retrieving value
        
        System.out.println("\nType safety:");
        
        Box<String> box = new Box<>();
        box.set("Text");
        // box.set(123); // Compile error - type mismatch
        
        String value = box.get(); // No cast needed
        System.out.println("  Value: " + value);
        
        System.out.println("\nWithout generics (old way):");
        
        class ObjectBox {
            private Object value;
            
            public void set(Object value) {
                this.value = value;
            }
            
            public Object get() {
                return value;
            }
        }
        
        ObjectBox oldBox = new ObjectBox();
        oldBox.set("Text");
        String str = (String) oldBox.get(); // Cast required
        System.out.println("  Value: " + str);
        
        // Runtime error possible:
        oldBox.set(123);
        // String wrong = (String) oldBox.get(); // ClassCastException at runtime
        
        System.out.println("\nMultiple type uses:");
        
        Box<Integer> scores = new Box<>();
        scores.set(95);
        
        Box<String> name = new Box<>();
        name.set("Alice");
        
        Box<Boolean> active = new Box<>();
        active.set(true);
        
        System.out.println("  Score: " + scores.get());
        System.out.println("  Name: " + name.get());
        System.out.println("  Active: " + active.get());
    }
}

class Box<T> - T is a placeholder for any type. Specified when used.

generics Type parameters: `Box<T>`. Compiler enforces type safety. No runtime casts.

Generic with methods

Methods using the type parameter.

GenericWithMethods.java
public class GenericWithMethods {
    static class Container<T> {
        private T item;
        
        public void store(T item) {
            this.item = item;
            System.out.println("  Stored: " + item);
        }
        
        public T retrieve() {
            System.out.println("  Retrieved: " + item);
            return item;
        }
        
        public boolean isEmpty() {
            return item == null;
        }
        
        public void clear() {
            item = null;
            System.out.println("  Cleared");
        }
        
        public String getInfo() {
            if (item == null) {
                return "Empty container";
            }
            return "Container holding: " + item.getClass().getSimpleName();
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Generic methods:\n");
        
        Container<String> strContainer = new Container<>();
        strContainer.store("Hello");
        String value = strContainer.retrieve();
        System.out.println("  Info: " + strContainer.getInfo());
        
        // Methods in generic class can use type parameter T
        // Parameters and return types can be T
        // Type is consistent throughout the instance
        
        System.out.println("\nInteger container:");
        
        Container<Integer> intContainer = new Container<>();
        System.out.println("  Empty: " + intContainer.isEmpty());
        intContainer.store(42);
        System.out.println("  Empty: " + intContainer.isEmpty());
        int num = intContainer.retrieve();
        System.out.println("  Value: " + num);
        
        System.out.println("\nPair class:");
        
        class Pair<T> {
            private T first;
            private T second;
            
            public Pair(T first, T second) {
                this.first = first;
                this.second = second;
            }
            
            public T getFirst() {
                return first;
            }
            
            public T getSecond() {
                return second;
            }
            
            public void swap() {
                T temp = first;
                first = second;
                second = temp;
            }
            
            public void display() {
                System.out.println("  First: " + first + ", Second: " + second);
            }
        }
        
        Pair<String> namePair = new Pair<>("Alice", "Bob");
        namePair.display();
        namePair.swap();
        System.out.println("  After swap:");
        namePair.display();
        
        System.out.println("\nPoint class:");
        
        class Point<T extends Number> {
            private T x;
            private T y;
            
            public Point(T x, T y) {
                this.x = x;
                this.y = y;
            }
            
            public T getX() { return x; }
            public T getY() { return y; }
            
            public double distance() {
                double dx = x.doubleValue();
                double dy = y.doubleValue();
                return Math.sqrt(dx * dx + dy * dy);
            }
            
            public void display() {
                System.out.println("  Point(" + x + ", " + y + ")");
            }
        }
        
        int pointX = ;
        Point<Integer> intPoint = new Point<>(pointX, 4);
        intPoint.display();
        System.out.println("  Distance: " + intPoint.distance());
        
        Point<Double> doublePoint = new Point<>(1.5, 2.5);
        doublePoint.display();
        System.out.println("  Distance: " + doublePoint.distance());
    }
}
public class GenericWithMethods {
    static class Container<T> {
        private T item;
        
        public void store(T item) {
            this.item = item;
            System.out.println("  Stored: " + item);
        }
        
        public T retrieve() {
            System.out.println("  Retrieved: " + item);
            return item;
        }
        
        public boolean isEmpty() {
            return item == null;
        }
        
        public void clear() {
            item = null;
            System.out.println("  Cleared");
        }
        
        public String getInfo() {
            if (item == null) {
                return "Empty container";
            }
            return "Container holding: " + item.getClass().getSimpleName();
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Generic methods:\n");
        
        Container<String> strContainer = new Container<>();
        strContainer.store("Hello");
        String value = strContainer.retrieve();
        System.out.println("  Info: " + strContainer.getInfo());
        
        // Methods in generic class can use type parameter T
        // Parameters and return types can be T
        // Type is consistent throughout the instance
        
        System.out.println("\nInteger container:");
        
        Container<Integer> intContainer = new Container<>();
        System.out.println("  Empty: " + intContainer.isEmpty());
        intContainer.store(42);
        System.out.println("  Empty: " + intContainer.isEmpty());
        int num = intContainer.retrieve();
        System.out.println("  Value: " + num);
        
        System.out.println("\nPair class:");
        
        class Pair<T> {
            private T first;
            private T second;
            
            public Pair(T first, T second) {
                this.first = first;
                this.second = second;
            }
            
            public T getFirst() {
                return first;
            }
            
            public T getSecond() {
                return second;
            }
            
            public void swap() {
                T temp = first;
                first = second;
                second = temp;
            }
            
            public void display() {
                System.out.println("  First: " + first + ", Second: " + second);
            }
        }
        
        Pair<String> namePair = new Pair<>("Alice", "Bob");
        namePair.display();
        namePair.swap();
        System.out.println("  After swap:");
        namePair.display();
        
        System.out.println("\nPoint class:");
        
        class Point<T extends Number> {
            private T x;
            private T y;
            
            public Point(T x, T y) {
                this.x = x;
                this.y = y;
            }
            
            public T getX() { return x; }
            public T getY() { return y; }
            
            public double distance() {
                double dx = x.doubleValue();
                double dy = y.doubleValue();
                return Math.sqrt(dx * dx + dy * dy);
            }
            
            public void display() {
                System.out.println("  Point(" + x + ", " + y + ")");
            }
        }
        
        int pointX = ;
        Point<Integer> intPoint = new Point<>(pointX, 4);
        intPoint.display();
        System.out.println("  Distance: " + intPoint.distance());
        
        Point<Double> doublePoint = new Point<>(1.5, 2.5);
        doublePoint.display();
        System.out.println("  Distance: " + doublePoint.distance());
    }
}
public class GenericWithMethods {
    static class Container<T> {
        private T item;
        
        public void store(T item) {
            this.item = item;
            System.out.println("  Stored: " + item);
        }
        
        public T retrieve() {
            System.out.println("  Retrieved: " + item);
            return item;
        }
        
        public boolean isEmpty() {
            return item == null;
        }
        
        public void clear() {
            item = null;
            System.out.println("  Cleared");
        }
        
        public String getInfo() {
            if (item == null) {
                return "Empty container";
            }
            return "Container holding: " + item.getClass().getSimpleName();
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Generic methods:\n");
        
        Container<String> strContainer = new Container<>();
        strContainer.store("Hello");
        String value = strContainer.retrieve();
        System.out.println("  Info: " + strContainer.getInfo());
        
        // Methods in generic class can use type parameter T
        // Parameters and return types can be T
        // Type is consistent throughout the instance
        
        System.out.println("\nInteger container:");
        
        Container<Integer> intContainer = new Container<>();
        System.out.println("  Empty: " + intContainer.isEmpty());
        intContainer.store(42);
        System.out.println("  Empty: " + intContainer.isEmpty());
        int num = intContainer.retrieve();
        System.out.println("  Value: " + num);
        
        System.out.println("\nPair class:");
        
        class Pair<T> {
            private T first;
            private T second;
            
            public Pair(T first, T second) {
                this.first = first;
                this.second = second;
            }
            
            public T getFirst() {
                return first;
            }
            
            public T getSecond() {
                return second;
            }
            
            public void swap() {
                T temp = first;
                first = second;
                second = temp;
            }
            
            public void display() {
                System.out.println("  First: " + first + ", Second: " + second);
            }
        }
        
        Pair<String> namePair = new Pair<>("Alice", "Bob");
        namePair.display();
        namePair.swap();
        System.out.println("  After swap:");
        namePair.display();
        
        System.out.println("\nPoint class:");
        
        class Point<T extends Number> {
            private T x;
            private T y;
            
            public Point(T x, T y) {
                this.x = x;
                this.y = y;
            }
            
            public T getX() { return x; }
            public T getY() { return y; }
            
            public double distance() {
                double dx = x.doubleValue();
                double dy = y.doubleValue();
                return Math.sqrt(dx * dx + dy * dy);
            }
            
            public void display() {
                System.out.println("  Point(" + x + ", " + y + ")");
            }
        }
        
        int pointX = ;
        Point<Integer> intPoint = new Point<>(pointX, 4);
        intPoint.display();
        System.out.println("  Distance: " + intPoint.distance());
        
        Point<Double> doublePoint = new Point<>(1.5, 2.5);
        doublePoint.display();
        System.out.println("  Distance: " + doublePoint.distance());
    }
}

Type parameter T available in fields, methods, and return types.

Multiple type parameters

Use more than one type parameter.

MultipleTypeParams.java
import java.util.*;

public class MultipleTypeParams {
    static class Pair<K, V> {
        private K key;
        private V value;
        
        public Pair(K key, V value) {
            this.key = key;
            this.value = value;
        }
        
        public K getKey() {
            return key;
        }
        
        public V getValue() {
            return value;
        }
        
        public void display() {
            System.out.println("  " + key + " -> " + value);
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Multiple type parameters:\n");
        
        Pair<String, Integer> agePair = new Pair<>("Alice", 30);
        agePair.display();
        
        Pair<Integer, String> namePair = new Pair<>(1, "Bob");
        namePair.display();
        
        Pair<String, Double> pricePair = new Pair<>("Coffee", 3.50);
        pricePair.display();
        
        // Multiple type parameters separated by commas: <K, V>
        // Each can be a different type
        // Common in key-value data structures
        
        System.out.println("\nSimple map:");
        
        class SimpleMap<K, V> {
            private List<K> keys = new ArrayList<>();
            private List<V> values = new ArrayList<>();
            
            public void put(K key, V value) {
                keys.add(key);
                values.add(value);
            }
            
            public V get(K key) {
                int index = keys.indexOf(key);
                return index >= 0 ? values.get(index) : null;
            }
            
            public void display() {
                for (int i = 0; i < keys.size(); i++) {
                    System.out.println("  " + keys.get(i) + " = " + values.get(i));
                }
            }
        }
        
        SimpleMap<String, Integer> scores = new SimpleMap<>();
        scores.put("Alice", 95);
        scores.put("Bob", 87);
        scores.put("Charlie", 92);
        scores.display();
        
        String lookupName = ;
        System.out.println("  " + lookupName + "'s score: " + scores.get(lookupName));
        
        System.out.println("\nTriple class:");
        
        class Triple<A, B, C> {
            private A first;
            private B second;
            private C third;
            
            public Triple(A first, B second, C third) {
                this.first = first;
                this.second = second;
                this.third = third;
            }
            
            public A getFirst() { return first; }
            public B getSecond() { return second; }
            public C getThird() { return third; }
            
            public void display() {
                System.out.println("  (" + first + ", " + second + ", " + third + ")");
            }
        }
        
        Triple<String, Integer, Double> studentRecord = 
            new Triple<>("Alice", 20, 3.8);
        studentRecord.display();
        
        Triple<Integer, String, Boolean> config = 
            new Triple<>(8080, "localhost", true);
        config.display();
        
        System.out.println("\nEntry class:");
        
        class Entry<K, V> {
            private K key;
            private V value;
            private long timestamp;
            
            public Entry(K key, V value) {
                this.key = key;
                this.value = value;
                this.timestamp = 1000L;
            }
            
            public K getKey() { return key; }
            public V getValue() { return value; }
            
            public void setValue(V value) {
                this.value = value;
                this.timestamp = 1001L;
            }
            
            public void display() {
                System.out.println("  " + key + ": " + value + 
                                   " (ts: " + timestamp + ")");
            }
        }
        
        Entry<String, String> config1 = new Entry<>("host", "localhost");
        config1.display();
        
        Entry<Integer, List<String>> userTags = 
            new Entry<>(101, Arrays.asList("admin", "active"));
        userTags.display();
    }
}
import java.util.*;

public class MultipleTypeParams {
    static class Pair<K, V> {
        private K key;
        private V value;
        
        public Pair(K key, V value) {
            this.key = key;
            this.value = value;
        }
        
        public K getKey() {
            return key;
        }
        
        public V getValue() {
            return value;
        }
        
        public void display() {
            System.out.println("  " + key + " -> " + value);
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Multiple type parameters:\n");
        
        Pair<String, Integer> agePair = new Pair<>("Alice", 30);
        agePair.display();
        
        Pair<Integer, String> namePair = new Pair<>(1, "Bob");
        namePair.display();
        
        Pair<String, Double> pricePair = new Pair<>("Coffee", 3.50);
        pricePair.display();
        
        // Multiple type parameters separated by commas: <K, V>
        // Each can be a different type
        // Common in key-value data structures
        
        System.out.println("\nSimple map:");
        
        class SimpleMap<K, V> {
            private List<K> keys = new ArrayList<>();
            private List<V> values = new ArrayList<>();
            
            public void put(K key, V value) {
                keys.add(key);
                values.add(value);
            }
            
            public V get(K key) {
                int index = keys.indexOf(key);
                return index >= 0 ? values.get(index) : null;
            }
            
            public void display() {
                for (int i = 0; i < keys.size(); i++) {
                    System.out.println("  " + keys.get(i) + " = " + values.get(i));
                }
            }
        }
        
        SimpleMap<String, Integer> scores = new SimpleMap<>();
        scores.put("Alice", 95);
        scores.put("Bob", 87);
        scores.put("Charlie", 92);
        scores.display();
        
        String lookupName = ;
        System.out.println("  " + lookupName + "'s score: " + scores.get(lookupName));
        
        System.out.println("\nTriple class:");
        
        class Triple<A, B, C> {
            private A first;
            private B second;
            private C third;
            
            public Triple(A first, B second, C third) {
                this.first = first;
                this.second = second;
                this.third = third;
            }
            
            public A getFirst() { return first; }
            public B getSecond() { return second; }
            public C getThird() { return third; }
            
            public void display() {
                System.out.println("  (" + first + ", " + second + ", " + third + ")");
            }
        }
        
        Triple<String, Integer, Double> studentRecord = 
            new Triple<>("Alice", 20, 3.8);
        studentRecord.display();
        
        Triple<Integer, String, Boolean> config = 
            new Triple<>(8080, "localhost", true);
        config.display();
        
        System.out.println("\nEntry class:");
        
        class Entry<K, V> {
            private K key;
            private V value;
            private long timestamp;
            
            public Entry(K key, V value) {
                this.key = key;
                this.value = value;
                this.timestamp = 1000L;
            }
            
            public K getKey() { return key; }
            public V getValue() { return value; }
            
            public void setValue(V value) {
                this.value = value;
                this.timestamp = 1001L;
            }
            
            public void display() {
                System.out.println("  " + key + ": " + value + 
                                   " (ts: " + timestamp + ")");
            }
        }
        
        Entry<String, String> config1 = new Entry<>("host", "localhost");
        config1.display();
        
        Entry<Integer, List<String>> userTags = 
            new Entry<>(101, Arrays.asList("admin", "active"));
        userTags.display();
    }
}
import java.util.*;

public class MultipleTypeParams {
    static class Pair<K, V> {
        private K key;
        private V value;
        
        public Pair(K key, V value) {
            this.key = key;
            this.value = value;
        }
        
        public K getKey() {
            return key;
        }
        
        public V getValue() {
            return value;
        }
        
        public void display() {
            System.out.println("  " + key + " -> " + value);
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Multiple type parameters:\n");
        
        Pair<String, Integer> agePair = new Pair<>("Alice", 30);
        agePair.display();
        
        Pair<Integer, String> namePair = new Pair<>(1, "Bob");
        namePair.display();
        
        Pair<String, Double> pricePair = new Pair<>("Coffee", 3.50);
        pricePair.display();
        
        // Multiple type parameters separated by commas: <K, V>
        // Each can be a different type
        // Common in key-value data structures
        
        System.out.println("\nSimple map:");
        
        class SimpleMap<K, V> {
            private List<K> keys = new ArrayList<>();
            private List<V> values = new ArrayList<>();
            
            public void put(K key, V value) {
                keys.add(key);
                values.add(value);
            }
            
            public V get(K key) {
                int index = keys.indexOf(key);
                return index >= 0 ? values.get(index) : null;
            }
            
            public void display() {
                for (int i = 0; i < keys.size(); i++) {
                    System.out.println("  " + keys.get(i) + " = " + values.get(i));
                }
            }
        }
        
        SimpleMap<String, Integer> scores = new SimpleMap<>();
        scores.put("Alice", 95);
        scores.put("Bob", 87);
        scores.put("Charlie", 92);
        scores.display();
        
        String lookupName = ;
        System.out.println("  " + lookupName + "'s score: " + scores.get(lookupName));
        
        System.out.println("\nTriple class:");
        
        class Triple<A, B, C> {
            private A first;
            private B second;
            private C third;
            
            public Triple(A first, B second, C third) {
                this.first = first;
                this.second = second;
                this.third = third;
            }
            
            public A getFirst() { return first; }
            public B getSecond() { return second; }
            public C getThird() { return third; }
            
            public void display() {
                System.out.println("  (" + first + ", " + second + ", " + third + ")");
            }
        }
        
        Triple<String, Integer, Double> studentRecord = 
            new Triple<>("Alice", 20, 3.8);
        studentRecord.display();
        
        Triple<Integer, String, Boolean> config = 
            new Triple<>(8080, "localhost", true);
        config.display();
        
        System.out.println("\nEntry class:");
        
        class Entry<K, V> {
            private K key;
            private V value;
            private long timestamp;
            
            public Entry(K key, V value) {
                this.key = key;
                this.value = value;
                this.timestamp = 1000L;
            }
            
            public K getKey() { return key; }
            public V getValue() { return value; }
            
            public void setValue(V value) {
                this.value = value;
                this.timestamp = 1001L;
            }
            
            public void display() {
                System.out.println("  " + key + ": " + value + 
                                   " (ts: " + timestamp + ")");
            }
        }
        
        Entry<String, String> config1 = new Entry<>("host", "localhost");
        config1.display();
        
        Entry<Integer, List<String>> userTags = 
            new Entry<>(101, Arrays.asList("admin", "active"));
        userTags.display();
    }
}

class Pair<K, V> - K for key, V for value. Like Map entries.

type parameter Placeholder for type: T, E, K, V. Convention: T=Type, E=Element, K=Key, V=Value.

Generic interface

Define interfaces with type parameters.

GenericInterface.java
import java.util.*;

public class GenericInterface {
    interface Container<T> {
        void add(T item);
        T get(int index);
        int size();
        boolean isEmpty();
    }
    
    static class ListContainer<T> implements Container<T> {
        private List<T> items = new ArrayList<>();
        
        @Override
        public void add(T item) {
            items.add(item);
            System.out.println("  Added: " + item);
        }
        
        @Override
        public T get(int index) {
            return items.get(index);
        }
        
        @Override
        public int size() {
            return items.size();
        }
        
        @Override
        public boolean isEmpty() {
            return items.isEmpty();
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Generic interface:\n");
        
        Container<String> strContainer = new ListContainer<>();
        strContainer.add("Apple");
        strContainer.add("Banana");
        strContainer.add("Cherry");
        
        System.out.println("  Size: " + strContainer.size());
        System.out.println("  First: " + strContainer.get(0));
        
        // Interfaces can have type parameters
        // Implementing class specifies type or remains generic
        // Same type safety benefits as generic classes
        
        System.out.println("\nStorage interface:");
        
        interface Storage<T> {
            void store(T item);
            T retrieve();
            void clear();
        }
        
        class MemoryStorage<T> implements Storage<T> {
            private T item;
            
            @Override
            public void store(T item) {
                this.item = item;
                System.out.println("  Stored: " + item);
            }
            
            @Override
            public T retrieve() {
                return item;
            }
            
            @Override
            public void clear() {
                item = null;
                System.out.println("  Cleared");
            }
        }
        
        Storage<Integer> intStorage = new MemoryStorage<>();
        intStorage.store(42);
        int value = intStorage.retrieve();
        System.out.println("  Retrieved: " + value);
        
        System.out.println("\nConcrete implementation:");
        
        class StringContainer implements Container<String> {
            private List<String> items = new ArrayList<>();
            
            @Override
            public void add(String item) {
                items.add(item.toUpperCase());
                System.out.println("  Added (upper): " + item);
            }
            
            @Override
            public String get(int index) {
                return items.get(index);
            }
            
            @Override
            public int size() {
                return items.size();
            }
            
            @Override
            public boolean isEmpty() {
                return items.isEmpty();
            }
        }
        
        Container<String> concrete = new StringContainer();
        concrete.add("hello");
        concrete.add("world");
        System.out.println("  First: " + concrete.get(0));
        
        System.out.println("\nComparable-like:");
        
        interface Comparable<T> {
            int compareTo(T other);
        }
        
        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 + ")";
            }
        }
        
        Person p1 = new Person("Alice", 30);
        Person p2 = new Person("Bob", 25);
        
        int result = p1.compareTo(p2);
        System.out.println("  " + p1 + " vs " + p2 + ": " + result);
        
        System.out.println("\nRepository pattern:");
        
        interface Repository<T, ID> {
            void save(T entity);
            T findById(ID id);
        }
        
        class UserRepository implements Repository<String, Integer> {
            private Map<Integer, String> users = new HashMap<>();
            
            @Override
            public void save(String user) {
                int id = users.size() + 1;
                users.put(id, user);
                System.out.println("  Saved user: " + user + " (id=" + id + ")");
            }
            
            @Override
            public String findById(Integer id) {
                return users.get(id);
            }
        }
        
        Repository<String, Integer> repo = new UserRepository();
        repo.save("Alice");
        repo.save("Bob");
        int lookupId = ;
        String user = repo.findById(lookupId);
        System.out.println("  User " + lookupId + ": " + user);
    }
}
import java.util.*;

public class GenericInterface {
    interface Container<T> {
        void add(T item);
        T get(int index);
        int size();
        boolean isEmpty();
    }
    
    static class ListContainer<T> implements Container<T> {
        private List<T> items = new ArrayList<>();
        
        @Override
        public void add(T item) {
            items.add(item);
            System.out.println("  Added: " + item);
        }
        
        @Override
        public T get(int index) {
            return items.get(index);
        }
        
        @Override
        public int size() {
            return items.size();
        }
        
        @Override
        public boolean isEmpty() {
            return items.isEmpty();
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Generic interface:\n");
        
        Container<String> strContainer = new ListContainer<>();
        strContainer.add("Apple");
        strContainer.add("Banana");
        strContainer.add("Cherry");
        
        System.out.println("  Size: " + strContainer.size());
        System.out.println("  First: " + strContainer.get(0));
        
        // Interfaces can have type parameters
        // Implementing class specifies type or remains generic
        // Same type safety benefits as generic classes
        
        System.out.println("\nStorage interface:");
        
        interface Storage<T> {
            void store(T item);
            T retrieve();
            void clear();
        }
        
        class MemoryStorage<T> implements Storage<T> {
            private T item;
            
            @Override
            public void store(T item) {
                this.item = item;
                System.out.println("  Stored: " + item);
            }
            
            @Override
            public T retrieve() {
                return item;
            }
            
            @Override
            public void clear() {
                item = null;
                System.out.println("  Cleared");
            }
        }
        
        Storage<Integer> intStorage = new MemoryStorage<>();
        intStorage.store(42);
        int value = intStorage.retrieve();
        System.out.println("  Retrieved: " + value);
        
        System.out.println("\nConcrete implementation:");
        
        class StringContainer implements Container<String> {
            private List<String> items = new ArrayList<>();
            
            @Override
            public void add(String item) {
                items.add(item.toUpperCase());
                System.out.println("  Added (upper): " + item);
            }
            
            @Override
            public String get(int index) {
                return items.get(index);
            }
            
            @Override
            public int size() {
                return items.size();
            }
            
            @Override
            public boolean isEmpty() {
                return items.isEmpty();
            }
        }
        
        Container<String> concrete = new StringContainer();
        concrete.add("hello");
        concrete.add("world");
        System.out.println("  First: " + concrete.get(0));
        
        System.out.println("\nComparable-like:");
        
        interface Comparable<T> {
            int compareTo(T other);
        }
        
        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 + ")";
            }
        }
        
        Person p1 = new Person("Alice", 30);
        Person p2 = new Person("Bob", 25);
        
        int result = p1.compareTo(p2);
        System.out.println("  " + p1 + " vs " + p2 + ": " + result);
        
        System.out.println("\nRepository pattern:");
        
        interface Repository<T, ID> {
            void save(T entity);
            T findById(ID id);
        }
        
        class UserRepository implements Repository<String, Integer> {
            private Map<Integer, String> users = new HashMap<>();
            
            @Override
            public void save(String user) {
                int id = users.size() + 1;
                users.put(id, user);
                System.out.println("  Saved user: " + user + " (id=" + id + ")");
            }
            
            @Override
            public String findById(Integer id) {
                return users.get(id);
            }
        }
        
        Repository<String, Integer> repo = new UserRepository();
        repo.save("Alice");
        repo.save("Bob");
        int lookupId = ;
        String user = repo.findById(lookupId);
        System.out.println("  User " + lookupId + ": " + user);
    }
}
import java.util.*;

public class GenericInterface {
    interface Container<T> {
        void add(T item);
        T get(int index);
        int size();
        boolean isEmpty();
    }
    
    static class ListContainer<T> implements Container<T> {
        private List<T> items = new ArrayList<>();
        
        @Override
        public void add(T item) {
            items.add(item);
            System.out.println("  Added: " + item);
        }
        
        @Override
        public T get(int index) {
            return items.get(index);
        }
        
        @Override
        public int size() {
            return items.size();
        }
        
        @Override
        public boolean isEmpty() {
            return items.isEmpty();
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Generic interface:\n");
        
        Container<String> strContainer = new ListContainer<>();
        strContainer.add("Apple");
        strContainer.add("Banana");
        strContainer.add("Cherry");
        
        System.out.println("  Size: " + strContainer.size());
        System.out.println("  First: " + strContainer.get(0));
        
        // Interfaces can have type parameters
        // Implementing class specifies type or remains generic
        // Same type safety benefits as generic classes
        
        System.out.println("\nStorage interface:");
        
        interface Storage<T> {
            void store(T item);
            T retrieve();
            void clear();
        }
        
        class MemoryStorage<T> implements Storage<T> {
            private T item;
            
            @Override
            public void store(T item) {
                this.item = item;
                System.out.println("  Stored: " + item);
            }
            
            @Override
            public T retrieve() {
                return item;
            }
            
            @Override
            public void clear() {
                item = null;
                System.out.println("  Cleared");
            }
        }
        
        Storage<Integer> intStorage = new MemoryStorage<>();
        intStorage.store(42);
        int value = intStorage.retrieve();
        System.out.println("  Retrieved: " + value);
        
        System.out.println("\nConcrete implementation:");
        
        class StringContainer implements Container<String> {
            private List<String> items = new ArrayList<>();
            
            @Override
            public void add(String item) {
                items.add(item.toUpperCase());
                System.out.println("  Added (upper): " + item);
            }
            
            @Override
            public String get(int index) {
                return items.get(index);
            }
            
            @Override
            public int size() {
                return items.size();
            }
            
            @Override
            public boolean isEmpty() {
                return items.isEmpty();
            }
        }
        
        Container<String> concrete = new StringContainer();
        concrete.add("hello");
        concrete.add("world");
        System.out.println("  First: " + concrete.get(0));
        
        System.out.println("\nComparable-like:");
        
        interface Comparable<T> {
            int compareTo(T other);
        }
        
        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 + ")";
            }
        }
        
        Person p1 = new Person("Alice", 30);
        Person p2 = new Person("Bob", 25);
        
        int result = p1.compareTo(p2);
        System.out.println("  " + p1 + " vs " + p2 + ": " + result);
        
        System.out.println("\nRepository pattern:");
        
        interface Repository<T, ID> {
            void save(T entity);
            T findById(ID id);
        }
        
        class UserRepository implements Repository<String, Integer> {
            private Map<Integer, String> users = new HashMap<>();
            
            @Override
            public void save(String user) {
                int id = users.size() + 1;
                users.put(id, user);
                System.out.println("  Saved user: " + user + " (id=" + id + ")");
            }
            
            @Override
            public String findById(Integer id) {
                return users.get(id);
            }
        }
        
        Repository<String, Integer> repo = new UserRepository();
        repo.save("Alice");
        repo.save("Bob");
        int lookupId = ;
        String user = repo.findById(lookupId);
        System.out.println("  User " + lookupId + ": " + user);
    }
}

interface Container<T> - implementations specify the type.

Diamond operator

Let compiler infer type arguments.

DiamondOperator.java
import java.util.*;

public class DiamondOperator {
    static class Box<T> {
        private T value;
        
        public Box(T value) {
            this.value = value;
        }
        
        public T getValue() {
            return value;
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Diamond operator:\n");
        
        // Before Java 7 (verbose)
        Box<String> box1 = new Box<String>("Hello");
        System.out.println("  Old way: " + box1.getValue());
        
        // Java 7+ (diamond operator)
        Box<String> box2 = new Box<>("World");
        System.out.println("  Diamond: " + box2.getValue());
        
        // Diamond operator <> infers type from left side
        // Available since Java 7
        // Reduces verbosity while maintaining type safety
        // Compiler infers the type parameter
        
        System.out.println("\nWith collections:");
        
        // Verbose
        List<String> list1 = new ArrayList<String>();
        list1.add("Apple");
        
        // Diamond
        List<String> list2 = new ArrayList<>();
        list2.add("Banana");
        
        Map<String, Integer> map = new LinkedHashMap<>();
        map.put("Alice", 30);
        map.put("Bob", 25);
        
        System.out.println("  List: " + list2);
        System.out.println("  Map: " + map);
        
        System.out.println("\nNested generics:");
        
        // Before Java 7 (very verbose)
        Map<String, List<Integer>> map1 = 
            new HashMap<String, List<Integer>>();
        
        // With diamond (cleaner)
        Map<String, List<Integer>> map2 = new LinkedHashMap<>();
        map2.put("scores", Arrays.asList(85, 90, 95));
        map2.put("ages", Arrays.asList(20, 21, 22));
        
        System.out.println("  Nested map: " + map2);
        
        System.out.println("\nCustom classes:");
        
        class Pair<K, V> {
            K key;
            V value;
            
            Pair(K key, V value) {
                this.key = key;
                this.value = value;
            }
            
            @Override
            public String toString() {
                return key + "=" + value;
            }
        }
        
        // Diamond inference
        Pair<String, Integer> pair1 = new Pair<>("age", 30);
        Pair<Integer, String> pair2 = new Pair<>(1, "first");
        
        System.out.println("  " + pair1);
        System.out.println("  " + pair2);
        
        System.out.println("\nReturn type inference:");
        
        class Factory {
            static <T> Box<T> createBox(T value) {
                return new Box<>(value); // Diamond infers T
            }
        }
        
        Box<String> strBox = Factory.createBox("Hello");
        Box<Integer> intBox = Factory.createBox(42);
        
        System.out.println("  String box: " + strBox.getValue());
        System.out.println("  Integer box: " + intBox.getValue());
        
        System.out.println("\nComplex example:");
        
        class Container<T> {
            List<T> items;
            
            Container() {
                this.items = new ArrayList<>(); // Diamond
            }
            
            void add(T item) {
                items.add(item);
            }
            
            List<T> getItems() {
                return items;
            }
        }
        
        Container<String> container = new Container<>(); // Diamond
        container.add("A");
        container.add("B");
        container.add("C");
        
        System.out.println("  Items: " + container.getItems());
        
        // Nested
        List<Container<Integer>> containers = new ArrayList<>();
        Container<Integer> c1 = new Container<>();
        c1.add(1);
        c1.add(2);
        containers.add(c1);
        
        System.out.println("  Nested: " + containers.get(0).getItems());
    }
}

new Box<>() - compiler infers type from variable declaration.

diamond operator `<>` in constructor - compiler infers type. Shorter than repeating type.

Exercise: Practical.java

Build a type-safe stack using generics