Generic Methods in Java

Table of Contents

  1. Introduction to Generic Methods
  2. Syntax and Structure
  3. Type Inference
  4. Bounded Type Parameters
  5. Generic Methods vs Generic Classes
  6. Common Use Cases
  7. Wildcards in Generic Methods
  8. Method Overloading with Generics
  9. Best Practices
  10. 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:

AspectGeneric MethodsGeneric Classes
ScopeMethod levelClass level
Type ParametersDeclared in methodDeclared in class
UsageCan be in any classOnly in generic classes
FlexibilityDifferent types per callSame type for all instances
InferenceCompiler can infer typesTypes 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

  1. Type Safety: Generic methods provide compile-time type checking
  2. Code Reusability: Write once, use with different types
  3. Type Inference: Compiler can often determine type parameters automatically
  4. Bounded Types: Restrict types that can be used with extends and super
  5. Wildcards: Provide flexibility in method parameters
  6. Method Overloading: Generic methods can be overloaded like regular methods
  7. 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.

Leave a Reply

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


Macro Nepal Helper