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