Generics in Java: why they are needed and how to use them
Generics are one of the key features of Java, introduced in version J2SE 5.0. They allow you to create classes, interfaces, and methods that work with data types specified at the time of invocation. Instead of writing separate implementations for Integer, String, and Double, you write one universal code. In this article, we will look at why Generics are needed in Java, how to use them in practice, and what pitfalls you should know about.
Why are Generics needed in Java?
Before the advent of Generics, developers used Object to store any types. This worked, but led to two serious problems:
- Lack of type checking at compile time. You could accidentally put a
Stringinto a list instead of anInteger, and the error would only appear during program execution. - Need for explicit type casting. Every time you retrieved an element from a collection, a cast was required:
(String) list.get(0). This made the code cumbersome and dangerous.
Generics solve both problems. They allow the compiler to check type correctness and perform casting automatically. Let's look at a simple example:
// Without Generics (old style)List list = new ArrayList();list.add("Hello");String s = (String) list.get(0); // explicit cast needed
// With GenericsList<String> list = new ArrayList<>();list.add("Hello");String s = list.get(0); // no cast requiredThe code has become safer and more readable. A type error is detected at compile time, not at runtime.
How to use Generics: basic constructs
1. Generic classes
You can define a class that takes one or more type parameters. Most often, a single letter is used: T (Type), E (Element), K (Key), V (Value).
public class Box<T> { private T content;
public void put(T item) { this.content = item; }
public T get() { return content; }}
// UsageBox<String> stringBox = new Box<>();stringBox.put("Java");String value = stringBox.get(); // automatic castingThe parameter T is substituted with the actual type when the object is created. The compiler guarantees that you cannot put an Integer into a Box<String>.
2. Generic methods
Methods can also be generic, even if the class is not. The type parameter is specified before the return type:
public class Utils { public static <T> T getMiddle(T... args) { return args[args.length / 2]; }}
// CallString middle = Utils.<String>getMiddle("A", "B", "C");// or simply: Utils.getMiddle("A", "B", "C") — the compiler will infer the typeGeneric methods are useful for utility functions that work with different data types.
3. Bounded type parameters
Sometimes you need to restrict which types can be substituted. For example, to make a method work only with numbers:
public class Calculator { public static <T extends Number> double sum(T a, T b) { return a.doubleValue() + b.doubleValue(); }}
// Works with Integer, Double, Float, etc.double result = Calculator.sum(5, 10); // 15.0The keyword extends means that T must be a subtype of the specified class (or implement an interface).
Wildcards: unknown types
Wildcards are used when the exact type is unknown or not important. They are denoted by a question mark ?. There are three types:
Blogs
Book Recommendations
Read also