Introduction: The Problem That Generics Solve
Imagine you are writing a method to sort a list of numbers. You write it for Integer, and a week later you need to sort a list of String. You have to create a new method or use Object and constantly perform type casting. This is inconvenient, unsafe, and leads to runtime errors.
This is exactly the problem that Generics solve in Java. They allow you to create classes, interfaces, and methods that work with different data types while maintaining strong type checking at compile time. Without generics, modern Java would be much less convenient and safe.
In this article, we will explore why Generics are needed, how they work, and most importantly, how to apply them with real-world examples.
1. Basics of Generics: What They Are and How They Work
1.1. The Problem with Raw Types
Before Java 5 (2004), developers used collections without specifying the type:
List list = new ArrayList();list.add("Hello");list.add(123);// When retrieving, you need to remember what type is storedString text = (String) list.get(0); // OKInteger num = (Integer) list.get(1); // OK// But if you make a mistake — ClassCastException at runtimeString error = (String) list.get(1); // Error!Such code is dangerous: the error is only discovered when the program runs.
1.2. How Generics Work
Generics allow you to specify the data type a class or method will work with, directly in angle brackets <T>. The compiler checks types at compile time and automatically inserts the correct casts.
Basic syntax:
// Declaring a generic classpublic class Box<T> { private T content; public void setContent(T content) { this.content = content; } public T getContent() { return content; }}
// UsageBox<String> stringBox = new Box<>(); // <> — diamond operator (Java 7+)stringBox.setContent("Hello");String text = stringBox.getContent(); // No casting!
Box<Integer> intBox = new Box<>();intBox.setContent(42);int number = intBox.getContent(); // No casting!
// stringBox.setContent(123); // Compilation error!1.3. Practical Advantages
- Type safety: errors are caught at compile time, not at runtime.
- Elimination of explicit casts: code becomes cleaner and more readable.
- Code reuse: one class/method works with any type.
2. Generics in Practice: Classes, Methods, and Interfaces
2.1. Generic Classes
The most common case is collections. But you can create your own generic classes:
public 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; }}
// UsagePair<String, Integer> pair = new Pair<>("Age", 30);String key = pair.getKey();Integer value = pair.getValue();2.2. Generic Methods
A method can have its own type parameters, independent of the class:
public class Utils { // Generic method to create a list from an array public static <T> List<T> arrayToList(T[] array) { List<T> list = new ArrayList<>(); for (T element : array) { list.add(element); } return list; }}
// UsageString[] names = {"Anna", "Boris", "Victor"};List<String> nameList = Utils.arrayToList(names);