Generics in Java


Table of Contents

  1. Introduction
  2. What are Generics?
  3. Benefits of Using Generics
  4. Generic Types
    • Generic Classes
    • Generic Methods
  5. Bounded Type Parameters
  6. Wildcards in Generics
  7. Type Erasure
  8. Example Usage
  9. Conclusion

1. Introduction

Generics in Java are a powerful feature introduced in Java 5 that allow you to write more flexible and reusable code. With generics, you can write classes, interfaces, and methods that work with any type of object while providing compile-time type safety. Generics eliminate the need for casting and help ensure that you’re working with the correct types.

This module will explore the concept of generics, how they are implemented in Java, and when and why to use them. We’ll also discuss bounded type parameters, wildcards, and how generics are used in common Java collections.


2. What are Generics?

Generics enable classes, interfaces, and methods to operate on objects of different types while providing compile-time type checking. A generic class or method can work with any object type, but the type is specified when the class or method is instantiated.

For example, instead of creating separate classes for handling different types of data, such as String or Integer, you can create a single generic class that works with any type, and the type is determined at runtime.

Syntax of Generics

The general syntax of generics in Java is:

class ClassName<T> {
T value;
public ClassName(T value) {
this.value = value;
}
}

Here, T is the placeholder for the type, and it can represent any type of object.


3. Benefits of Using Generics

Generics provide several benefits:

  1. Type Safety: Generics enforce compile-time type safety, preventing you from accidentally mixing incompatible types.
  2. Code Reusability: You can create classes, interfaces, and methods that work with any type, making your code more reusable.
  3. Avoiding Type Casting: Generics eliminate the need for explicit type casting, reducing errors and improving code readability.
  4. Improved Maintainability: With type safety and reusable components, the code is easier to maintain and extend.

4. Generic Types

Generics can be applied to both classes and methods. Let’s take a look at each type:

Generic Classes

A generic class is a class that can operate on objects of various types, specified at the time the object is created. For example:

// A simple generic class
class Box<T> {
private T value;

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

public T getValue() {
return value;
}

public void setValue(T value) {
this.value = value;
}
}

Here, T represents a type that will be defined when the Box class is instantiated.

Example Usage:

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

System.out.println(stringBox.getValue());
System.out.println(intBox.getValue());
}
}

Output:

Hello
123

Generic Methods

You can also define generic methods within regular classes or interfaces. A generic method can be defined with its own type parameter, separate from the class’s type parameter.

public class GenericMethodExample {

// A generic method
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}

public static void main(String[] args) {
Integer[] intArray = {1, 2, 3};
String[] strArray = {"Java", "Generics", "Example"};

// Calling the generic method
printArray(intArray);
printArray(strArray);
}
}

Output:

1
2
3
Java
Generics
Example

In this example, the method printArray() is able to handle arrays of any type (Integer[], String[], etc.) by defining the generic type T in the method signature.


5. Bounded Type Parameters

Sometimes, you may want to limit the types that can be used with generics. This can be achieved by bounded type parameters.

Upper Bounded Wildcards

Upper bounded wildcards specify that the type must be a subtype of a specific class. This is done using the extends keyword.

public class UpperBoundedExample {

// Upper bounded wildcard
public static void printNumbers(List<? extends Number> list) {
for (Number num : list) {
System.out.println(num);
}
}

public static void main(String[] args) {
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);

printNumbers(intList); // Works with Integer (subtype of Number)
printNumbers(doubleList); // Works with Double (subtype of Number)
}
}

Output:

1
2
3
1.1
2.2
3.3

Lower Bounded Wildcards

In some cases, you may need to specify that a type must be a supertype of a particular class. This is achieved with lower bounded wildcards, using the super keyword.

public class LowerBoundedExample {

// Lower bounded wildcard
public static void addNumbers(List<? super Integer> list) {
list.add(10); // Works because Integer is a subtype of Number
}

public static void main(String[] args) {
List<Number> numberList = new ArrayList<>();
addNumbers(numberList); // Works with Number or any superclass of Integer
}
}

6. Wildcards in Generics

Wildcards are used to represent unknown types and are most often used when you are dealing with methods that can accept different types of arguments, but you don’t need to know the exact type.

  • ? extends T: Wildcard that matches any type that is a subtype of T.
  • ? super T: Wildcard that matches any type that is a supertype of T.
  • ?: Represents any type.

7. Type Erasure

Java uses a feature called type erasure to implement generics. During compile time, the compiler works with the generic types, but at runtime, the type information is erased. This means that at runtime, all type parameters are replaced by their raw types.

For instance, in the case of Box<T>, after type erasure, it becomes Box at runtime, and the type parameter T is not available.


8. Example Usage

Here’s an example that uses generics, bounded wildcards, and type parameters:

import java.util.*;

public class GenericsExample {

public static <T> void printList(List<T> list) {
for (T element : list) {
System.out.println(element);
}
}

public static void main(String[] args) {
List<Integer> intList = Arrays.asList(1, 2, 3);
List<String> strList = Arrays.asList("Java", "Generics", "Example");

printList(intList); // Prints Integer list
printList(strList); // Prints String list
}
}

9. Conclusion

Generics are a powerful feature in Java that allow for type-safe code that can work with different types of objects. Using generics helps you avoid type-casting, enhances code reuse, and ensures type safety at compile-time. It’s important to understand the different ways to work with generics, including generic classes, generic methods, and bounded wildcards, as well as how Java handles type erasure.

Generics are widely used in Java Collections Framework, ensuring that you can work with collections in a more flexible and error-free manner. Understanding how to use generics efficiently can significantly improve your ability to write robust and maintainable code.