Your utility class needs a swap method that works with any type. You don't want a generic class - just one method. Generic methods declare their own type parameters, independent of any class generics.

Basic generic method

Declare type parameter before return type.

Basic.java
import java.util.*;

public class Basic {
    public static <T> void print(T item) {
        System.out.println("  Item: " + item);
    }
    
    public static <T> void printArray(T[] array) {
        System.out.print("  Array: [");
        for (int i = 0; i < array.length; i++) {
            if (i > 0) System.out.print(", ");
            System.out.print(array[i]);
        }
        System.out.println("]");
    }
    
    public static void main(String[] args) {
        System.out.println("Basic generic methods:\n");
        
        print(42);
        print("Hello");
        print(3.14);
        
        Integer[] ints = {1, 2, 3, 4, 5};
        String[] strs = {"a", "b", "c"};
        
        printArray(ints);
        printArray(strs);
        
        // Generic methods have type parameters: <T>
        // Type parameter comes before return type
        // Can be used in parameters and return type
        // Java infers type from arguments
        
        System.out.println("\nIdentity function:");
        
        class Utils {
            public static <T> T identity(T value) {
                return value;
            }
        }
        
        Integer num = Utils.identity(42);
        String text = Utils.identity("Hello");
        
        System.out.println("  Number: " + num);
        System.out.println("  Text: " + text);
        
        System.out.println("\nSwap elements:");
        
        class ArrayOps {
            public static <T> void swap(T[] array, int i, int j) {
                T temp = array[i];
                array[i] = array[j];
                array[j] = temp;
            }
        }
        
        Integer[] numbers = {1, 2, 3, 4, 5};
        System.out.println("  Before: " + Arrays.toString(numbers));
        ArrayOps.swap(numbers, 0, 4);
        System.out.println("  After: " + Arrays.toString(numbers));
        
        System.out.println("\nGet first:");
        
        class Getter {
            public static <T> T getFirst(T[] array) {
                return array.length > 0 ? array[0] : null;
            }
            
            public static <T> T getFirst(List<T> list) {
                return list.isEmpty() ? null : list.get(0);
            }
        }
        
        System.out.println("  First int: " + Getter.getFirst(ints));
        System.out.println("  First string: " + Getter.getFirst(strs));
        
        System.out.println("\nCreate pair:");
        
        class Pair<K, V> {
            K key;
            V value;
            
            Pair(K key, V value) {
                this.key = key;
                this.value = value;
            }
            
            public String toString() {
                return "(" + key + ", " + value + ")";
            }
        }
        
        class PairFactory {
            public static <K, V> Pair<K, V> create(K key, V value) {
                return new Pair<>(key, value);
            }
        }
        
        Pair<String, Integer> p1 = PairFactory.create("age", 30);
        Pair<Integer, String> p2 = PairFactory.create(1, "first");
        
        System.out.println("  Pair 1: " + p1);
        System.out.println("  Pair 2: " + p2);
        
        System.out.println("\nContains check:");
        
        class Checker {
            public static <T> boolean contains(T[] array, T item) {
                for (T element : array) {
                    if (element.equals(item)) {
                        return true;
                    }
                }
                return false;
            }
        }
        
        int searchNumber = ;
        System.out.println("  ints contains " + searchNumber + ": " +
                         Checker.contains(ints, searchNumber));
        System.out.println("  strs contains 'd': " + Checker.contains(strs, "d"));
    }
}
import java.util.*;

public class Basic {
    public static <T> void print(T item) {
        System.out.println("  Item: " + item);
    }
    
    public static <T> void printArray(T[] array) {
        System.out.print("  Array: [");
        for (int i = 0; i < array.length; i++) {
            if (i > 0) System.out.print(", ");
            System.out.print(array[i]);
        }
        System.out.println("]");
    }
    
    public static void main(String[] args) {
        System.out.println("Basic generic methods:\n");
        
        print(42);
        print("Hello");
        print(3.14);
        
        Integer[] ints = {1, 2, 3, 4, 5};
        String[] strs = {"a", "b", "c"};
        
        printArray(ints);
        printArray(strs);
        
        // Generic methods have type parameters: <T>
        // Type parameter comes before return type
        // Can be used in parameters and return type
        // Java infers type from arguments
        
        System.out.println("\nIdentity function:");
        
        class Utils {
            public static <T> T identity(T value) {
                return value;
            }
        }
        
        Integer num = Utils.identity(42);
        String text = Utils.identity("Hello");
        
        System.out.println("  Number: " + num);
        System.out.println("  Text: " + text);
        
        System.out.println("\nSwap elements:");
        
        class ArrayOps {
            public static <T> void swap(T[] array, int i, int j) {
                T temp = array[i];
                array[i] = array[j];
                array[j] = temp;
            }
        }
        
        Integer[] numbers = {1, 2, 3, 4, 5};
        System.out.println("  Before: " + Arrays.toString(numbers));
        ArrayOps.swap(numbers, 0, 4);
        System.out.println("  After: " + Arrays.toString(numbers));
        
        System.out.println("\nGet first:");
        
        class Getter {
            public static <T> T getFirst(T[] array) {
                return array.length > 0 ? array[0] : null;
            }
            
            public static <T> T getFirst(List<T> list) {
                return list.isEmpty() ? null : list.get(0);
            }
        }
        
        System.out.println("  First int: " + Getter.getFirst(ints));
        System.out.println("  First string: " + Getter.getFirst(strs));
        
        System.out.println("\nCreate pair:");
        
        class Pair<K, V> {
            K key;
            V value;
            
            Pair(K key, V value) {
                this.key = key;
                this.value = value;
            }
            
            public String toString() {
                return "(" + key + ", " + value + ")";
            }
        }
        
        class PairFactory {
            public static <K, V> Pair<K, V> create(K key, V value) {
                return new Pair<>(key, value);
            }
        }
        
        Pair<String, Integer> p1 = PairFactory.create("age", 30);
        Pair<Integer, String> p2 = PairFactory.create(1, "first");
        
        System.out.println("  Pair 1: " + p1);
        System.out.println("  Pair 2: " + p2);
        
        System.out.println("\nContains check:");
        
        class Checker {
            public static <T> boolean contains(T[] array, T item) {
                for (T element : array) {
                    if (element.equals(item)) {
                        return true;
                    }
                }
                return false;
            }
        }
        
        int searchNumber = ;
        System.out.println("  ints contains " + searchNumber + ": " +
                         Checker.contains(ints, searchNumber));
        System.out.println("  strs contains 'd': " + Checker.contains(strs, "d"));
    }
}
import java.util.*;

public class Basic {
    public static <T> void print(T item) {
        System.out.println("  Item: " + item);
    }
    
    public static <T> void printArray(T[] array) {
        System.out.print("  Array: [");
        for (int i = 0; i < array.length; i++) {
            if (i > 0) System.out.print(", ");
            System.out.print(array[i]);
        }
        System.out.println("]");
    }
    
    public static void main(String[] args) {
        System.out.println("Basic generic methods:\n");
        
        print(42);
        print("Hello");
        print(3.14);
        
        Integer[] ints = {1, 2, 3, 4, 5};
        String[] strs = {"a", "b", "c"};
        
        printArray(ints);
        printArray(strs);
        
        // Generic methods have type parameters: <T>
        // Type parameter comes before return type
        // Can be used in parameters and return type
        // Java infers type from arguments
        
        System.out.println("\nIdentity function:");
        
        class Utils {
            public static <T> T identity(T value) {
                return value;
            }
        }
        
        Integer num = Utils.identity(42);
        String text = Utils.identity("Hello");
        
        System.out.println("  Number: " + num);
        System.out.println("  Text: " + text);
        
        System.out.println("\nSwap elements:");
        
        class ArrayOps {
            public static <T> void swap(T[] array, int i, int j) {
                T temp = array[i];
                array[i] = array[j];
                array[j] = temp;
            }
        }
        
        Integer[] numbers = {1, 2, 3, 4, 5};
        System.out.println("  Before: " + Arrays.toString(numbers));
        ArrayOps.swap(numbers, 0, 4);
        System.out.println("  After: " + Arrays.toString(numbers));
        
        System.out.println("\nGet first:");
        
        class Getter {
            public static <T> T getFirst(T[] array) {
                return array.length > 0 ? array[0] : null;
            }
            
            public static <T> T getFirst(List<T> list) {
                return list.isEmpty() ? null : list.get(0);
            }
        }
        
        System.out.println("  First int: " + Getter.getFirst(ints));
        System.out.println("  First string: " + Getter.getFirst(strs));
        
        System.out.println("\nCreate pair:");
        
        class Pair<K, V> {
            K key;
            V value;
            
            Pair(K key, V value) {
                this.key = key;
                this.value = value;
            }
            
            public String toString() {
                return "(" + key + ", " + value + ")";
            }
        }
        
        class PairFactory {
            public static <K, V> Pair<K, V> create(K key, V value) {
                return new Pair<>(key, value);
            }
        }
        
        Pair<String, Integer> p1 = PairFactory.create("age", 30);
        Pair<Integer, String> p2 = PairFactory.create(1, "first");
        
        System.out.println("  Pair 1: " + p1);
        System.out.println("  Pair 2: " + p2);
        
        System.out.println("\nContains check:");
        
        class Checker {
            public static <T> boolean contains(T[] array, T item) {
                for (T element : array) {
                    if (element.equals(item)) {
                        return true;
                    }
                }
                return false;
            }
        }
        
        int searchNumber = ;
        System.out.println("  ints contains " + searchNumber + ": " +
                         Checker.contains(ints, searchNumber));
        System.out.println("  strs contains 'd': " + Checker.contains(strs, "d"));
    }
}

<T> T method(T arg) - T is scoped to this method only.

generic method Method with its own type parameter: `<T> void process(T item)`.

Bounded generic methods

Restrict type parameter to certain types.

Bounds.java
import java.util.*;

public class Bounds {
    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 min(T a, T b) {
        return a.compareTo(b) < 0 ? a : b;
    }
    
    public static void main(String[] args) {
        System.out.println("Bounded generic methods:\n");
        
        System.out.println("  max(5, 10): " + max(5, 10));
        System.out.println("  max('a', 'z'): " + max('a', 'z'));
        System.out.println("  min(\"apple\", \"banana\"): " + min("apple", "banana"));
        
        // <T extends Type> constrains T to be Type or subclass
        // Allows calling Type's methods on T
        // Common with Comparable, Number, Serializable
        // Can combine multiple bounds with &
        
        System.out.println("\nSum numbers:");
        
        class NumberOps {
            public static <T extends Number> double sum(T[] numbers) {
                double total = 0;
                for (T num : numbers) {
                    total += num.doubleValue();
                }
                return total;
            }
            
            public static <T extends Number> double average(T[] numbers) {
                if (numbers.length == 0) {
                    return 0;
                }
                return sum(numbers) / numbers.length;
            }
        }
        
        Integer[] ints = {1, 2, 3, 4, 5};
        Double[] doubles = {1.5, 2.5, 3.5};
        
        System.out.println("  Sum ints: " + NumberOps.sum(ints));
        System.out.println("  Sum doubles: " + NumberOps.sum(doubles));
        System.out.println("  Average: " + NumberOps.average(ints));
        
        System.out.println("\nFind maximum:");
        
        class Finder {
            public static <T extends Comparable<T>> T findMax(T[] array) {
                if (array.length == 0) {
                    return null;
                }
                
                T max = array[0];
                for (T item : array) {
                    if (item.compareTo(max) > 0) {
                        max = item;
                    }
                }
                return max;
            }
        }
        
        System.out.println("  Max int: " + Finder.findMax(ints));
        
        String[] words = {"apple", "zebra", "banana", "cherry"};
        System.out.println("  Max string: " + Finder.findMax(words));
        
        System.out.println("\nSort array:");
        
        class Sorter {
            public static <T extends Comparable<T>> void sort(T[] array) {
                for (int i = 0; i < array.length - 1; i++) {
                    for (int j = i + 1; j < array.length; j++) {
                        if (array[i].compareTo(array[j]) > 0) {
                            T temp = array[i];
                            array[i] = array[j];
                            array[j] = temp;
                        }
                    }
                }
            }
        }
        
        Integer[] nums = {5, 2, 8, 1, 9};
        System.out.println("  Before: " + Arrays.toString(nums));
        Sorter.sort(nums);
        System.out.println("  After: " + Arrays.toString(nums));
        
        System.out.println("\nClamp value:");
        
        class RangeOps {
            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("  clamp(5, 0, 10): " + RangeOps.clamp(5, 0, 10));
        System.out.println("  clamp(15, 0, 10): " + RangeOps.clamp(15, 0, 10));
        System.out.println("  clamp(-5, 0, 10): " + RangeOps.clamp(-5, 0, 10));
        
        System.out.println("\nCount greater:");
        
        class Counter {
            public static <T extends Comparable<T>> int countGreater(
                    T[] array, T threshold) {
                int count = 0;
                for (T item : array) {
                    if (item.compareTo(threshold) > 0) {
                        count++;
                    }
                }
                return count;
            }
        }
        
        int countThreshold = ;
        System.out.println("  Count > " + countThreshold + ": " +
                         Counter.countGreater(ints, countThreshold));
        System.out.println("  Count > 'c': " + Counter.countGreater(words, "c"));
    }
}
import java.util.*;

public class Bounds {
    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 min(T a, T b) {
        return a.compareTo(b) < 0 ? a : b;
    }
    
    public static void main(String[] args) {
        System.out.println("Bounded generic methods:\n");
        
        System.out.println("  max(5, 10): " + max(5, 10));
        System.out.println("  max('a', 'z'): " + max('a', 'z'));
        System.out.println("  min(\"apple\", \"banana\"): " + min("apple", "banana"));
        
        // <T extends Type> constrains T to be Type or subclass
        // Allows calling Type's methods on T
        // Common with Comparable, Number, Serializable
        // Can combine multiple bounds with &
        
        System.out.println("\nSum numbers:");
        
        class NumberOps {
            public static <T extends Number> double sum(T[] numbers) {
                double total = 0;
                for (T num : numbers) {
                    total += num.doubleValue();
                }
                return total;
            }
            
            public static <T extends Number> double average(T[] numbers) {
                if (numbers.length == 0) {
                    return 0;
                }
                return sum(numbers) / numbers.length;
            }
        }
        
        Integer[] ints = {1, 2, 3, 4, 5};
        Double[] doubles = {1.5, 2.5, 3.5};
        
        System.out.println("  Sum ints: " + NumberOps.sum(ints));
        System.out.println("  Sum doubles: " + NumberOps.sum(doubles));
        System.out.println("  Average: " + NumberOps.average(ints));
        
        System.out.println("\nFind maximum:");
        
        class Finder {
            public static <T extends Comparable<T>> T findMax(T[] array) {
                if (array.length == 0) {
                    return null;
                }
                
                T max = array[0];
                for (T item : array) {
                    if (item.compareTo(max) > 0) {
                        max = item;
                    }
                }
                return max;
            }
        }
        
        System.out.println("  Max int: " + Finder.findMax(ints));
        
        String[] words = {"apple", "zebra", "banana", "cherry"};
        System.out.println("  Max string: " + Finder.findMax(words));
        
        System.out.println("\nSort array:");
        
        class Sorter {
            public static <T extends Comparable<T>> void sort(T[] array) {
                for (int i = 0; i < array.length - 1; i++) {
                    for (int j = i + 1; j < array.length; j++) {
                        if (array[i].compareTo(array[j]) > 0) {
                            T temp = array[i];
                            array[i] = array[j];
                            array[j] = temp;
                        }
                    }
                }
            }
        }
        
        Integer[] nums = {5, 2, 8, 1, 9};
        System.out.println("  Before: " + Arrays.toString(nums));
        Sorter.sort(nums);
        System.out.println("  After: " + Arrays.toString(nums));
        
        System.out.println("\nClamp value:");
        
        class RangeOps {
            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("  clamp(5, 0, 10): " + RangeOps.clamp(5, 0, 10));
        System.out.println("  clamp(15, 0, 10): " + RangeOps.clamp(15, 0, 10));
        System.out.println("  clamp(-5, 0, 10): " + RangeOps.clamp(-5, 0, 10));
        
        System.out.println("\nCount greater:");
        
        class Counter {
            public static <T extends Comparable<T>> int countGreater(
                    T[] array, T threshold) {
                int count = 0;
                for (T item : array) {
                    if (item.compareTo(threshold) > 0) {
                        count++;
                    }
                }
                return count;
            }
        }
        
        int countThreshold = ;
        System.out.println("  Count > " + countThreshold + ": " +
                         Counter.countGreater(ints, countThreshold));
        System.out.println("  Count > 'c': " + Counter.countGreater(words, "c"));
    }
}
import java.util.*;

public class Bounds {
    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 min(T a, T b) {
        return a.compareTo(b) < 0 ? a : b;
    }
    
    public static void main(String[] args) {
        System.out.println("Bounded generic methods:\n");
        
        System.out.println("  max(5, 10): " + max(5, 10));
        System.out.println("  max('a', 'z'): " + max('a', 'z'));
        System.out.println("  min(\"apple\", \"banana\"): " + min("apple", "banana"));
        
        // <T extends Type> constrains T to be Type or subclass
        // Allows calling Type's methods on T
        // Common with Comparable, Number, Serializable
        // Can combine multiple bounds with &
        
        System.out.println("\nSum numbers:");
        
        class NumberOps {
            public static <T extends Number> double sum(T[] numbers) {
                double total = 0;
                for (T num : numbers) {
                    total += num.doubleValue();
                }
                return total;
            }
            
            public static <T extends Number> double average(T[] numbers) {
                if (numbers.length == 0) {
                    return 0;
                }
                return sum(numbers) / numbers.length;
            }
        }
        
        Integer[] ints = {1, 2, 3, 4, 5};
        Double[] doubles = {1.5, 2.5, 3.5};
        
        System.out.println("  Sum ints: " + NumberOps.sum(ints));
        System.out.println("  Sum doubles: " + NumberOps.sum(doubles));
        System.out.println("  Average: " + NumberOps.average(ints));
        
        System.out.println("\nFind maximum:");
        
        class Finder {
            public static <T extends Comparable<T>> T findMax(T[] array) {
                if (array.length == 0) {
                    return null;
                }
                
                T max = array[0];
                for (T item : array) {
                    if (item.compareTo(max) > 0) {
                        max = item;
                    }
                }
                return max;
            }
        }
        
        System.out.println("  Max int: " + Finder.findMax(ints));
        
        String[] words = {"apple", "zebra", "banana", "cherry"};
        System.out.println("  Max string: " + Finder.findMax(words));
        
        System.out.println("\nSort array:");
        
        class Sorter {
            public static <T extends Comparable<T>> void sort(T[] array) {
                for (int i = 0; i < array.length - 1; i++) {
                    for (int j = i + 1; j < array.length; j++) {
                        if (array[i].compareTo(array[j]) > 0) {
                            T temp = array[i];
                            array[i] = array[j];
                            array[j] = temp;
                        }
                    }
                }
            }
        }
        
        Integer[] nums = {5, 2, 8, 1, 9};
        System.out.println("  Before: " + Arrays.toString(nums));
        Sorter.sort(nums);
        System.out.println("  After: " + Arrays.toString(nums));
        
        System.out.println("\nClamp value:");
        
        class RangeOps {
            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("  clamp(5, 0, 10): " + RangeOps.clamp(5, 0, 10));
        System.out.println("  clamp(15, 0, 10): " + RangeOps.clamp(15, 0, 10));
        System.out.println("  clamp(-5, 0, 10): " + RangeOps.clamp(-5, 0, 10));
        
        System.out.println("\nCount greater:");
        
        class Counter {
            public static <T extends Comparable<T>> int countGreater(
                    T[] array, T threshold) {
                int count = 0;
                for (T item : array) {
                    if (item.compareTo(threshold) > 0) {
                        count++;
                    }
                }
                return count;
            }
        }
        
        int countThreshold = ;
        System.out.println("  Count > " + countThreshold + ": " +
                         Counter.countGreater(ints, countThreshold));
        System.out.println("  Count > 'c': " + Counter.countGreater(words, "c"));
    }
}

<T extends Comparable<T>> - T must be comparable to itself.

Static generic methods

Static methods can be generic even in non-generic classes.

Static.java
import java.util.*;

public class Static {
    static class CollectionUtils {
        public static <T> List<T> newArrayList() {
            return new ArrayList<>();
        }
        
        public static <K, V> Map<K, V> newHashMap() {
            return new LinkedHashMap<>();
        }
        
        public static <T> Set<T> newHashSet() {
            return new LinkedHashSet<>();
        }
    }
    
    public static void main(String[] args) {
        System.out.println("Static generic methods:\n");
        
        List<String> list = CollectionUtils.newArrayList();
        list.add("a");
        list.add("b");
        
        Map<String, Integer> map = CollectionUtils.newHashMap();
        map.put("one", 1);
        map.put("two", 2);
        
        System.out.println("  List: " + list);
        System.out.println("  Map: " + map);
        
        // Static methods can be generic
        // Don't need generic class
        // Type parameters independent of class
        // Common in utility classes
        
        System.out.println("\nCreate from varargs:");
        
        class ListFactory {
            @SafeVarargs
            public static <T> List<T> of(T... items) {
                List<T> list = new ArrayList<>();
                for (T item : items) {
                    list.add(item);
                }
                return list;
            }
        }
        
        List<Integer> nums = ListFactory.of(1, 2, 3, 4, 5);
        List<String> words = ListFactory.of("a", "b", "c");
        
        System.out.println("  Numbers: " + nums);
        System.out.println("  Words: " + words);
        
        System.out.println("\nCopy collections:");
        
        class Copier {
            public static <T> void copy(
                    Collection<T> dest,
                    Collection<T> src) {
                dest.clear();
                dest.addAll(src);
            }
            
            public static <T> List<T> copyToList(Collection<T> src) {
                return new ArrayList<>(src);
            }
        }
        
        Set<String> source = new LinkedHashSet<>(Arrays.asList("x", "y", "z"));
        List<String> dest = new ArrayList<>();
        
        Copier.copy(dest, source);
        System.out.println("  Copied: " + dest);
        
        System.out.println("\nReverse:");
        
        class Reverser {
            public static <T> List<T> reverse(List<T> list) {
                List<T> result = new ArrayList<>();
                for (int i = list.size() - 1; i >= 0; i--) {
                    result.add(list.get(i));
                }
                return result;
            }
        }
        
        List<Integer> original = Arrays.asList(1, 2, 3, 4, 5);
        List<Integer> reversed = Reverser.reverse(original);
        
        System.out.println("  Original: " + original);
        System.out.println("  Reversed: " + reversed);
        
        System.out.println("\nFilter:");
        
        interface Predicate<T> {
            boolean test(T item);
        }
        
        class Filter {
            public static <T> List<T> filter(
                    Collection<T> collection,
                    Predicate<T> predicate) {
                List<T> result = new ArrayList<>();
                for (T item : collection) {
                    if (predicate.test(item)) {
                        result.add(item);
                    }
                }
                return result;
            }
        }
        
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        int divisor = 2;
        List<Integer> evens = Filter.filter(numbers, n -> n % divisor == 0);
        
        System.out.println("  Divisible by " + divisor + ": " + evens);
        
        System.out.println("\nTransform:");
        
        interface Function<T, R> {
            R apply(T item);
        }
        
        class Transformer {
            public static <T, R> List<R> map(
                    Collection<T> collection,
                    Function<T, R> mapper) {
                List<R> result = new ArrayList<>();
                for (T item : collection) {
                    result.add(mapper.apply(item));
                }
                return result;
            }
        }
        
        List<String> strings = Arrays.asList("1", "2", "3");
        List<Integer> parsed = Transformer.map(strings, Integer::parseInt);
        
        System.out.println("  Strings: " + strings);
        System.out.println("  Parsed: " + parsed);
    }
}

public static <T> T identity(T val) - no instance needed.

Type inference

Compiler infers type from arguments.

Inference.java
import java.util.*;

public class Inference {
    public static <T> T identity(T value) {
        return value;
    }
    
    public static <K, V> Map<K, V> createMap(K key, V value) {
        Map<K, V> map = new HashMap<>();
        map.put(key, value);
        return map;
    }
    
    public static void main(String[] args) {
        System.out.println("Type inference:\n");
        
        // Explicit type argument
        String s1 = Inference.<String>identity("hello");
        
        // Inferred type argument (preferred)
        String s2 = identity("hello");
        
        System.out.println("  Explicit: " + s1);
        System.out.println("  Inferred: " + s2);
        
        // Java can infer type parameters from arguments
        // Explicit: ClassName.<Type>method(arg)
        // Inferred: ClassName.method(arg) - preferred
        // Inference works with return type context
        
        System.out.println("\nInferred from arguments:");
        
        class Pair<K, V> {
            K key;
            V value;
            
            Pair(K key, V value) {
                this.key = key;
                this.value = value;
            }
            
            public String toString() {
                return "(" + key + ", " + value + ")";
            }
        }
        
        class PairFactory {
            public static <K, V> Pair<K, V> make(K key, V value) {
                return new Pair<>(key, value);
            }
        }
        
        // Type inferred from arguments
        Pair<String, Integer> p1 = PairFactory.make("age", 30);
        Pair<Integer, String> p2 = PairFactory.make(1, "first");
        
        System.out.println("  Pair 1: " + p1);
        System.out.println("  Pair 2: " + p2);
        
        System.out.println("\nDiamond operator:");
        
        // Before Java 7 - redundant
        Map<String, List<Integer>> old = 
            new HashMap<String, List<Integer>>();
        
        // Java 7+ - inferred
        Map<String, List<Integer>> modern = new HashMap<>();
        
        modern.put("nums", Arrays.asList(1, 2, 3));
        System.out.println("  Map: " + modern);
        
        System.out.println("\nInferred return type:");
        
        class Builder {
            public static <T> List<T> build(T... items) {
                List<T> list = new ArrayList<>();
                for (T item : items) {
                    list.add(item);
                }
                return list;
            }
        }
        
        List<String> words = Builder.build("a", "b", "c");
        List<Integer> nums = Builder.build(1, 2, 3);
        
        System.out.println("  Words: " + words);
        System.out.println("  Numbers: " + nums);
        
        System.out.println("\nComplex inference:");
        
        class Wrapper<T> {
            T value;
            
            Wrapper(T value) {
                this.value = value;
            }
            
            public String toString() {
                return "Wrapper(" + value + ")";
            }
        }
        
        class WrapperFactory {
            public static <T> Wrapper<T> wrap(T value) {
                return new Wrapper<>(value);
            }
            
            public static <T> List<Wrapper<T>> wrapAll(List<T> values) {
                List<Wrapper<T>> result = new ArrayList<>();
                for (T value : values) {
                    result.add(wrap(value));
                }
                return result;
            }
        }
        
        List<Integer> numbers = Arrays.asList(1, 2, 3);
        List<Wrapper<Integer>> wrapped = WrapperFactory.wrapAll(numbers);
        
        System.out.println("  Wrapped: " + wrapped);
        
        System.out.println("\nTarget type:");
        
        class Utils {
            public static <T> List<T> emptyList() {
                return new ArrayList<>();
            }
        }
        
        // Type inferred from variable declaration
        List<String> empty1 = Utils.emptyList();
        List<Integer> empty2 = Utils.emptyList();
        
        System.out.println("  Empty string list: " + empty1);
        System.out.println("  Empty int list: " + empty2);
        
        System.out.println("\nMethod chaining:");
        
        class FluentBuilder<T> {
            private List<T> items = new ArrayList<>();
            
            public static <T> FluentBuilder<T> create() {
                return new FluentBuilder<>();
            }
            
            public FluentBuilder<T> add(T item) {
                items.add(item);
                return this;
            }
            
            public List<T> build() {
                return items;
            }
        }
        
        List<String> result = FluentBuilder.<String>create()  // Explicit
            .add("a")
            .add("b")
            .add("c")
            .build();
        
        List<Integer> result2 = FluentBuilder.<Integer>create()  // Also explicit - chains need hint
            .add(1)
            .add(2)
            .add(3)
            .build();
        
        System.out.println("  Result 1: " + result);
        System.out.println("  Result 2: " + result2);
    }
}

Box.create("text") - compiler knows T is String. No explicit <String>.

type inference Compiler deduces type arguments from context. Explicit syntax rarely needed.

Generic constructors

Constructors can have their own type parameters.

Constructors.java
import java.util.*;

public class Constructors {
    static class Box<T> {
        private T content;
        
        // Generic constructor - U independent of T
        <U> Box(U initialValue, Converter<U, T> converter) {
            this.content = converter.convert(initialValue);
        }
        
        public T get() {
            return content;
        }
    }
    
    interface Converter<F, T> {
        T convert(F from);
    }
    
    public static void main(String[] args) {
        System.out.println("Generic constructors:\n");
        
        // Create Box<Integer> from String
        String rawBoxValue = ;
        Box<Integer> box = new Box<>(rawBoxValue, Integer::parseInt);
        System.out.println("  Box content: " + box.get());
        
        // Constructors can have their own type parameters
        // Type parameters independent of class type parameters
        // Useful for type conversion during construction
        // Generic constructors enable flexible initialization
        
        System.out.println("\nCollection initialization:");
        
        class Container<T> {
            private List<T> items;
            
            // Non-generic constructor
            Container() {
                this.items = new ArrayList<>();
            }
            
            // Generic constructor
            <U> Container(Collection<U> source, Converter<U, T> converter) {
                this.items = new ArrayList<>();
                for (U item : source) {
                    items.add(converter.convert(item));
                }
            }
            
            public List<T> getItems() {
                return items;
            }
        }
        
        List<String> strings = Arrays.asList("1", "2", "3", "4", "5");
        Container<Integer> numbers = new Container<>(strings, Integer::parseInt);
        
        System.out.println("  Numbers: " + numbers.getItems());
        
        System.out.println("\nBuilder pattern:");
        
        class Person {
            private String name;
            private int age;
            
            private Person(String name, int age) {
                this.name = name;
                this.age = age;
            }
            
            public String toString() {
                return name + " (" + age + ")";
            }
        }
        
        interface Factory<T> {
            T create(Map<String, Object> props);
        }

        class Builder<T> {
            private Map<String, Object> properties = new LinkedHashMap<>();

            <U> Builder<T> set(String key, U value) {
                properties.put(key, value);
                return this;
            }

            T build(Factory<T> factory) {
                return factory.create(properties);
            }
        }
        
        Person person = new Builder<Person>()
            .set("name", "Alice")
            .set("age", 30)
            .build(props -> new Person(
                (String) props.get("name"),
                (Integer) props.get("age")
            ));
        
        System.out.println("  Person: " + person);
        
        System.out.println("\nCached initialization:");
        
        interface KeyConverter<K> {
            String convert(K key);
        }

        class Cache<T> {
            private Map<String, T> cache = new LinkedHashMap<>();

            <K> Cache(Map<K, T> initial, KeyConverter<K> converter) {
                for (Map.Entry<K, T> entry : initial.entrySet()) {
                    String key = converter.convert(entry.getKey());
                    cache.put(key, entry.getValue());
                }
            }

            public T get(String key) {
                return cache.get(key);
            }

            public Set<String> keys() {
                return cache.keySet();
            }
        }
        
        Map<Integer, String> data = new LinkedHashMap<>();
        data.put(1, "one");
        data.put(2, "two");
        data.put(3, "three");
        
        Cache<String> cache = new Cache<>(data, Object::toString);
        
        System.out.println("  Keys: " + cache.keys());
        System.out.println("  Value for '1': " + cache.get("1"));
        
        System.out.println("\nMulti-source constructor:");
        
        class Aggregator<T> {
            private List<T> all = new ArrayList<>();
            
            @SafeVarargs
            <U> Aggregator(Converter<U, T> converter, Collection<U>... sources) {
                for (Collection<U> source : sources) {
                    for (U item : source) {
                        all.add(converter.convert(item));
                    }
                }
            }
            
            public List<T> getAll() {
                return all;
            }
        }
        
        List<String> source1 = Arrays.asList("1", "2", "3");
        List<String> source2 = Arrays.asList("4", "5", "6");
        
        Aggregator<Integer> agg = new Aggregator<>(
            Integer::parseInt, 
            source1, 
            source2
        );
        
        System.out.println("  Aggregated: " + agg.getAll());
        
        System.out.println("\nWrapper constructor:");
        
        interface Parser<F, T> {
            T parse(F from);
        }

        class Wrapper<T> {
            private T value;

            Wrapper(T value) {
                this.value = value;
            }

            <U> Wrapper(U rawValue, Parser<U, T> parser) {
                this.value = parser.parse(rawValue);
            }

            public T getValue() {
                return value;
            }
        }
        
        Wrapper<Integer> w1 = new Wrapper<>(42);
        Wrapper<Integer> w2 = new Wrapper<>("123", Integer::parseInt);
        
        System.out.println("  Wrapper 1: " + w1.getValue());
        System.out.println("  Wrapper 2: " + w2.getValue());
    }
}
import java.util.*;

public class Constructors {
    static class Box<T> {
        private T content;
        
        // Generic constructor - U independent of T
        <U> Box(U initialValue, Converter<U, T> converter) {
            this.content = converter.convert(initialValue);
        }
        
        public T get() {
            return content;
        }
    }
    
    interface Converter<F, T> {
        T convert(F from);
    }
    
    public static void main(String[] args) {
        System.out.println("Generic constructors:\n");
        
        // Create Box<Integer> from String
        String rawBoxValue = ;
        Box<Integer> box = new Box<>(rawBoxValue, Integer::parseInt);
        System.out.println("  Box content: " + box.get());
        
        // Constructors can have their own type parameters
        // Type parameters independent of class type parameters
        // Useful for type conversion during construction
        // Generic constructors enable flexible initialization
        
        System.out.println("\nCollection initialization:");
        
        class Container<T> {
            private List<T> items;
            
            // Non-generic constructor
            Container() {
                this.items = new ArrayList<>();
            }
            
            // Generic constructor
            <U> Container(Collection<U> source, Converter<U, T> converter) {
                this.items = new ArrayList<>();
                for (U item : source) {
                    items.add(converter.convert(item));
                }
            }
            
            public List<T> getItems() {
                return items;
            }
        }
        
        List<String> strings = Arrays.asList("1", "2", "3", "4", "5");
        Container<Integer> numbers = new Container<>(strings, Integer::parseInt);
        
        System.out.println("  Numbers: " + numbers.getItems());
        
        System.out.println("\nBuilder pattern:");
        
        class Person {
            private String name;
            private int age;
            
            private Person(String name, int age) {
                this.name = name;
                this.age = age;
            }
            
            public String toString() {
                return name + " (" + age + ")";
            }
        }
        
        interface Factory<T> {
            T create(Map<String, Object> props);
        }

        class Builder<T> {
            private Map<String, Object> properties = new LinkedHashMap<>();

            <U> Builder<T> set(String key, U value) {
                properties.put(key, value);
                return this;
            }

            T build(Factory<T> factory) {
                return factory.create(properties);
            }
        }
        
        Person person = new Builder<Person>()
            .set("name", "Alice")
            .set("age", 30)
            .build(props -> new Person(
                (String) props.get("name"),
                (Integer) props.get("age")
            ));
        
        System.out.println("  Person: " + person);
        
        System.out.println("\nCached initialization:");
        
        interface KeyConverter<K> {
            String convert(K key);
        }

        class Cache<T> {
            private Map<String, T> cache = new LinkedHashMap<>();

            <K> Cache(Map<K, T> initial, KeyConverter<K> converter) {
                for (Map.Entry<K, T> entry : initial.entrySet()) {
                    String key = converter.convert(entry.getKey());
                    cache.put(key, entry.getValue());
                }
            }

            public T get(String key) {
                return cache.get(key);
            }

            public Set<String> keys() {
                return cache.keySet();
            }
        }
        
        Map<Integer, String> data = new LinkedHashMap<>();
        data.put(1, "one");
        data.put(2, "two");
        data.put(3, "three");
        
        Cache<String> cache = new Cache<>(data, Object::toString);
        
        System.out.println("  Keys: " + cache.keys());
        System.out.println("  Value for '1': " + cache.get("1"));
        
        System.out.println("\nMulti-source constructor:");
        
        class Aggregator<T> {
            private List<T> all = new ArrayList<>();
            
            @SafeVarargs
            <U> Aggregator(Converter<U, T> converter, Collection<U>... sources) {
                for (Collection<U> source : sources) {
                    for (U item : source) {
                        all.add(converter.convert(item));
                    }
                }
            }
            
            public List<T> getAll() {
                return all;
            }
        }
        
        List<String> source1 = Arrays.asList("1", "2", "3");
        List<String> source2 = Arrays.asList("4", "5", "6");
        
        Aggregator<Integer> agg = new Aggregator<>(
            Integer::parseInt, 
            source1, 
            source2
        );
        
        System.out.println("  Aggregated: " + agg.getAll());
        
        System.out.println("\nWrapper constructor:");
        
        interface Parser<F, T> {
            T parse(F from);
        }

        class Wrapper<T> {
            private T value;

            Wrapper(T value) {
                this.value = value;
            }

            <U> Wrapper(U rawValue, Parser<U, T> parser) {
                this.value = parser.parse(rawValue);
            }

            public T getValue() {
                return value;
            }
        }
        
        Wrapper<Integer> w1 = new Wrapper<>(42);
        Wrapper<Integer> w2 = new Wrapper<>("123", Integer::parseInt);
        
        System.out.println("  Wrapper 1: " + w1.getValue());
        System.out.println("  Wrapper 2: " + w2.getValue());
    }
}
import java.util.*;

public class Constructors {
    static class Box<T> {
        private T content;
        
        // Generic constructor - U independent of T
        <U> Box(U initialValue, Converter<U, T> converter) {
            this.content = converter.convert(initialValue);
        }
        
        public T get() {
            return content;
        }
    }
    
    interface Converter<F, T> {
        T convert(F from);
    }
    
    public static void main(String[] args) {
        System.out.println("Generic constructors:\n");
        
        // Create Box<Integer> from String
        String rawBoxValue = ;
        Box<Integer> box = new Box<>(rawBoxValue, Integer::parseInt);
        System.out.println("  Box content: " + box.get());
        
        // Constructors can have their own type parameters
        // Type parameters independent of class type parameters
        // Useful for type conversion during construction
        // Generic constructors enable flexible initialization
        
        System.out.println("\nCollection initialization:");
        
        class Container<T> {
            private List<T> items;
            
            // Non-generic constructor
            Container() {
                this.items = new ArrayList<>();
            }
            
            // Generic constructor
            <U> Container(Collection<U> source, Converter<U, T> converter) {
                this.items = new ArrayList<>();
                for (U item : source) {
                    items.add(converter.convert(item));
                }
            }
            
            public List<T> getItems() {
                return items;
            }
        }
        
        List<String> strings = Arrays.asList("1", "2", "3", "4", "5");
        Container<Integer> numbers = new Container<>(strings, Integer::parseInt);
        
        System.out.println("  Numbers: " + numbers.getItems());
        
        System.out.println("\nBuilder pattern:");
        
        class Person {
            private String name;
            private int age;
            
            private Person(String name, int age) {
                this.name = name;
                this.age = age;
            }
            
            public String toString() {
                return name + " (" + age + ")";
            }
        }
        
        interface Factory<T> {
            T create(Map<String, Object> props);
        }

        class Builder<T> {
            private Map<String, Object> properties = new LinkedHashMap<>();

            <U> Builder<T> set(String key, U value) {
                properties.put(key, value);
                return this;
            }

            T build(Factory<T> factory) {
                return factory.create(properties);
            }
        }
        
        Person person = new Builder<Person>()
            .set("name", "Alice")
            .set("age", 30)
            .build(props -> new Person(
                (String) props.get("name"),
                (Integer) props.get("age")
            ));
        
        System.out.println("  Person: " + person);
        
        System.out.println("\nCached initialization:");
        
        interface KeyConverter<K> {
            String convert(K key);
        }

        class Cache<T> {
            private Map<String, T> cache = new LinkedHashMap<>();

            <K> Cache(Map<K, T> initial, KeyConverter<K> converter) {
                for (Map.Entry<K, T> entry : initial.entrySet()) {
                    String key = converter.convert(entry.getKey());
                    cache.put(key, entry.getValue());
                }
            }

            public T get(String key) {
                return cache.get(key);
            }

            public Set<String> keys() {
                return cache.keySet();
            }
        }
        
        Map<Integer, String> data = new LinkedHashMap<>();
        data.put(1, "one");
        data.put(2, "two");
        data.put(3, "three");
        
        Cache<String> cache = new Cache<>(data, Object::toString);
        
        System.out.println("  Keys: " + cache.keys());
        System.out.println("  Value for '1': " + cache.get("1"));
        
        System.out.println("\nMulti-source constructor:");
        
        class Aggregator<T> {
            private List<T> all = new ArrayList<>();
            
            @SafeVarargs
            <U> Aggregator(Converter<U, T> converter, Collection<U>... sources) {
                for (Collection<U> source : sources) {
                    for (U item : source) {
                        all.add(converter.convert(item));
                    }
                }
            }
            
            public List<T> getAll() {
                return all;
            }
        }
        
        List<String> source1 = Arrays.asList("1", "2", "3");
        List<String> source2 = Arrays.asList("4", "5", "6");
        
        Aggregator<Integer> agg = new Aggregator<>(
            Integer::parseInt, 
            source1, 
            source2
        );
        
        System.out.println("  Aggregated: " + agg.getAll());
        
        System.out.println("\nWrapper constructor:");
        
        interface Parser<F, T> {
            T parse(F from);
        }

        class Wrapper<T> {
            private T value;

            Wrapper(T value) {
                this.value = value;
            }

            <U> Wrapper(U rawValue, Parser<U, T> parser) {
                this.value = parser.parse(rawValue);
            }

            public T getValue() {
                return value;
            }
        }
        
        Wrapper<Integer> w1 = new Wrapper<>(42);
        Wrapper<Integer> w2 = new Wrapper<>("123", Integer::parseInt);
        
        System.out.println("  Wrapper 1: " + w1.getValue());
        System.out.println("  Wrapper 2: " + w2.getValue());
    }
}

<U> MyClass(U value) - U separate from class type parameter.

Exercise: Practical.java

Build utility methods using generic methods