Table of Contents
- Introduction
- What are Generics?
- Benefits of Using Generics
- Generic Types
- Generic Classes
- Generic Methods
- Bounded Type Parameters
- Wildcards in Generics
- Type Erasure
- Example Usage
- Conclusion
1. Introduction
Generics in Java are a powerful feature introduced in Java 5 that allow you to write more flexible and reusable code. With generics, you can write classes, interfaces, and methods that work with any type of object while providing compile-time type safety. Generics eliminate the need for casting and help ensure that you’re working with the correct types.
This module will explore the concept of generics, how they are implemented in Java, and when and why to use them. We’ll also discuss bounded type parameters, wildcards, and how generics are used in common Java collections.
2. What are Generics?
Generics enable classes, interfaces, and methods to operate on objects of different types while providing compile-time type checking. A generic class or method can work with any object type, but the type is specified when the class or method is instantiated.
For example, instead of creating separate classes for handling different types of data, such as String or Integer, you can create a single generic class that works with any type, and the type is determined at runtime.
Syntax of Generics
The general syntax of generics in Java is:
class ClassName<T> {
T value;
public ClassName(T value) {
this.value = value;
}
}
Here, T
is the placeholder for the type, and it can represent any type of object.
3. Benefits of Using Generics
Generics provide several benefits:
- Type Safety: Generics enforce compile-time type safety, preventing you from accidentally mixing incompatible types.
- Code Reusability: You can create classes, interfaces, and methods that work with any type, making your code more reusable.
- Avoiding Type Casting: Generics eliminate the need for explicit type casting, reducing errors and improving code readability.
- Improved Maintainability: With type safety and reusable components, the code is easier to maintain and extend.
4. Generic Types
Generics can be applied to both classes and methods. Let’s take a look at each type:
Generic Classes
A generic class is a class that can operate on objects of various types, specified at the time the object is created. For example:
// A simple generic class
class Box<T> {
private T value;
public Box(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
Here, T
represents a type that will be defined when the Box
class is instantiated.
Example Usage:
public class Main {
public static void main(String[] args) {
Box<String> stringBox = new Box<>("Hello");
Box<Integer> intBox = new Box<>(123);
System.out.println(stringBox.getValue());
System.out.println(intBox.getValue());
}
}
Output:
Hello
123
Generic Methods
You can also define generic methods within regular classes or interfaces. A generic method can be defined with its own type parameter, separate from the class’s type parameter.
public class GenericMethodExample {
// A generic method
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
public static void main(String[] args) {
Integer[] intArray = {1, 2, 3};
String[] strArray = {"Java", "Generics", "Example"};
// Calling the generic method
printArray(intArray);
printArray(strArray);
}
}
Output:
1
2
3
Java
Generics
Example
In this example, the method printArray()
is able to handle arrays of any type (Integer[]
, String[]
, etc.) by defining the generic type T
in the method signature.
5. Bounded Type Parameters
Sometimes, you may want to limit the types that can be used with generics. This can be achieved by bounded type parameters.
Upper Bounded Wildcards
Upper bounded wildcards specify that the type must be a subtype of a specific class. This is done using the extends
keyword.
public class UpperBoundedExample {
// Upper bounded wildcard
public static void printNumbers(List<? extends Number> list) {
for (Number num : list) {
System.out.println(num);
}
}
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); // Works with Integer (subtype of Number)
printNumbers(doubleList); // Works with Double (subtype of Number)
}
}
Output:
1
2
3
1.1
2.2
3.3
Lower Bounded Wildcards
In some cases, you may need to specify that a type must be a supertype of a particular class. This is achieved with lower bounded wildcards, using the super
keyword.
public class LowerBoundedExample {
// Lower bounded wildcard
public static void addNumbers(List<? super Integer> list) {
list.add(10); // Works because Integer is a subtype of Number
}
public static void main(String[] args) {
List<Number> numberList = new ArrayList<>();
addNumbers(numberList); // Works with Number or any superclass of Integer
}
}
6. Wildcards in Generics
Wildcards are used to represent unknown types and are most often used when you are dealing with methods that can accept different types of arguments, but you don’t need to know the exact type.
- ? extends T: Wildcard that matches any type that is a subtype of
T
. - ? super T: Wildcard that matches any type that is a supertype of
T
. - ?: Represents any type.
7. Type Erasure
Java uses a feature called type erasure to implement generics. During compile time, the compiler works with the generic types, but at runtime, the type information is erased. This means that at runtime, all type parameters are replaced by their raw types.
For instance, in the case of Box<T>
, after type erasure, it becomes Box
at runtime, and the type parameter T
is not available.
8. Example Usage
Here’s an example that uses generics, bounded wildcards, and type parameters:
import java.util.*;
public class GenericsExample {
public static <T> void printList(List<T> list) {
for (T element : list) {
System.out.println(element);
}
}
public static void main(String[] args) {
List<Integer> intList = Arrays.asList(1, 2, 3);
List<String> strList = Arrays.asList("Java", "Generics", "Example");
printList(intList); // Prints Integer list
printList(strList); // Prints String list
}
}
9. Conclusion
Generics are a powerful feature in Java that allow for type-safe code that can work with different types of objects. Using generics helps you avoid type-casting, enhances code reuse, and ensures type safety at compile-time. It’s important to understand the different ways to work with generics, including generic classes, generic methods, and bounded wildcards, as well as how Java handles type erasure.
Generics are widely used in Java Collections Framework, ensuring that you can work with collections in a more flexible and error-free manner. Understanding how to use generics efficiently can significantly improve your ability to write robust and maintainable code.