Generics in Java allow you to write flexible, reusable code that works with different types of data, while maintaining type safety. A generic method is a method that can operate on objects of various types, allowing you to define a method that can be used with different data types without repeating code. This is particularly useful when writing code that deals with collections or when the exact data type is not known until runtime.
Generic methods can work with any type of data as long as the type is specified when calling the method. They provide a way to ensure that the method is type-safe and that the type of data being passed to the method is consistent with the type declared.
A generic method is defined with a type parameter that is declared before the method’s return type. The type parameter can be used as a placeholder for any type, and it can appear in the method’s parameters, return type, or both.
The syntax for a generic method is as follows:
public <T> ReturnType methodName(T parameter) {
// method body
}
Here:
<T>
indicates that the method is generic, and T
is the type parameter.T
can be used in the method’s parameters, return type, or inside the method body.ReturnType
is the return type of the method, which can also be generic.Here’s a simple example of a generic method that prints elements of an array:
public class GenericMethodExample {
// A generic method that accepts any type of array and prints its elements
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
public static void main(String[] args) {
// Calling the generic method with Integer array
Integer[] intArray = {1, 2, 3, 4, 5};
printArray(intArray);
// Calling the generic method with String array
String[] strArray = {"Hello", "World"};
printArray(strArray);
}
}
printArray()
is generic because it is declared with <T>
before the return type (void
). The type parameter T
can represent any data type.T[]
represents an array of any type. This means the method can accept an array of Integer
, String
, Double
, or any other type.printArray()
method is called twice: once with an array of Integer
and once with an array of String
. In both cases, the same method is used, but with different data types, demonstrating the flexibility of generics.printArray()
is consistent with the declared type T[]
, providing type safety without the need for casting or manual type checks.You can declare a generic method with multiple type parameters by separating the type parameters with commas. This allows you to create methods that work with multiple types at the same time.
Example:
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public void displayPair() {
System.out.println("Key: " + key + ", Value: " + value);
}
public static void main(String[] args) {
// Creating a Pair of Integer and String
Pair<Integer, String> pair = new Pair<>(1, "One");
pair.displayPair(); // Output: Key: 1, Value: One
}
}
Pair
class is defined with two type parameters, K
and V
, representing the key and value, respectively.Pair
class, you can specify different types for K
and V
(e.g., Integer
and String
), and the generic method will handle the types correctly.Sometimes, you may want to restrict the types that can be passed to a generic method. This is done using bounded type parameters, which specify that the type parameter must be a subtype of a specific class or implement a particular interface.
Example of a generic method with a bounded type parameter:
public class BoundedGenericExample {
// A generic method that works only with Number and its subclasses
public static <T extends Number> double sum(T num1, T num2) {
return num1.doubleValue() + num2.doubleValue();
}
public static void main(String[] args) {
// Works with Integer
System.out.println(sum(10, 20)); // Output: 30.0
// Works with Double
System.out.println(sum(10.5, 20.5)); // Output: 31.0
}
}
<T extends Number>
:T
to be of type Number
or any of its subclasses (such as Integer
, Double
, Float
, etc.). This ensures that the method sum()
can only be used with numeric types.Number
Methods:num1.doubleValue()
and num2.doubleValue()
to ensure that both numbers are treated as double
values during addition. The Number
class provides the doubleValue()
method, and all its subclasses inherit it.Integer
, Double
, etc.) while ensuring that only numeric types are allowed. This is useful for performing operations that require numeric types without worrying about improper type inputs.Java generics support wildcards (?
), which represent an unknown type. Wildcards are useful when you need to work with a generic type but do not know or care about the specific type. Wildcards come in three forms:
?
):? extends T
):T
).? super T
):T
).Example of a method with an upper-bounded wildcard:
public class WildcardExample {
// A method that accepts a list of any subclass of Number
public static void printNumbers(List<? extends Number> numbers) {
for (Number number : numbers) {
System.out.println(number);
}
}
public static void main(String[] args) {
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
printNumbers(intList); // Output: 1 2 3
printNumbers(doubleList); // Output: 1.1 2.2 3.3
}
}
<? extends Number>
:printNumbers()
accepts a list of any type that is a subclass of Number
(e.g., Integer
, Double
, Float
).Integer
, Double
, etc.) without needing to define separate methods for each type.Generics in methods provide a powerful way to write reusable, flexible, and type-safe code. They enable you to define methods that work with a variety of data types while ensuring that type consistency is maintained at compile time. With features like bounded type parameters and wildcards, Java generics allow for even greater flexibility when working with collections and complex data types, making it easier to build scalable, maintainable code.