Generics in Java: Why They Are Needed and How to Use Them for Beginners
When learning Java, sooner or later you will encounter the concept of Generics. For a beginner, this may seem complicated, but in reality, it is one of the most useful tools in the language. In this article, we will break down what Generics are, why they are needed, and how to use them correctly. The material is intended for beginner developers.
What are Generics and why is it bad without them?
Generics are a Java mechanism that allows you to parameterize types. In simple terms, you can write a class, interface, or method that will work with different data types, while maintaining strict typing at compile time.
Imagine you want to create a box that can hold any item. Without Generics, you would have to use Object:
public class Box { private Object item;
public void put(Object item) { this.item = item; }
public Object get() { return item; }}Using such a class is a real pain:
Box box = new Box();box.put("Hello");// When retrieving, you need to explicitly cast the typeString text = (String) box.get(); // Risk of ClassCastException!If you accidentally put a number and then try to cast it to a string, the program will crash with an error at runtime. Generics solve this problem by moving type checking to compile time.
How to use Generics: basic syntax
Let's rewrite our Box class using Generics:
public class Box<T> { private T item;
public void put(T item) { this.item = item; }
public T get() { return item; }}Here, <T> is the type parameter. T is a commonly used name (from the word "Type"), but you can use any letter or word: E (Element), K (Key), V (Value), etc.
Now the usage has become safe and elegant:
Box<String> stringBox = new Box<>();stringBox.put("Hello");String text = stringBox.get(); // No need for casting!
// This line will not compile:// stringBox.put(123); // Compilation error!The compiler itself knows that stringBox only works with strings. If you try to put a number, the code won't even compile — you'll learn about the error immediately, not at runtime.
Generics in collections: where they are needed most
The most popular place to use Generics is in Java collections (List, Set, Map, and others). Before the advent of generics (in Java 5), developers struggled with ArrayList, which stored Object:
// Old style (without Generics)List list = new ArrayList();list.add("Hello");list.add(123); // This can also be added!
String item = (String) list.get(0); // FineString item2 = (String) list.get(1); // ClassCastException!With Generics, everything is simpler and safer:
List<String> names = new ArrayList<>();names.add("Alice");names.add("Bob");// names.add(123); // Compilation error
for (String name : names) { System.out.println(name.toUpperCase()); // No casting!}The same works for Map:
Map<String, Integer> ageMap = new HashMap<>();ageMap.put("Alice", 30);ageMap.put("Bob", 25);
int aliceAge = ageMap.get("Alice"); // Automatic unboxing to intBounded types and wildcards: how to make Generics flexible
Sometimes you need to restrict what types a generic can accept. Bounded type parameters are used for this purpose.
For example