Generics in Java enable the creation of classes, interfaces, and methods that can operate on different data types. Instead of writing separate code for each data type, you can write a single generic code that can handle multiple types. This is achieved by using type parameters, which are placeholders for actual data types.
A generic class is a class that has one or more type parameters. Here is an example of a simple generic class:
class GenericBox<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
public class GenericClassExample {
public static void main(String[] args) {
GenericBox<String> stringBox = new GenericBox<>();
stringBox.setItem("Hello, Generics!");
String item = stringBox.getItem();
System.out.println(item);
GenericBox<Integer> integerBox = new GenericBox<>();
integerBox.setItem(123);
Integer num = integerBox.getItem();
System.out.println(num);
}
}
In the above code, T
is a type parameter. When creating an instance of GenericBox
, you specify the actual type (e.g., String
or Integer
).
A generic interface is similar to a generic class. Here is an example of a generic interface:
interface GenericInterface<T> {
T getValue();
}
class GenericInterfaceImpl<T> implements GenericInterface<T> {
private T value;
public GenericInterfaceImpl(T value) {
this.value = value;
}
@Override
public T getValue() {
return value;
}
}
public class GenericInterfaceExample {
public static void main(String[] args) {
GenericInterface<String> stringImpl = new GenericInterfaceImpl<>("Generic Interface");
System.out.println(stringImpl.getValue());
GenericInterface<Integer> integerImpl = new GenericInterfaceImpl<>(456);
System.out.println(integerImpl.getValue());
}
}
A generic method is a method that has its own type parameters. Here is an example:
public class GenericMethodExample {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
public static void main(String[] args) {
Integer[] intArray = {1, 2, 3, 4, 5};
String[] stringArray = {"Hello", "World"};
printArray(intArray);
printArray(stringArray);
}
}
The <T>
before the return type of the printArray
method indicates that it is a generic method.
One of the most common uses of generics in Java is with collections. For example, ArrayList
is a generic class:
import java.util.ArrayList;
import java.util.List;
public class GenericCollectionExample {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("Apple");
stringList.add("Banana");
for (String fruit : stringList) {
System.out.println(fruit);
}
}
}
By using generics with ArrayList
, we ensure that only String
objects can be added to the list, and we don’t need to perform type casting when retrieving elements.
Bounded type parameters allow you to restrict the types that can be used as type arguments. For example, if you want a generic class to work only with numbers, you can use an upper - bounded type parameter:
class NumberBox<T extends Number> {
private T number;
public NumberBox(T number) {
this.number = number;
}
public double getDoubleValue() {
return number.doubleValue();
}
}
public class BoundedTypeExample {
public static void main(String[] args) {
NumberBox<Integer> integerBox = new NumberBox<>(10);
System.out.println(integerBox.getDoubleValue());
// This would cause a compilation error
// NumberBox<String> stringBox = new NumberBox<>("Hello");
}
}
The T extends Number
syntax restricts the type parameter T
to be a subtype of Number
.
Java uses type erasure to implement generics. Type erasure means that the type information of generics is removed at runtime. For example, a List<String>
and a List<Integer>
have the same runtime type, which is List
.
import java.util.ArrayList;
import java.util.List;
public class TypeErasureExample {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
System.out.println(stringList.getClass() == integerList.getClass()); // true
}
}
This can have implications when using reflection or creating arrays of generic types.
T
, use more descriptive names if possible. For example, E
is commonly used for elements in collections.?
) are useful when you want to work with unknown types. For example, List<? extends Number>
can hold any list of subtypes of Number
.Java generics are a powerful feature that provides compile - time type safety, code reusability, and eliminates the need for explicit type casting. By understanding the fundamental concepts, usage methods, common practices, and best practices of generics, you can write more robust and maintainable Java code. Whether you are working with collections or creating your own generic classes and methods, generics are an essential part of modern Java programming.