Table of Contents
- Introduction to Generic Methods
- Syntax and Structure
- Type Inference
- Bounded Type Parameters
- Generic Methods vs Generic Classes
- Common Use Cases
- Wildcards in Generic Methods
- Method Overloading with Generics
- Best Practices
- Complete Examples
Introduction to Generic Methods
Generic methods are methods that introduce their own type parameters. This is similar to declaring a generic type, but the type parameter's scope is limited to the method where it is declared.
Key Characteristics:
- Can be defined in both generic and non-generic classes
- Type parameter scope is limited to the method
- Support type inference (compiler can often determine the type)
- Can have multiple type parameters
- Can use bounded type parameters
Syntax and Structure
Basic Syntax:
public <T> returnType methodName(T parameter) { // method body } Simple Examples:
public class GenericMethodExamples { // Basic generic method public static <T> void printArray(T[] array) { for (T element : array) { System.out.print(element + " "); } System.out.println(); } // Generic method returning generic type public static <T> T getFirstElement(T[] array) { if (array == null || array.length == 0) { return null; } return array[0]; } // Generic method with multiple type parameters public static <T, U> void printPair(T first, U second) { System.out.println("First: " + first + ", Second: " + second); } // Generic method in non-generic class public <E> int countOccurrences(E[] array, E target) { int count = 0; for (E element : array) { if (element.equals(target)) { count++; } } return count; } public static void main(String[] args) { // Using generic methods with different types Integer[] intArray = {1, 2, 3, 4, 5}; String[] strArray = {"A", "B", "C", "D"}; Double[] doubleArray = {1.1, 2.2, 3.3}; System.out.print("Integer array: "); printArray(intArray); System.out.print("String array: "); printArray(strArray); System.out.print("Double array: "); printArray(doubleArray); // Using multiple type parameters printPair("Hello", 42); printPair(3.14, "Pi"); // Getting first element System.out.println("First string: " + getFirstElement(strArray)); System.out.println("First integer: " + getFirstElement(intArray)); // Instance method usage GenericMethodExamples instance = new GenericMethodExamples(); String[] words = {"apple", "banana", "apple", "cherry", "apple"}; System.out.println("Occurrences of 'apple': " + instance.countOccurrences(words, "apple")); } } Output:
Integer array: 1 2 3 4 5 String array: A B C D Double array: 1.1 2.2 3.3 First: Hello, Second: 42 First: 3.14, Second: Pi First string: A First integer: 1 Occurrences of 'apple': 3
Type Inference
The Java compiler can infer the type parameter based on the method arguments and context.
Type Inference Examples:
import java.util.*; public class TypeInferenceExamples { // Generic method with type inference public static <T> T identity(T input) { return input; } // Method that uses type inference from parameters public static <T> void addToList(List<T> list, T element) { list.add(element); } // Method with return type inference public static <T> T createDefault(Class<T> clazz) throws Exception { return clazz.getDeclaredConstructor().newInstance(); } // Complex type inference public static <T> List<T> filter(List<T> list, Predicate<T> predicate) { List<T> result = new ArrayList<>(); for (T item : list) { if (predicate.test(item)) { result.add(item); } } return result; } public static void main(String[] args) throws Exception { // Type inference in action // 1. Explicit type specification (rarely needed) String explicit = TypeInferenceExamples.<String>identity("Hello"); System.out.println("Explicit: " + explicit); // 2. Type inference (common usage) String inferred = identity("World"); System.out.println("Inferred: " + inferred); // 3. Type inference with collections List<String> stringList = new ArrayList<>(); addToList(stringList, "Apple"); // T inferred as String addToList(stringList, "Banana"); // T inferred as String System.out.println("String list: " + stringList); List<Integer> intList = new ArrayList<>(); addToList(intList, 1); // T inferred as Integer addToList(intList, 2); // T inferred as Integer System.out.println("Integer list: " + intList); // 4. Type inference with return type String defaultString = createDefault(String.class); System.out.println("Default string: '" + defaultString + "'"); // 5. Complex type inference with predicates List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); List<Integer> evenNumbers = filter(numbers, n -> n % 2 == 0); System.out.println("Even numbers: " + evenNumbers); List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David"); List<String> longNames = filter(names, name -> name.length() > 4); System.out.println("Long names: " + longNames); } } // Functional interface for filtering interface Predicate<T> { boolean test(T t); } Output:
Explicit: Hello Inferred: World String list: [Apple, Banana] Integer list: [1, 2] Default string: '' Even numbers: [2, 4, 6, 8, 10] Long names: [Alice, Charlie, David]
Bounded Type Parameters
Upper Bounded Type Parameters:
import java.util.*; public class BoundedGenericMethods { // Upper bounded type parameter - only accepts Number and subclasses public static <T extends Number> double sum(List<T> numbers) { double total = 0.0; for (T number : numbers) { total += number.doubleValue(); } return total; } // Upper bounded with interface public static <T extends Comparable<T>> T findMax(T[] array) { if (array == null || array.length == 0) { return null; } T max = array[0]; for (T element : array) { if (element.compareTo(max) > 0) { max = element; } } return max; } // Multiple bounds public static <T extends Number & Comparable<T>> T findMaxNumber(T[] array) { if (array == null || array.length == 0) { return null; } T max = array[0]; for (T element : array) { if (element.compareTo(max) > 0) { max = element; } } return max; } // Bounded type with custom interface public static <T extends Drawable> void drawAll(List<T> drawables) { for (T drawable : drawables) { drawable.draw(); } } public static void main(String[] args) { // Test with Number types List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5); List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3); System.out.println("Sum of integers: " + sum(integers)); System.out.println("Sum of doubles: " + sum(doubles)); // Test with Comparable types Integer[] intArray = {5, 2, 8, 1, 9}; String[] strArray = {"apple", "zebra", "banana", "cherry"}; System.out.println("Max integer: " + findMax(intArray)); System.out.println("Max string: " + findMax(strArray)); // Test with multiple bounds Integer[] numbers = {10, 5, 20, 15, 25}; System.out.println("Max number: " + findMaxNumber(numbers)); // Test with custom interface List<Circle> circles = Arrays.asList(new Circle(), new Circle()); List<Rectangle> rectangles = Arrays.asList(new Rectangle(), new Rectangle()); System.out.println("Drawing circles:"); drawAll(circles); System.out.println("Drawing rectangles:"); drawAll(rectangles); } } // Custom interface and implementations interface Drawable { void draw(); } class Circle implements Drawable { @Override public void draw() { System.out.println("Drawing a circle"); } } class Rectangle implements Drawable { @Override public void draw() { System.out.println("Drawing a rectangle"); } } Output:
Sum of integers: 15.0 Sum of doubles: 6.6 Max integer: 9 Max string: zebra Max number: 25 Drawing circles: Drawing a circle Drawing a circle Drawing rectangles: Drawing a rectangle Drawing a rectangle
Generic Methods vs Generic Classes
Comparison:
| Aspect | Generic Methods | Generic Classes |
|---|---|---|
| Scope | Method level | Class level |
| Type Parameters | Declared in method | Declared in class |
| Usage | Can be in any class | Only in generic classes |
| Flexibility | Different types per call | Same type for all instances |
| Inference | Compiler can infer types | Types must be specified |
Examples Showing Difference:
// Generic class class GenericClass<T> { private T value; public GenericClass(T value) { this.value = value; } public T getValue() { return value; } // Generic method in generic class public <U> void printBoth(U otherValue) { System.out.println("Class type: " + value); System.out.println("Method type: " + otherValue); } // Method using class type parameter public void setValue(T newValue) { this.value = newValue; } } public class MethodVsClass { // Generic method in non-generic class public static <T> T processItem(T item) { return item; } public static void main(String[] args) { // Generic class usage - type is fixed per instance GenericClass<String> stringContainer = new GenericClass<>("Hello"); GenericClass<Integer> intContainer = new GenericClass<>(42); System.out.println("String container: " + stringContainer.getValue()); System.out.println("Integer container: " + intContainer.getValue()); // Generic method usage - different types per call String result1 = processItem("Test"); Integer result2 = processItem(123); Double result3 = processItem(45.67); System.out.println("String result: " + result1); System.out.println("Integer result: " + result2); System.out.println("Double result: " + result3); // Generic method in generic class stringContainer.<Integer>printBoth(100); // Different type than class intContainer.<String>printBoth("World"); // Different type than class } } Output:
String container: Hello Integer container: 42 String result: Test Integer result: 123 Double result: 45.67 Class type: Hello Method type: 100 Class type: 42 Method type: World
Common Use Cases
1. Utility Methods:
import java.util.*; public class GenericUtilityMethods { // Swap elements in an array public static <T> void swap(T[] array, int i, int j) { T temp = array[i]; array[i] = array[j]; array[j] = temp; } // Reverse an array public static <T> void reverse(T[] array) { int left = 0; int right = array.length - 1; while (left < right) { swap(array, left, right); left++; right--; } } // Convert array to list public static <T> List<T> arrayToList(T[] array) { return new ArrayList<>(Arrays.asList(array)); } // Check if array contains element public static <T> boolean contains(T[] array, T element) { for (T item : array) { if (Objects.equals(item, element)) { return true; } } return false; } // Find index of element public static <T> int indexOf(T[] array, T element) { for (int i = 0; i < array.length; i++) { if (Objects.equals(array[i], element)) { return i; } } return -1; } // Concatenate two arrays public static <T> T[] concatenate(T[] first, T[] second) { T[] result = Arrays.copyOf(first, first.length + second.length); System.arraycopy(second, 0, result, first.length, second.length); return result; } public static void main(String[] args) { // Test swap Integer[] numbers = {1, 2, 3, 4, 5}; System.out.println("Before swap: " + Arrays.toString(numbers)); swap(numbers, 1, 3); System.out.println("After swap: " + Arrays.toString(numbers)); // Test reverse String[] words = {"A", "B", "C", "D", "E"}; System.out.println("Before reverse: " + Arrays.toString(words)); reverse(words); System.out.println("After reverse: " + Arrays.toString(words)); // Test arrayToList Double[] doubles = {1.1, 2.2, 3.3}; List<Double> doubleList = arrayToList(doubles); System.out.println("Array to list: " + doubleList); // Test contains and indexOf String[] fruits = {"apple", "banana", "cherry"}; System.out.println("Contains 'banana': " + contains(fruits, "banana")); System.out.println("Index of 'cherry': " + indexOf(fruits, "cherry")); // Test concatenate Integer[] first = {1, 2, 3}; Integer[] second = {4, 5, 6}; Integer[] combined = concatenate(first, second); System.out.println("Concatenated: " + Arrays.toString(combined)); } } Output:
Before swap: [1, 2, 3, 4, 5] After swap: [1, 4, 3, 2, 5] Before reverse: [A, B, C, D, E] After reverse: [E, D, C, B, A] Array to list: [1.1, 2.2, 3.3] Contains 'banana': true Index of 'cherry': 2 Concatenated: [1, 2, 3, 4, 5, 6]
2. Collection Utilities:
import java.util.*; public class CollectionUtilities { // Find maximum element in collection public static <T extends Comparable<T>> T findMax(Collection<T> collection) { if (collection.isEmpty()) { throw new IllegalArgumentException("Collection is empty"); } T max = null; for (T element : collection) { if (max == null || element.compareTo(max) > 0) { max = element; } } return max; } // Find minimum element in collection public static <T extends Comparable<T>> T findMin(Collection<T> collection) { if (collection.isEmpty()) { throw new IllegalArgumentException("Collection is empty"); } T min = null; for (T element : collection) { if (min == null || element.compareTo(min) < 0) { min = element; } } return min; } // Filter collection based on predicate public static <T> List<T> filter(Collection<T> collection, Predicate<T> predicate) { List<T> result = new ArrayList<>(); for (T element : collection) { if (predicate.test(element)) { result.add(element); } } return result; } // Transform collection using function public static <T, R> List<R> map(Collection<T> collection, Function<T, R> function) { List<R> result = new ArrayList<>(); for (T element : collection) { result.add(function.apply(element)); } return result; } // Check if all elements satisfy predicate public static <T> boolean allMatch(Collection<T> collection, Predicate<T> predicate) { for (T element : collection) { if (!predicate.test(element)) { return false; } } return true; } // Check if any element satisfies predicate public static <T> boolean anyMatch(Collection<T> collection, Predicate<T> predicate) { for (T element : collection) { if (predicate.test(element)) { return true; } } return false; } public static void main(String[] args) { List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9, 3, 7, 4, 6); System.out.println("Numbers: " + numbers); System.out.println("Max: " + findMax(numbers)); System.out.println("Min: " + findMin(numbers)); // Filter even numbers List<Integer> evenNumbers = filter(numbers, n -> n % 2 == 0); System.out.println("Even numbers: " + evenNumbers); // Transform numbers to their squares List<Integer> squares = map(numbers, n -> n * n); System.out.println("Squares: " + squares); // Check predicates boolean allPositive = allMatch(numbers, n -> n > 0); boolean hasEven = anyMatch(numbers, n -> n % 2 == 0); System.out.println("All positive: " + allPositive); System.out.println("Has even: " + hasEven); // With strings List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve"); System.out.println("\nNames: " + names); System.out.println("Max name: " + findMax(names)); System.out.println("Min name: " + findMin(names)); // Filter long names List<String> longNames = filter(names, name -> name.length() > 4); System.out.println("Long names: " + longNames); // Transform to uppercase List<String> upperCaseNames = map(names, String::toUpperCase); System.out.println("Uppercase names: " + upperCaseNames); } } // Functional interfaces interface Predicate<T> { boolean test(T t); } interface Function<T, R> { R apply(T t); } Output:
Numbers: [5, 2, 8, 1, 9, 3, 7, 4, 6] Max: 9 Min: 1 Even numbers: [2, 8, 4, 6] Squares: [25, 4, 64, 1, 81, 9, 49, 16, 36] All positive: true Has even: true Names: [Alice, Bob, Charlie, David, Eve] Max name: Eve Min name: Alice Long names: [Alice, Charlie, David] Uppercase names: [ALICE, BOB, CHARLIE, DAVID, EVE]
Wildcards in Generic Methods
Using Wildcards with Generic Methods:
import java.util.*; public class WildcardGenericMethods { // Upper bounded wildcard in generic method public static double sumOfList(List<? extends Number> list) { double sum = 0.0; for (Number number : list) { sum += number.doubleValue(); } return sum; } // Lower bounded wildcard in generic method public static void addNumbers(List<? super Integer> list) { for (int i = 1; i <= 5; i++) { list.add(i); } } // Unbounded wildcard in generic method public static void printList(List<?> list) { for (Object element : list) { System.out.print(element + " "); } System.out.println(); } // Generic method with wildcard and type parameter public static <T> void copy(List<? super T> dest, List<? extends T> src) { for (T element : src) { dest.add(element); } } // Wildcard with bounded type parameter public static <T extends Number> void processNumbers(List<T> numbers) { double sum = 0.0; for (T number : numbers) { sum += number.doubleValue(); } System.out.println("Sum: " + sum); } public static void main(String[] args) { // Upper bounded wildcard List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5); List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3); System.out.println("Sum of integers: " + sumOfList(integers)); System.out.println("Sum of doubles: " + sumOfList(doubles)); // Lower bounded wildcard List<Number> numberList = new ArrayList<>(); List<Object> objectList = new ArrayList<>(); addNumbers(numberList); addNumbers(objectList); System.out.println("Number list: " + numberList); System.out.println("Object list: " + objectList); // Unbounded wildcard List<String> strings = Arrays.asList("A", "B", "C"); List<Integer> numbers = Arrays.asList(1, 2, 3); System.out.print("Strings: "); printList(strings); System.out.print("Numbers: "); printList(numbers); // Copy method List<Number> destination = new ArrayList<>(); List<Integer> source = Arrays.asList(10, 20, 30); copy(destination, source); System.out.println("After copy: " + destination); // Process numbers processNumbers(integers); processNumbers(doubles); } } Output:
Sum of integers: 15.0 Sum of doubles: 6.6 Number list: [1, 2, 3, 4, 5] Object list: [1, 2, 3, 4, 5] Strings: A B C Numbers: 1 2 3 After copy: [10, 20, 30] Sum: 15.0 Sum: 6.6
Method Overloading with Generics
Overloading Generic Methods:
import java.util.*; public class GenericMethodOverloading { // Overloaded generic methods // 1. Print single element public static <T> void print(T element) { System.out.println("Single: " + element); } // 2. Print array public static <T> void print(T[] array) { System.out.print("Array: "); for (T element : array) { System.out.print(element + " "); } System.out.println(); } // 3. Print collection public static <T> void print(Collection<T> collection) { System.out.println("Collection: " + collection); } // 4. Print with custom message public static <T> void print(String message, T element) { System.out.println(message + ": " + element); } // 5. Print multiple elements public static <T> void print(T first, T second) { System.out.println("Two elements: " + first + ", " + second); } // 6. Varargs with generics @SafeVarargs public static <T> void printAll(T... elements) { System.out.print("All: "); for (T element : elements) { System.out.print(element + " "); } System.out.println(); } public static void main(String[] args) { // Test overloaded methods // Single element print("Hello"); print(42); print(3.14); // Array Integer[] intArray = {1, 2, 3}; String[] strArray = {"A", "B", "C"}; print(intArray); print(strArray); // Collection List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); Set<String> names = new HashSet<>(Arrays.asList("Alice", "Bob")); print(numbers); print(names); // With message print("The answer is", 42); print("PI value", 3.14159); // Two elements print("Java", "Generics"); print(10, 20); // Varargs printAll(1, 2, 3, 4, 5); printAll("A", "B", "C"); printAll(1.1, 2.2, 3.3); } } Output:
Single: Hello Single: 42 Single: 3.14 Array: 1 2 3 Array: A B C Collection: [1, 2, 3, 4, 5] Collection: [Alice, Bob] The answer is: 42 PI value: 3.14159 Two elements: Java, Generics Two elements: 10, 20 All: 1 2 3 4 5 All: A B C All: 1.1 2.2 3.3
Best Practices
1. Use Descriptive Type Parameter Names:
// ✅ Good - descriptive names public static <T> T getFirst(List<T> list) { ... } public static <K, V> V getValue(Map<K, V> map, K key) { ... } public static <E extends Comparable<E>> E findMax(Collection<E> collection) { ... } // ❌ Avoid - unclear names public static <X> X getFirst(List<X> list) { ... } 2. Prefer Type Inference:
List<String> names = Arrays.asList("Alice", "Bob"); // ✅ Good - let compiler infer type String first = getFirst(names); // ❌ Unnecessary - explicit type specification String first = GenericMethods.<String>getFirst(names); 3. Use Bounds When Appropriate:
// ✅ Good - uses bounds for type safety public static <T extends Comparable<T>> T findMax(T[] array) { ... } // ❌ Bad - no bounds, might not have compareTo method public static <T> T findMax(T[] array) { ... } 4. Avoid Raw Types:
// ✅ Good - uses generic types public static <T> void processList(List<T> list) { ... } // ❌ Bad - uses raw type public static void processList(List list) { ... } 5. Use @SuppressWarnings Judiciously:
// Only when you're sure about type safety @SuppressWarnings("unchecked") public static <T> T[] createArray(int size) { return (T[]) new Object[size]; } Complete Examples
Complete Working Example:
import java.util.*; import java.util.function.Function; import java.util.function.Predicate; public class GenericMethodsCompleteExample { public static void main(String[] args) { System.out.println("=== Generic Methods Complete Example ===\n"); // Example 1: Basic Generic Methods basicGenericMethods(); // Example 2: Collection Utilities collectionUtilities(); // Example 3: Mathematical Operations mathematicalOperations(); // Example 4: Advanced - Generic Builders and Factories advancedExamples(); } public static void basicGenericMethods() { System.out.println("1. Basic Generic Methods:"); // Identity method String str = identity("Hello Generics"); Integer num = identity(42); System.out.println("Identity string: " + str); System.out.println("Identity integer: " + num); // Print array Integer[] numbers = {1, 2, 3, 4, 5}; String[] words = {"apple", "banana", "cherry"}; System.out.print("Numbers: "); printArray(numbers); System.out.print("Words: "); printArray(words); // Swap elements System.out.println("Before swap: " + Arrays.toString(numbers)); swap(numbers, 0, 4); System.out.println("After swap: " + Arrays.toString(numbers)); System.out.println(); } public static void collectionUtilities() { System.out.println("2. Collection Utilities:"); List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9, 3, 7, 4, 6); List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve"); // Filtering List<Integer> evenNumbers = filter(numbers, n -> n % 2 == 0); List<String> longNames = filter(names, name -> name.length() > 4); System.out.println("Even numbers: " + evenNumbers); System.out.println("Long names: " + longNames); // Transformation List<Integer> squares = map(numbers, n -> n * n); List<String> upperCaseNames = map(names, String::toUpperCase); System.out.println("Squares: " + squares); System.out.println("Uppercase names: " + upperCaseNames); // Finding max/min System.out.println("Max number: " + findMax(numbers)); System.out.println("Min name: " + findMin(names)); System.out.println(); } public static void mathematicalOperations() { System.out.println("3. Mathematical Operations:"); List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5); List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5); // Sum of numbers System.out.println("Sum of integers: " + sum(integers)); System.out.println("Sum of doubles: " + sum(doubles)); // Average System.out.println("Average of integers: " + average(integers)); System.out.println("Average of doubles: " + average(doubles)); // Statistics Integer[] intArray = {5, 2, 8, 1, 9, 3}; Double[] doubleArray = {5.5, 2.2, 8.8, 1.1, 9.9}; System.out.println("Max integer: " + findMax(intArray)); System.out.println("Max double: " + findMax(doubleArray)); System.out.println(); } public static void advancedExamples() { System.out.println("4. Advanced Examples:"); // Generic factory method List<String> stringList = createEmptyArrayList(); List<Integer> intList = createEmptyArrayList(); stringList.add("Hello"); intList.add(42); System.out.println("String list: " + stringList); System.out.println("Integer list: " + intList); // Generic builder pattern Person person = buildPerson() .withName("John Doe") .withAge(30) .withEmail("[email protected]") .build(); System.out.println("Built person: " + person); // Generic pair operations Pair<String, Integer> nameAge = new Pair<>("Alice", 25); Pair<Integer, String> idName = new Pair<>(101, "Bob"); System.out.println("Name-Age: " + nameAge); System.out.println("ID-Name: " + idName); System.out.println("Swapped: " + swapPair(nameAge)); } // Basic generic methods public static <T> T identity(T value) { return value; } public static <T> void printArray(T[] array) { for (T element : array) { System.out.print(element + " "); } System.out.println(); } public static <T> void swap(T[] array, int i, int j) { T temp = array[i]; array[i] = array[j]; array[j] = temp; } // Collection utilities public static <T> List<T> filter(List<T> list, Predicate<T> predicate) { List<T> result = new ArrayList<>(); for (T item : list) { if (predicate.test(item)) { result.add(item); } } return result; } public static <T, R> List<R> map(List<T> list, Function<T, R> function) { List<R> result = new ArrayList<>(); for (T item : list) { result.add(function.apply(item)); } return result; } public static <T extends Comparable<T>> T findMax(List<T> list) { if (list.isEmpty()) throw new IllegalArgumentException("List is empty"); return Collections.max(list); } public static <T extends Comparable<T>> T findMin(List<T> list) { if (list.isEmpty()) throw new IllegalArgumentException("List is empty"); return Collections.min(list); } // Mathematical operations public static <T extends Number> double sum(List<T> numbers) { return numbers.stream().mapToDouble(Number::doubleValue).sum(); } public static <T extends Number> double average(List<T> numbers) { if (numbers.isEmpty()) return 0.0; return sum(numbers) / numbers.size(); } public static <T extends Comparable<T>> T findMax(T[] array) { if (array.length == 0) throw new IllegalArgumentException("Array is empty"); T max = array[0]; for (T element : array) { if (element.compareTo(max) > 0) { max = element; } } return max; } // Advanced methods public static <T> ArrayList<T> createEmptyArrayList() { return new ArrayList<>(); } public static PersonBuilder buildPerson() { return new PersonBuilder(); } public static <K, V> Pair<V, K> swapPair(Pair<K, V> pair) { return new Pair<>(pair.getValue(), pair.getKey()); } } // Supporting classes class Person { private String name; private int age; private String email; private Person(String name, int age, String email) { this.name = name; this.age = age; this.email = email; } @Override public String toString() { return String.format("Person{name='%s', age=%d, email='%s'}", name, age, email); } // Builder static class PersonBuilder { private String name; private int age; private String email; public PersonBuilder withName(String name) { this.name = name; return this; } public PersonBuilder withAge(int age) { this.age = age; return this; } public PersonBuilder withEmail(String email) { this.email = email; return this; } public Person build() { return new Person(name, age, email); } } } class Pair<K, V> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; } @Override public String toString() { return String.format("Pair{%s=%s}", key, value); } } Expected Output:
=== Generic Methods Complete Example === 1. Basic Generic Methods: Identity string: Hello Generics Identity integer: 42 Numbers: 1 2 3 4 5 Words: apple banana cherry Before swap: [1, 2, 3, 4, 5] After swap: [5, 2, 3, 4, 1] 2. Collection Utilities: Even numbers: [2, 8, 4, 6] Long names: [Alice, Charlie, David] Squares: [25, 4, 64, 1, 81, 9, 49, 16, 36] Uppercase names: [ALICE, BOB, CHARLIE, DAVID, EVE] Max number: 9 Min name: Alice 3. Mathematical Operations: Sum of integers: 15.0 Sum of doubles: 16.5 Average of integers: 3.0 Average of doubles: 3.3 Max integer: 9 Max double: 9.9 4. Advanced Examples: String list: [Hello] Integer list: [42] Built person: Person{name='John Doe', age=30, email='[email protected]'} Name-Age: Pair{Alice=25} ID-Name: Pair{101=Bob} Swapped: Pair{25=Alice} Key Takeaways
- Type Safety: Generic methods provide compile-time type checking
- Code Reusability: Write once, use with different types
- Type Inference: Compiler can often determine type parameters automatically
- Bounded Types: Restrict types that can be used with
extendsandsuper - Wildcards: Provide flexibility in method parameters
- Method Overloading: Generic methods can be overloaded like regular methods
- Utility Methods: Perfect for creating reusable utility functions
Generic methods are essential for writing flexible, type-safe, and reusable code in Java, especially when working with collections and utility functions.