Generics in Java: A Complete Guide
Generics in Java: A Complete Guide
Java Generics provide a way to define classes, interfaces, and methods with type parameters. This allows us to write flexible and reusable code while maintaining type safety at compile time.
1. Generic Variables
A generic variable is a type placeholder (e.g., <T>, <K, V>) that gets replaced with a concrete type when an object is instantiated.
Example: Generic Variable in a Class
class Box<T> { // T is a type parameter
??? private T value; // Generic variable
?
??? public void setValue(T value) {
??????? this.value = value;
??? }
?
??? public T getValue() {
??????? return value;
??? }
}
?
public class GenericVariableExample {
??? public static void main(String[] args) {
??????? Box<Integer> intBox = new Box<>(); // T is Integer
??????? intBox.setValue(100);
??????? System.out.println("Integer Value: " + intBox.getValue());
?
??????? Box<String> strBox = new Box<>(); // T is String
??????? strBox.setValue("Hello Generics");
??????? System.out.println("String Value: " + strBox.getValue());
??? }
}
Output
Integer Value: 100
String Value: Hello Generics
2. Generic Methods
A generic method allows type parameters that are defined only within the method scope.
Example: Generic Method
class Utility {
??? // Generic method to print arrays of any type
??? public static <T> void printArray(T[] array) {
??????? for (T item : array) {
??????????? System.out.print(item + " ");
??????? }
??????? System.out.println();
??? }
}
?
public class GenericMethodExample {
??? public static void main(String[] args) {
??????? Integer[] intArray = {1, 2, 3, 4, 5};
??????? String[] strArray = {"A", "B", "C"};
?
??????? System.out.print("Integer Array: ");
??????? Utility.printArray(intArray);
?
??????? System.out.print("String Array: ");
??????? Utility.printArray(strArray);
??? }
}
Output
Integer Array: 1 2 3 4 5
String Array: A B C
3. Generic Classes
A generic class uses type parameters to handle different data types while ensuring type safety.
Example: Generic Class
class Pair<K, V> { // Two generic parameters: K and 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;
??? }
}
?
public class GenericClassExample {
??? public static void main(String[] args) {
??????? Pair<Integer, String> p1 = new Pair<>(1, "One");
??????? Pair<String, Double> p2 = new Pair<>("Pi", 3.14);
?
??????? System.out.println("Pair 1: " + p1.getKey() + " -> " + p1.getValue());
??????? System.out.println("Pair 2: " + p2.getKey() + " -> " + p2.getValue());
??? }
}
领英推荐
Output
Pair 1: 1 -> One
Pair 2: Pi -> 3.14
4. Wildcards (?) in Generics
The wildcard (?) is used when the exact type parameter is unknown. It allows flexibility in working with different generic types.
4.1 Unbounded Wildcard (?)
Used when the type is unknown.
class Printer {
??? public static void printList(List<?> list) { // ? allows any type
??????? for (Object obj : list) {
??????????? System.out.print(obj + " ");
??????? }
??????? System.out.println();
??? }
}
?
public class WildcardExample {
??? public static void main(String[] args) {
??????? List<Integer> intList = List.of(1, 2, 3);
??????? List<String> strList = List.of("A", "B", "C");
?
??????? Printer.printList(intList); // Works with Integer
??????? Printer.printList(strList); // Works with String
??? }
}
Output
1 2 3
A B C
4.2 Upper Bounded Wildcard (? extends T)
Allows a method to accept T or any subclass of T.
class NumberPrinter {
??? public static void printNumbers(List<? extends Number> list) { // Accepts Number or its subclasses
??????? for (Number num : list) {
??????????? System.out.print(num + " ");
??????? }
??????? System.out.println();
??? }
}
?
public class UpperBoundWildcardExample {
??? public static void main(String[] args) {
??????? List<Integer> intList = List.of(10, 20, 30);
??????? List<Double> doubleList = List.of(1.1, 2.2, 3.3);
?
??????? NumberPrinter.printNumbers(intList);?? // Works with Integer
??????? NumberPrinter.printNumbers(doubleList); // Works with Double
??? }
}
Output
10 20 30
1.1 2.2 3.3
4.3 Lower Bounded Wildcard (? super T)
Allows a method to accept T or any superclass of T.
class LowerBoundPrinter {
??? public static void addNumbers(List<? super Integer> list) {
???? ???list.add(100);
??????? list.add(200);
??????? System.out.println(list);
??? }
}
?
public class LowerBoundWildcardExample {
??? public static void main(String[] args) {
??????? List<Number> numList = new ArrayList<>();
??????? LowerBoundPrinter.addNumbers(numList); // Works with Number
??? }
}
Output
[100, 200]
5. Generics and Inheritance
Generics do not follow normal inheritance rules.
class Animal {}
class Dog extends Animal {}
?
public class InheritanceWithGenerics {
??? public static void main(String[] args) {
??????? List<Dog> dogList = new ArrayList<>();
??????? // List<Animal> animalList = dogList; // Compilation error!
??? }
}
Even though Dog extends Animal, List<Dog> is not a subtype of List<Animal>. To handle such cases, wildcards (? extends T) can be used.
6. Benefits of Generics
Conclusion
Generics in Java provide a powerful way to create type-safe, reusable, and efficient code. They allow us to define generic variables, methods, and classes, along with wildcards (?), upper/lower bounds, and inheritance rules.
?