Advanced Java - Generics
Introduction to Generics
Generics were introduced in Java 5 to provide a way to parameterize types. With Generics, you can define a class, interface, or method with placeholder types that will be specified later during instantiation or invocation. This feature is particularly useful for creating code that is both more readable and reusable.
Basic Syntax
The basic syntax of Generics involves using angle brackets (<>) and type parameters. Here is a simple example of a generic class:
public class Box<T> { private T t; public void set(T t) { this.t = t; } public T get() { return t; } }
In this example, T
is a type parameter that will be specified later. You can use this generic class as follows:
public class Main { public static void main(String[] args) { Box<Integer> integerBox = new Box<>(); integerBox.set(10); System.out.println("Integer Value: " + integerBox.get()); Box<String> stringBox = new Box<>(); stringBox.set("Hello Generics"); System.out.println("String Value: " + stringBox.get()); } }
String Value: Hello Generics
Generic Methods
In addition to generic classes, you can also create generic methods. Here is an example of a generic method that prints an array of any type:
public class Main { public static <E> void printArray(E[] inputArray) { for (E element : inputArray) { System.out.print(element + " "); } System.out.println(); } public static void main(String[] args) { Integer[] intArray = {1, 2, 3, 4, 5}; String[] strArray = {"one", "two", "three"}; printArray(intArray); printArray(strArray); } }
one two three
Bounded Type Parameters
Sometimes, you might want to restrict the types that can be used as type arguments. You can achieve this by using bounded type parameters. Here is an example:
public class Main { public static <T extends Number> void printNumbers(T[] nums) { for (T num : nums) { System.out.print(num + " "); } System.out.println(); } public static void main(String[] args) { Integer[] intArray = {1, 2, 3, 4, 5}; Float[] floatArray = {1.1f, 2.2f, 3.3f, 4.4f}; printNumbers(intArray); printNumbers(floatArray); } }
1.1 2.2 3.3 4.4
Wildcards in Generics
Wildcards are special kind of type arguments that allow for more flexibility. There are three types of wildcards in Java:
- Unbounded Wildcards:
?
- Upper Bounded Wildcards:
? extends Type
- Lower Bounded Wildcards:
? super Type
Here is an example of using upper-bounded wildcards:
import java.util.List; import java.util.Arrays; public class Main { public static void printList(List<? extends Number> list) { for (Number n : list) { System.out.print(n + " "); } System.out.println(); } public static void main(String[] args) { List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5); List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3); printList(intList); printList(doubleList); } }
1.1 2.2 3.3
Type Erasure
Generics in Java are implemented using a feature called type erasure. This means that generic type information is removed at runtime, and the code is transformed to use raw types and type casts. Here is an example to illustrate type erasure:
import java.util.ArrayList; public class Main { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("Hello"); // At runtime, the type information is erased // and the code is equivalent to: ArrayList listRaw = list; System.out.println(listRaw.get(0)); } }