Generics in Java enable classes, interfaces, and methods to operate on types specified by the client at the time of use. This feature brings stronger type checks at compile time, eliminates the need for type casting, and allows for more generalized and reusable code. Let’s delve into a comprehensive yet straightforward tutorial on Java generics with examples.
Introduction to Generics
Generics were introduced to Java in JDK 5 to extend the language’s expressiveness while ensuring type safety and reducing runtime errors.
Basic Syntax:
- Generic classes:
class ClassName<T> { /*...*/ }
- Generic methods:
public <T> ReturnType methodName(T param) { /*...*/ }
Example 1: A Generic Class
Let’s start with a simple generic class that can store any type of object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class GenericBox<T> { private T content; public void add(T content) { this.content = content; } public T get() { return content; } public static void main(String[] args) { GenericBox<String> stringBox = new GenericBox<>(); stringBox.add("Hello Generics"); System.out.println(stringBox.get()); // Output: Hello Generics GenericBox<Integer> integerBox = new GenericBox<>(); integerBox.add(10); System.out.println(integerBox.get()); // Output: 10 } } |
Example 2: A Generic Method
Now, let’s look at a generic method that can work with any type of array and finds the middle element.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class GenericMethodTest { public static <T> T findMiddle(T[] array) { return array[array.length / 2]; } public static void main(String[] args) { Integer[] numbers = {1, 2, 3, 4, 5}; System.out.println(findMiddle(numbers)); // Output: 3 String[] words = {"Java", "Generics", "Tutorial"}; System.out.println(findMiddle(words)); // Output: Generics } } |
Example 3: Bounded Type Parameters
You can limit the types that can be used in generic methods and classes. This is known as bounded type parameters.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class NumberBox<T extends Number> { private T number; public NumberBox(T number) { this.number = number; } public double square() { return number.doubleValue() * number.doubleValue(); } public static void main(String[] args) { NumberBox<Integer> intBox = new NumberBox<>(5); System.out.println(intBox.square()); // Output: 25.0 NumberBox<Double> doubleBox = new NumberBox<>(3.14); System.out.println(doubleBox.square()); // Output: 9.8596 } } |
Example 4: Generic Interfaces
Interfaces can also be generic. Here’s how you can define and implement a generic interface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
interface GenericInterface<T> { void performAction(T object); } class StringAction implements GenericInterface<String> { public void performAction(String object) { System.out.println(object.toUpperCase()); } public static void main(String[] args) { StringAction action = new StringAction(); action.performAction("hello"); // Output: HELLO } } |
Example 5: Wildcards
Wildcards (?
) allow for more flexibility when dealing with type parameters, particularly when you’re not sure about the type of objects you’ll need to work with.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class WildcardExample { public static void printListItems(List<?> list) { for (Object item : list) { System.out.println(item); } } public static void main(String[] args) { List<Integer> intList = Arrays.asList(1, 2, 3); printListItems(intList); // Output: 1 2 3 List<String> stringList = Arrays.asList("Java", "Generics"); printListItems(stringList); // Output: Java Generics } } |
Conclusion
Generics in Java provide a way to write code that is more type-safe, reusable, and readable. By allowing types to be parameters when defining classes, interfaces, and methods, generics enable you to create flexible and generalized code that can operate on various data types. Through the use of generics, Java developers can achieve strong type checking at compile time and avoid the pitfalls of type casting and runtime errors.