Generics in Java: Type Safety and Flexibility
Generics in Java allow you to define classes, methods, and interfaces with type parameters. They provide stronger type checks at compile time, reducing errors related to type casting and enhancing code reusability.
1. What are Generics?
Generics enable classes, interfaces, and methods to operate on objects of various types while providing compile-time type safety. Instead of working with raw types, you specify type parameters to make your code more flexible and reusable.
2. Need for Generics
- Generics provide type safety by ensuring that only the specified type of data is used.
- They eliminate type-casting errors at runtime.
- Generics allow for reusable code that works with any type, enhancing maintainability.
- Compile-time errors are detected, reducing runtime issues.
3. Generic Collections
Using generics with collections ensures type safety and avoids the need for type casting. For example, instead of using raw collections, you can specify the type of elements the collection will hold.
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
// list.add(123); // Compile-time error
for (String item : list) {
System.out.println(item);
}
}
}
4. Create a Generic Class
A generic class can hold objects of any type. Here's an example of a simple generic class:
public class GenericBox<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public static void main(String[] args) {
GenericBox<String> box = new GenericBox<>();
box.setValue("Generics in Java");
System.out.println(box.getValue());
}
}
5. Create Generic Methods
Generic methods allow you to specify type parameters in methods to handle different types of data.
public class Main {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
public static void main(String[] args) {
String[] names = {"Alice", "Bob", "Charlie"};
Integer[] numbers = {1, 2, 3};
printArray(names);
printArray(numbers);
}
}
6. Bounded Types
In generics, you can restrict the types allowed for a parameter using bounded types. For example, you can restrict the type to subclasses of a specific class.
public class Main {
public static <T extends Number> void printDouble(T number) {
System.out.println(number.doubleValue());
}
public static void main(String[] args) {
printDouble(10); // Integer
printDouble(10.5); // Double
// printDouble("text"); // Compile-time error
}
}
7. Wildcards
Wildcards in generics provide flexibility when you want to allow multiple types. There are three types of wildcards in Java:
Unbounded Wildcard
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
Upper-Bounded Wildcard
Use `? extends Type` to specify that the type is a subclass of the given class.
public static void printNumbers(List<? extends Number> list) {
for (Number num : list) {
System.out.println(num);
}
}
Lower-Bounded Wildcard
Use `? super Type` to specify that the type is a superclass of the given class.
public static void addNumbers(List<? super Integer> list) {
list.add(10);
list.add(20);
}
8. Conclusion
Generics in Java provide compile-time type safety, allowing you to write more flexible and reusable code. They are widely used in the Java Collections Framework, making it easier to work with different types of data in a type-safe manner.