Understanding Java Generics in 10 Easy Steps
Table of Contents
- What are Java Generics?
- Generic Classes
- Generic Interfaces
- Generic Methods
- Type Parameters and Type Arguments
- Bounded Type Parameters
- Wildcards
- Type Erasure
- Common Practices with Java Generics
- Best Practices for Using Java Generics
1. What are Java Generics?
Java generics provide a way to create type - safe code by allowing you to define classes, interfaces, and methods that can operate on different data types. Instead of writing separate code for each data type, you can use a single generic code that works with any type.
// A simple generic class
class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
In this example, T is a type parameter. It represents a type that will be specified when an object of the Box class is created.
2. Generic Classes
A generic class is a class that has one or more type parameters. These type parameters act as placeholders for actual types.
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
You can use the Pair class like this:
Pair<String, Integer> pair = new Pair<>("One", 1);
String key = pair.getKey();
Integer value = pair.getValue();
3. Generic Interfaces
Similar to generic classes, you can also create generic interfaces.
interface Container<T> {
void add(T item);
T get();
}
class MyContainer<T> implements Container<T> {
private T item;
@Override
public void add(T item) {
this.item = item;
}
@Override
public T get() {
return item;
}
}
4. Generic Methods
A generic method is a method that has its own type parameters.
class GenericMethodExample {
public static <T> T getLastElement(T[] array) {
if (array.length == 0) {
return null;
}
return array[array.length - 1];
}
}
You can call the generic method like this:
Integer[] intArray = {1, 2, 3};
Integer lastInt = GenericMethodExample.getLastElement(intArray);
String[] stringArray = {"a", "b", "c"};
String lastString = GenericMethodExample.getLastElement(stringArray);
5. Type Parameters and Type Arguments
Type parameters are the names (like T, K, V) used in the definition of generic classes, interfaces, or methods. Type arguments are the actual types that replace the type parameters when an object is created or a method is called.
// Type parameter is T
class MyGenericClass<T> {
private T data;
public MyGenericClass(T data) {
this.data = data;
}
public T getData() {
return data;
}
}
// Type argument is String
MyGenericClass<String> myObject = new MyGenericClass<>("Hello");
6. Bounded Type Parameters
Bounded type parameters allow you to restrict the types that can be used as type arguments. You can use the extends keyword to specify an upper bound.
class Calculator<T extends Number> {
public double add(T num1, T num2) {
return num1.doubleValue() + num2.doubleValue();
}
}
Calculator<Integer> calculator = new Calculator<>();
double result = calculator.add(1, 2);
7. Wildcards
Wildcards are used when you want to work with unknown types. There are three types of wildcards: ? (unbounded wildcard), ? extends T (upper - bounded wildcard), and ? super T (lower - bounded wildcard).
import java.util.ArrayList;
import java.util.List;
class WildcardExample {
public static void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
}
List<Integer> intList = new ArrayList<>();
intList.add(1);
WildcardExample.printList(intList);
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
WildcardExample.printList(stringList);
8. Type Erasure
Java uses type erasure to implement generics. At compile time, the type parameters are removed and replaced with their upper bounds (usually Object).
class TypeErasureExample<T> {
private T data;
public TypeErasureExample(T data) {
this.data = data;
}
public T getData() {
return data;
}
}
After type erasure, the TypeErasureExample class becomes:
class TypeErasureExample {
private Object data;
public TypeErasureExample(Object data) {
this.data = data;
}
public Object getData() {
return data;
}
}
9. Common Practices with Java Generics
- Use generic collections: Java’s collection framework (
List,Set,Map) is generic. Always use them with specific type arguments to ensure type safety.
List<String> stringList = new ArrayList<>();
stringList.add("Java");
- Create reusable code: Generic classes and methods can be reused with different data types, reducing code duplication.
10. Best Practices for Using Java Generics
- Keep type parameters simple: Use short and meaningful names like
T,K,Vfor type parameters. - Use bounded type parameters when necessary: This helps in ensuring that the generic code works only with valid types.
- Understand type erasure: Be aware of how type erasure affects your code, especially when using reflection.
Conclusion
Java generics are a powerful tool for writing type - safe and reusable code. By following these 10 steps, you should have a solid understanding of the fundamental concepts, usage methods, common practices, and best practices of Java generics. With this knowledge, you can create more robust and maintainable Java applications.
References
- The Java Tutorials - Oracle: https://docs.oracle.com/javase/tutorial/java/generics/index.html
- Effective Java by Joshua Bloch.