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.