Generics in Java allow you to write more flexible and reusable code while maintaining type safety. Generics provide a way to define classes, interfaces, and methods with a placeholder for the type of data they operate on. This allows you to create code that can work with different data types without sacrificing type safety.
Key Concepts of Generics
- Type Parameters: Placeholders for types that are specified when the class, interface, or method is instantiated or called.
- Type Safety: Ensures that the code does not produce runtime errors due to type mismatches.
- Generic Classes and Interfaces: Classes and interfaces that use type parameters.
- Generic Methods: Methods that use type parameters.
- Bounded Type Parameters: Restrict the types that can be used as type arguments.
- Wildcards: Provide flexibility when working with generic types.
Generic Classes and Interfaces
A generic class or interface is defined with type parameters. For example, a generic class
Box
that can hold any type of object:
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
Here, T
is a type parameter that will be specified when the class is instantiated:
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
String content = stringBox.getContent();
Generic Methods
A generic method is a method that can operate on objects of various types while providing compile-time type safety. Generic methods are defined with type parameters:
public class GenericMethods {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
}
You can call this method with arrays of different types:
Integer[] intArray = {1, 2, 3, 4, 5};
String[] stringArray = {"A", "B", "C"};
GenericMethods.printArray(intArray); // Output: 1 2 3 4 5
GenericMethods.printArray(stringArray); // Output: A B C
Bounded Type Parameters
You can restrict the types that can be used as type arguments using bounded type parameters. For example, a method that works only with numbers:
public class BoundedTypeParameters {
public static <T extends Number> void printNumber(T number) {
System.out.println(number);
}
}
This method can only be called with arguments that are subclasses of Number
:
BoundedTypeParameters.printNumber(123); // Valid
BoundedTypeParameters.printNumber(45.67); // Valid
// BoundedTypeParameters.printNumber("Hello"); // Compilation error
Wildcards
Wildcards provide flexibility in using generic types. There are three types of wildcards:
Unbounded Wildcards (?
): Accept any type.
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.print(elem + " ");
}
System.out.println();
}
Bounded Wildcards (? extends Type
): Accept any type that is a subclass of
Type
.
public static void printListOfNumbers(List<? extends Number> list) {
for (Number num : list) {
System.out.print(num + " ");
}
System.out.println();
}
Lower Bounded Wildcards (? super Type
): Accept any type that is a superclass of
Type
.
public static void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
list.add(3);
}
Example: Generic Class, Method, and Wildcards
Here's an example that combines these concepts:
import java.util.ArrayList;
import java.util.List;
public class GenericExample {
// Generic class
public static class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
// Generic method
public static <T> void printBoxContent(Box<T> box) {
System.out.println("Box contains: " + box.getContent());
}
// Using wildcards
public static void printBoxContentWildcard(Box<?> box) {
System.out.println("Box contains: " + box.getContent());
}
public static void main(String[] args) {
// Using generic class
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello, Generics!");
printBoxContent(stringBox);
// Using bounded type parameters
Box<Integer> integerBox = new Box<>();
integerBox.setContent(123);
printBoxContentWildcard(integerBox);
// Using wildcards
List<Box<?>> list = new ArrayList<>();
list.add(stringBox);
list.add(integerBox);
for (Box<?> box : list) {
printBoxContentWildcard(box);
}
}
}
Output
java -cp /tmp/EeVlaGwUXv/GenericExample
Box contains: Hello, Generics!
Box contains: 123
Box contains: Hello, Generics!
Box contains: 123
=== Code Execution Successful ===
Summary
Generics in Java provide a powerful mechanism for writing flexible, reusable, and type-safe code. The key concepts include:
- Type Parameters: Placeholders for types that provide compile-time type safety.
- Generic Classes and Interfaces: Allow classes and interfaces to work with any data type.
- Generic Methods: Methods that use type parameters to operate on various types.
- Bounded Type Parameters: Restrict the types that can be used as type arguments.
- Wildcards: Provide flexibility in working with generic types.
Mastering generics allows you to write more robust and maintainable Java programs, ensuring type safety and reducing runtime errors.
Read more
Explain the Stream API introduced in Java 8. How does it work?
Java Memory Management: Understanding Stack and Heap
Java Streams API: Intermediate and Terminal Operations
Java I/O Streams: Working with Files and Input/Output Operations
Leave Comment