Skip to main content

9.3 Working with Generics

Overview of Generics​

Generics in Java allow developers to specify types when defining classes, interfaces, and methods. Generics enhance code safety by enabling type checks at compile-time, reducing runtime errors. They also help in creating reusable and type-safe collections.

Key Benefits of Generics​

  • Type Safety: Prevents ClassCastException by enforcing a specific type at compile-time.
  • Code Reusability: Enables creating methods and classes that work with various types.
  • Improved Readability: Enhances the clarity of the code by specifying data types.

Using Generics in Collections​

Java Collections Framework is one of the most common applications of generics. Collections like List, Set, and Map use generics to specify the type of objects they hold.

Example: Generic List with Type-Safe Operations​

import java.util.ArrayList;
import java.util.List;

public class GenericsExample {
public static void main(String[] args) {
// Creating a List with Generics
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");

// This line would cause a compile-time error, enforcing type safety
// names.add(10);

// Accessing elements without casting
for (String name : names) {
System.out.println(name);
}
}
}

In this example, a List is created with a generic type <String>. Trying to add an element of a different type (e.g., Integer) would cause a compile-time error.


Generic Classes​

A generic class defines one or more type parameters, allowing it to be used with different types. You specify the generic type parameter when instantiating the class.

Example: Creating a Generic Class​

public class Box<T> {
private T content;

public Box(T content) {
this.content = content;
}

public T getContent() {
return content;
}

public void setContent(T content) {
this.content = content;
}
}

public class Main {
public static void main(String[] args) {
Box<String> stringBox = new Box<>("Hello");
Box<Integer> integerBox = new Box<>(123);

System.out.println("String Box: " + stringBox.getContent()); // Output: Hello
System.out.println("Integer Box: " + integerBox.getContent()); // Output: 123
}
}

In this example, we define a generic class Box<T>, where T represents the type. We can use Box with different types (String, Integer, etc.), making it versatile and type-safe.


Generic Methods​

A generic method allows specifying generic parameters for individual methods, independent of the class's generic type.

Example: Generic Method to Print Arrays​

public class GenericsMethodExample {
// Generic method to print any type of array
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}

public static void main(String[] args) {
Integer[] intArray = {1, 2, 3, 4, 5};
String[] stringArray = {"A", "B", "C"};

printArray(intArray); // Output: 1 2 3 4 5
printArray(stringArray); // Output: A B C
}
}

In this example, <T> is a type parameter for the printArray method. This method can accept arrays of any type, making it reusable for different data types.


Bounded Type Parameters​

Bounded type parameters restrict the types that can be used with generics, allowing only specific classes or interfaces as the type argument.

Example: Bounded Type with Comparable​

public class BoundedExample {
// Bounded type parameter with Comparable
public static <T extends Comparable<T>> T findMax(T x, T y) {
return x.compareTo(y) > 0 ? x : y;
}

public static void main(String[] args) {
System.out.println(findMax(10, 20)); // Output: 20
System.out.println(findMax("Apple", "Banana")); // Output: Banana
}
}

In this example, the <T extends Comparable<T>> constraint ensures that only types implementing the Comparable interface can be used with the findMax method.


Summary​

  • Generics enable type safety, code reusability, and improve readability in Java.
  • They are commonly used with the Java Collections Framework to ensure collections hold specific types.
  • Generic classes and generic methods provide flexibility in defining reusable code components.
  • Bounded type parameters restrict the types that can be used with generics, allowing for specific interfaces or classes only.

Generics are a powerful feature in Java that promotes clean, maintainable, and reliable code, especially when working with collections and reusable components.