Java Generics is a powerful feature introduced in Java 5 that allows you to define classes, interfaces, and methods with type parameters. This provides stronger type checks at compile time and eliminates the need for type casting. Let’s dive into the details.
Basics of Generics
Generics enable types (classes and interfaces) to be parameters when defining classes, interfaces, and methods. Much like the more familiar formal parameters used in method declarations, type parameters provide a way for you to re-use the same code with different inputs.
Generic Class
A generic class is defined with the following syntax:
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
Here, T
is a type parameter that will be replaced with a concrete type when an instance of Box
is created.
Generic Method
A generic method is a method that can operate on objects of various types while providing compile-time type safety. Here’s an example:
public class Util {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
}
In this example, <T>
before the return type void
indicates that the method is generic.
Type Erasure
Java generics are implemented through a technique called type erasure. This means that generic type information is erased at runtime, and replaced with their bounds or Object
if the type parameter is unbounded. This ensures backward compatibility with older versions of Java that do not support generics.
Bounded Type Parameters
You can restrict the types that can be used as type arguments in a parameterized type. This is called bounded type parameters.
Upper Bounded Wildcards
You can use the extends
keyword to set an upper bound for a type parameter:
public <T extends Number> void printNumbers(T[] numbers) {
for (T number : numbers) {
System.out.println(number);
}
}
In this example, T
can be any type that is a subclass of Number
.
Lower Bounded Wildcards
You can use the super
keyword to set a lower bound for a type parameter:
public void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
In this example, the list can accept any type that is a superclass of Integer
.
Wildcards
Wildcards are used to specify unknown types. They can be used in three ways:
- Unbounded Wildcards:
<?>
- Upper Bounded Wildcards:
<? extends T>
- Lower Bounded Wildcards:
<? super T>
Unbounded Wildcards
An unbounded wildcard represents any type:
public void printList(List<?> list) {
for (Object element : list) {
System.out.println(element);
}
}
Upper Bounded Wildcards
An upper bounded wildcard restricts the unknown type to be a specific type or a subtype of that type:
public void printListOfNumbers(List<? extends Number> list) {
for (Number number : list) {
System.out.println(number);
}
}
Lower Bounded Wildcards
A lower bounded wildcard restricts the unknown type to be a specific type or a supertype of that type:
public void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
Generic Interfaces
Interfaces can also be generic:
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
Type Inference
Java can infer the type parameters of a generic method call, making the code more concise:
Box<Integer> integerBox = new Box<>();
integerBox.set(10);
Common Pitfalls
- Type Erasure: Generic type information is not available at runtime.
- Cannot Instantiate Generic Types with Primitive Types: You cannot create instances of generic types with primitive types.
- Static Members: Static members cannot use type parameters.
Example
Here’s a complete example that demonstrates many of the concepts discussed:
import java.util.ArrayList;
import java.util.List;
public class GenericsExample {
public static void main(String[] args) {
// Generic class
Box<Integer> integerBox = new Box<>();
integerBox.set(10);
System.out.println("Box contains: " + integerBox.get());
// Generic method
Integer[] intArray = {1Certainly! Let's continue with the example:
```java
Integer[] intArray = {1, 2, 3, 4, 5};
Util.printArray(intArray);
// Bounded type parameters
List<Number> numberList = new ArrayList<>();
numberList.add(1);
numberList.add(2.5);
printNumbers(numberList);
// Wildcards
List<Integer> integerList = new ArrayList<>();
integerList.add(1);
integerList.add(2);
printList(integerList);
printListOfNumbers(integerList);
List<Object> objectList = new ArrayList<>();
addIntegers(objectList);
printList(objectList);
}
// Bounded type parameter method
public static <T extends Number> void printNumbers(List<T> list) {
for (T number : list) {
System.out.println(number);
}
}
// Unbounded wildcard method
public static void printList(List<?> list) {
for (Object element : list) {
System.out.println(element);
}
}
// Upper bounded wildcard method
public static void printListOfNumbers(List<? extends Number> list) {
for (Number number : list) {
System.out.println(number);
}
}
// Lower bounded wildcard method
public static void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
}
// Generic class
class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
// Generic method utility class
class Util {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
}
Explanation
Generic Class: The
Box<T>
class can hold any typeT
. In themain
method, we create an instance ofBox
withInteger
as the type parameter.Generic Method: The
Util.printArray
method can print arrays of any type. We demonstrate this with an array ofInteger
.Bounded Type Parameters: The
printNumbers
method can accept a list of any type that extendsNumber
. We demonstrate this with a list ofNumber
.Wildcards:
- Unbounded Wildcard: The
printList
method can accept a list of any type. - Upper Bounded Wildcard: The
printListOfNumbers
method can accept a list of any type that extendsNumber
. - Lower Bounded Wildcard: The
addIntegers
method can accept a list of any type that is a superclass ofInteger
.
- Unbounded Wildcard: The
Summary
Java Generics provide a way to define classes, interfaces, and methods with type parameters, offering compile-time type safety and eliminating the need for type casting. Key concepts include:
- Generic Classes and Methods: Define classes and methods with type parameters.
- Type Safety: Generic type information is erased at runtime.
- Bounded Type Parameters: Restrict the types that can be used as type arguments.
- Wildcards: Represent unknown types with unbounded, upper bounded, and lower bounded wildcards.
- Type Inference: Java can infer type parameters, making the code more concise.
Generics are a powerful feature that can make your code more flexible and type-safe. Understanding and using them effectively can greatly improve the quality of your Java programs.
Post a Comment