Generics in Java are a powerful feature that allows you to write flexible, reusable, and type-safe code. They enable you to define classes, interfaces, and methods with a placeholder for the type of data they operate on. This helps in reducing code duplication and increasing type safety by catching errors at compile time.
Key Concepts
- Type Parameters: Generics use type parameters to specify the type of data they operate on. These parameters are usually denoted by single uppercase letters like
T
,E
,K
,V
, etc. - Generic Classes: Classes that can operate on any data type.
- Generic Methods: Methods that can operate on any data type.
- Bounded Type Parameters: Restricting the types that can be used as type arguments.
- Wildcards: Represent an unknown type and are used to specify a range of acceptable types.
Generic Classes
A generic class is defined with a type parameter in angle brackets <>
after the class name.
Example
public class Box<T> { private T content; public void setContent(T content) { this.content = content; } public T getContent() { return content; } }
Explanation
Box<T>
:T
is a type parameter that will be replaced with a specific type when an object ofBox
is created.setContent(T content)
: A method that sets the content of the box.getContent()
: A method that returns the content of the box.
Usage
public class Main { public static void main(String[] args) { Box<String> stringBox = new Box<>(); stringBox.setContent("Hello, Generics!"); System.out.println(stringBox.getContent()); Box<Integer> integerBox = new Box<>(); integerBox.setContent(123); System.out.println(integerBox.getContent()); } }
Generic Methods
A generic method is a method that can operate on any data type. It is defined with a type parameter before the return type.
Example
public class Util { public static <T> void printArray(T[] array) { for (T element : array) { System.out.println(element); } } }
Explanation
<T>
: Declares a type parameterT
for the method.printArray(T[] array)
: A method that prints all elements of an array.
Usage
public class Main { public static void main(String[] args) { Integer[] intArray = {1, 2, 3, 4, 5}; String[] stringArray = {"A", "B", "C"}; Util.printArray(intArray); Util.printArray(stringArray); } }
Bounded Type Parameters
You can restrict the types that can be used as type arguments by using bounded type parameters.
Example
public class NumberBox<T extends Number> { private T number; public void setNumber(T number) { this.number = number; } public T getNumber() { return number; } }
Explanation
T extends Number
: RestrictsT
to be a subclass ofNumber
.
Usage
public class Main { public static void main(String[] args) { NumberBox<Integer> intBox = new NumberBox<>(); intBox.setNumber(10); System.out.println(intBox.getNumber()); NumberBox<Double> doubleBox = new NumberBox<>(); doubleBox.setNumber(10.5); System.out.println(doubleBox.getNumber()); } }
Wildcards
Wildcards are used to specify a range of acceptable types.
Example
public class WildcardDemo { public static void printList(List<?> list) { for (Object element : list) { System.out.println(element); } } }
Explanation
List<?>
: A list of unknown type.
Usage
public class Main { public static void main(String[] args) { List<Integer> intList = Arrays.asList(1, 2, 3); List<String> stringList = Arrays.asList("A", "B", "C"); WildcardDemo.printList(intList); WildcardDemo.printList(stringList); } }
Practical Exercises
Exercise 1: Create a Generic Pair Class
Create a generic class Pair
that holds two values of any type.
public class Pair<T, U> { private T first; private U second; public Pair(T first, U second) { this.first = first; this.second = second; } public T getFirst() { return first; } public U getSecond() { return second; } }
Solution
public class Main { public static void main(String[] args) { Pair<String, Integer> pair = new Pair<>("Age", 30); System.out.println("First: " + pair.getFirst()); System.out.println("Second: " + pair.getSecond()); } }
Exercise 2: Implement a Generic Stack
Create a generic stack class with methods to push, pop, and peek elements.
public class Stack<T> { private List<T> elements = new ArrayList<>(); public void push(T element) { elements.add(element); } public T pop() { if (elements.isEmpty()) { throw new EmptyStackException(); } return elements.remove(elements.size() - 1); } public T peek() { if (elements.isEmpty()) { throw new EmptyStackException(); } return elements.get(elements.size() - 1); } }
Solution
public class Main { public static void main(String[] args) { Stack<Integer> stack = new Stack<>(); stack.push(1); stack.push(2); stack.push(3); System.out.println("Peek: " + stack.peek()); System.out.println("Pop: " + stack.pop()); System.out.println("Peek: " + stack.peek()); } }
Common Mistakes and Tips
- Type Erasure: Remember that Java uses type erasure to implement generics, which means that generic type information is removed at runtime. This can lead to some unexpected behavior if not understood properly.
- Raw Types: Avoid using raw types (e.g.,
List
instead ofList<T>
). They can lead to runtime errors and defeat the purpose of generics. - Bounded Wildcards: Use bounded wildcards (
<? extends T>
and<? super T>
) to increase the flexibility of your code.
Conclusion
Generics are a fundamental feature in Java that enhance code reusability and type safety. By understanding and utilizing generics, you can write more flexible and robust code. In the next topic, we will explore annotations, another powerful feature in Java that allows you to add metadata to your code.
Java Programming Course
Module 1: Introduction to Java
- Introduction to Java
- Setting Up the Development Environment
- Basic Syntax and Structure
- Variables and Data Types
- Operators
Module 2: Control Flow
Module 3: Object-Oriented Programming
- Introduction to OOP
- Classes and Objects
- Methods
- Constructors
- Inheritance
- Polymorphism
- Encapsulation
- Abstraction
Module 4: Advanced Object-Oriented Programming
Module 5: Data Structures and Collections
Module 6: Exception Handling
Module 7: File I/O
Module 8: Multithreading and Concurrency
- Introduction to Multithreading
- Creating Threads
- Thread Lifecycle
- Synchronization
- Concurrency Utilities
Module 9: Networking
- Introduction to Networking
- Sockets
- ServerSocket
- DatagramSocket and DatagramPacket
- URL and HttpURLConnection