Generics in Java

35
0

Generics in Java

Generics in Java allow classes, interfaces, and methods to work with different data types while maintaining compile-time type safety.

Generics were introduced in Java 5. They use type parameters such as <T> to represent a data type that is supplied later.

For example:

ArrayList<String> names = new ArrayList<>();

Here, the list can only store String values.

Why Are Generics Used?

Generics provide the following benefits:

  • Detect type errors at compile time

  • Reduce the risk of ClassCastException

  • Remove unnecessary type casting

  • Allow the same code to work with different data types

  • Improve code readability and reusability

Collections Without and With Generics

Without generics, a collection can store values of different types. This may cause type-casting errors during execution.

With generics, the required element type is specified when the collection is created.

Generic Collection Example

import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
// Create a list that can only store String values
ArrayList<String> messages = new ArrayList<>();
// Add a valid String value
messages.add("Hello Generics");
// Adding an integer would cause a compile-time error
// messages.add(100);
// No explicit type casting is required
String message = messages.get(0);
// Display the stored value
System.out.println(message); // Output :- Hello Generics
}
}

Because the list is declared as ArrayList<String>, Java prevents other data types from being added.

Generics in Java

Generics in Java

Generic Class in Java

A generic class is a class that can work with different data types without rewriting the class for each type.

Syntax

class ClassName<T> {
// T represents a data type
}

T is a type parameter. Its actual type is provided while creating an object.

Generic Class Example

// Generic class that can store any reference type
class Box<T> {
// Value has the type supplied through T
private T value;
// Store a value in the box
void set(T value) {
this.value = value;
}
// Return the stored value
T get() {
return value;
}
}
public class Main {
public static void main(String[] args) {
// Create a Box that stores Integer values
Box<Integer> numberBox = new Box<>();
numberBox.set(100);
// Retrieve the Integer value
System.out.println(numberBox.get()); // Output :- 100
// Create another Box that stores String values
Box<String> textBox = new Box<>();
textBox.set("Java Generics");
// Retrieve the String value
System.out.println(textBox.get()); // Output :- Java Generics
}
}

The same Box<T> class works with both Integer and String, providing code reusability without losing type safety.

Generic class in Java

Generic class in Java

Generic Method in Java

A generic method declares its own type parameter and can work with values of different types.

The type parameter is written before the method’s return type.

Syntax

<T> void methodName(T value) {
// Method body
}

Generic Method Example

// Class containing a generic method
class Printer {
// T is declared specifically for this method
<T> void print(T data) {
System.out.println("Data: " + data);
}
}
public class Main {
public static void main(String[] args) {
// Create the Printer object
Printer printer = new Printer();
// Use the same method with an Integer
printer.print(100); // Output :- Data: 100
// Use the same method with a String
printer.print("Generics"); // Output :- Data: Generics
// Use the same method with a Double
printer.print(3.14); // Output :- Data: 3.14
}
}

A generic method reduces the need to create multiple overloaded methods for different data types.

Generic method in Java

Generic method in Java

Bounded Type Parameters

A bounded type parameter restricts the types that can be used with a generic class or method.

For example, the following syntax accepts Number and its subclasses:

<T extends Number>

This allows types such as Integer, Double, and Float, but not String.

Bounded Generic Example

// T is restricted to Number and its subclasses
class NumericBox<T extends Number> {
// Store a numeric value
private T number;
// Initialize the numeric value
NumericBox(T number) {
this.number = number;
}
// Calculate and return its square
double square() {
double value = number.doubleValue();
return value * value;
}
}
public class Main {
public static void main(String[] args) {
// Integer is allowed because it extends Number
NumericBox<Integer> integerBox = new NumericBox<>(10);
System.out.println(integerBox.square()); // Output :- 100.0
// Double is also allowed
NumericBox<Double> doubleBox = new NumericBox<>(5.5);
System.out.println(doubleBox.square()); // Output :- 30.25
// String is not a subclass of Number
// NumericBox<String> textBox = new NumericBox<>("Hello");
}
}
Bounded type parameters

Bounded type parameters

Wildcards in Java Generics

The wildcard symbol ? represents an unknown generic type. It provides flexibility when a method needs to accept different generic types.

The main wildcard forms are:

  • <?>: Accepts an unknown type

  • <? extends T>: Accepts T or its subclasses

  • <? super T>: Accepts T or its superclasses

Upper-bounded wildcards are commonly used when reading values, while lower-bounded wildcards are useful when adding values.

Generics vs Raw Types

Generics

Raw Types

Specify the required data type

Do not specify a data type

Provide compile-time type checking

Lose compile-time type safety

Usually do not require casting

May require explicit casting

Reduce type-related runtime errors

Can produce unchecked warnings and errors

Raw types should generally be avoided in new Java code.

Summary

Generics in Java provide type safety and code reusability by allowing classes, interfaces, and methods to work with different data types.

Generic classes and methods reduce duplicated code, bounded types restrict accepted values, and wildcards provide flexibility when working with unknown generic types.

CS Core

Read Similar Blogs

Comments0