CodeToConcept
About

java

java Tutorials

Introduction
Java Variables
Java Data Types
Java Type Casting
Java Operators
Java Input/Output
Java Class & Object
java Method
Java Constructor
Java this keyword
Java super keyword
Java Method Overloading and Overriding
Java Interface
Java Abstract Class
Java Memory Management
Design Patterns
JDBC (Database Connectivity)
Java IDEs

java Tutorials

Introduction
Java Variables
Java Data Types
Java Type Casting
Java Operators
Java Input/Output
Java Class & Object
java Method
Java Constructor
Java this keyword
Java super keyword
Java Method Overloading and Overriding
Java Interface
Java Abstract Class
Java Memory Management
Design Patterns
JDBC (Database Connectivity)
Java IDEs

Sample Question

Think it through, then check the tutorial.

CodeToConcept

Quickly learn through simple, structured, and practical coding tutorials. Build your confidence from step zero to real-world experience.

© 2026 CodeToConcept. All rights reserved.

Quick Links

AboutTutorials

Tutorials

JavaHTMLCSS

Support

Privacy PolicyTerms of ServiceContact
GitHubTwitterYouTube

Java Generics

Run each complete program separately. Its required helper classes and imports are included in the same code block. Short syntax fragments explain individual operations.

Generics let a class or method express which types it works with before the program runs. A List<String>, for example, asks the compiler to check String use at the places where elements enter and leave the list. Bounds and wildcards describe more flexible relationships without abandoning those checks.

For example:

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

Here, String specifies that the list should contain only String values.

Generics were introduced in Java 5 and are heavily used throughout the Java Collections Framework.

What Are Generics in Java?

Generics allow a type to be specified as a parameter.

Instead of writing code that works only with one specific type, we can write code that works with different types while still maintaining type safety.

Without Generics

List list = new ArrayList();
 
list.add("Java");
list.add(100);
 
String value = (String) list.get(1);

This can cause a runtime ClassCastException because the second element is an Integer, not a String.

With Generics

List<String> list = new ArrayList<>();
 
list.add("Java");
list.add(100);   // Compile-time error

The compiler prevents an invalid value from being added.

Why Do We Need Generics?

Generics provide several important benefits.

1. Type Safety

The compiler can detect incorrect types.

2. No Explicit Casting

Generics often eliminate the need for manual type casting.

3. Code Reusability

The same generic class or method can work with different types.

4. Better Readability

The expected type is clearly visible in the code.

5. Compile-Time Error Detection

Many type-related problems are detected during compilation instead of at runtime.

Simple Example

import java.util.ArrayList;
import java.util.List;
 
public class HelloWorld {
 
    public static void main(String[] args) {
 
        List<String> names = new ArrayList<>();
 
        names.add("John");
        names.add("David");
        names.add("Robert");
 
        for (String name : names) {
            System.out.println(name);
        }
    }
}

Output

John
David
Robert

Because the list is declared as:

List<String>

only String values can be added.

Generic Type Syntax

A generic type is written using angle brackets:

ClassName<Type>

Examples:

List<String>
List<Integer>
List<Double>
Map<String, Integer>
Set<String>

The type inside < > is called a type argument.

Generic Type Parameter

When defining a generic class or method, we use a type parameter.

Example

class Box<T> {
 
}

Here:

T

is a type parameter.

When creating an object:

Box<String>

String becomes the actual type argument.

Common Generic Type Parameter Names

Java does not require a specific name, but certain conventions are commonly followed.

Type ParameterCommon Meaning
TType
EElement
KKey
VValue
NNumber
SState
RResult

For example:

Map<K, V>

means a map containing a key type and a value type.

Generic Class

A class can be made generic by declaring a type parameter after the class name.

Syntax

class ClassName<T> {
 
}

Example

class Box<T> {
 
    private T value;
 
    public void setValue(T value) {
        this.value = value;
    }
 
    public T getValue() {
        return value;
    }
}

Now the same class can store different types.

Using a Generic Class with String

class Box<T> {
 
    private T value;
 
    public void setValue(T value) {
        this.value = value;
    }
 
    public T getValue() {
        return value;
    }
}
 
public class HelloWorld {
 
    public static void main(String[] args) {
 
        Box<String> box = new Box<>();
 
        box.setValue("Java");
 
        String value = box.getValue();
 
        System.out.println(value);
    }
}

Output

Java

Using a Generic Class with Integer

The same Box class can be used with Integer.

class Box<T> {
 
    private T value;
 
    public void setValue(T value) {
        this.value = value;
    }
 
    public T getValue() {
        return value;
    }
}
 
public class HelloWorld {
 
    public static void main(String[] args) {
 
        Box<Integer> box = new Box<>();
 
        box.setValue(100);
 
        Integer value = box.getValue();
 
        System.out.println(value);
    }
}

Output

100

The class did not need to be rewritten.

Generic Class with Multiple Type Parameters

A generic class can have more than one type parameter.

Example

class Pair<K, V> {
 
    private K key;
    private V value;
 
    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }
 
    public K getKey() {
        return key;
    }
 
    public V getValue() {
        return value;
    }
}

Usage:

class Pair<K, V> {
 
    private K key;
    private V value;
 
    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }
 
    public K getKey() {
        return key;
    }
 
    public V getValue() {
        return value;
    }
}
 
public class HelloWorld {
 
    public static void main(String[] args) {
 
        Pair<Integer, String> pair =
                new Pair<>(101, "Java");
 
        System.out.println(pair.getKey());
        System.out.println(pair.getValue());
    }
}

Output

101
Java

Here:

K → Integer
V → String

Generic Interface

Interfaces can also be generic.

Example

interface Container<T> {
 
    void add(T value);
 
    T get();
}

A class can implement it with a specific type:

class StringContainer implements Container<String> {
 
    private String value;
 
    @Override
    public void add(String value) {
        this.value = value;
    }
 
    @Override
    public String get() {
        return value;
    }
}

Usage:

interface Container<T> {
 
    void add(T value);
 
    T get();
}
 
class StringContainer implements Container<String> {
 
    private String value;
 
    @Override
    public void add(String value) {
        this.value = value;
    }
 
    @Override
    public String get() {
        return value;
    }
}
 
public class HelloWorld {
 
    public static void main(String[] args) {
 
        StringContainer container =
                new StringContainer();
 
        container.add("Java");
 
        System.out.println(container.get());
    }
}

Output

Java

Generic Methods

A method can have its own type parameter.

Syntax

public <T> void method(T value) {
}

Notice that <T> appears before the return type.

Example

class Utility {
 
    public <T> void print(T value) {
 
        System.out.println(value);
    }
}

Usage:

class Utility {
 
    public <T> void print(T value) {
 
        System.out.println(value);
    }
}
 
public class HelloWorld {
 
    public static void main(String[] args) {
 
        Utility utility = new Utility();
 
        utility.print("Java");
        utility.print(100);
        utility.print(25.5);
    }
}

Output

Java
100
25.5

The same method works with different types.

Generic Static Method

A generic method can also be static.

Example

class Utility {
 
    public static <T> void print(T value) {
 
        System.out.println(value);
    }
}

Usage:

class Utility {
 
    public static <T> void print(T value) {
 
        System.out.println(value);
    }
}
 
public class HelloWorld {
 
    public static void main(String[] args) {
 
        Utility.print("Java");
        Utility.print(100);
    }
}

Output

Java
100

The type parameter belongs to the method, not to the class.

Generic Method Returning a Value

A generic method can return a generic type.

class Utility {
 
    public static <T> T getValue(T value) {
 
        return value;
    }
}

Usage:

class Utility {
 
    public static <T> T getValue(T value) {
 
        return value;
    }
}
 
public class HelloWorld {
 
    public static void main(String[] args) {
 
        String name = Utility.getValue("Java");
 
        Integer number = Utility.getValue(100);
 
        System.out.println(name);
        System.out.println(number);
    }
}

Output

Java
100

Generic Method with Multiple Type Parameters

A method can have multiple type parameters.

class Utility {
 
    public static <K, V> void printPair(K key, V value) {
 
        System.out.println("Key: " + key);
        System.out.println("Value: " + value);
    }
}

Usage:

class Utility {
 
    public static <K, V> void printPair(K key, V value) {
 
        System.out.println("Key: " + key);
        System.out.println("Value: " + value);
    }
}
 
public class HelloWorld {
 
    public static void main(String[] args) {
 
        Utility.printPair(101, "Java");
    }
}

Output

Key: 101
Value: Java

Generic Constructor

Constructors can also use generic types when they belong to a generic class.

Example

class Box<T> {
 
    private T value;
 
    Box(T value) {
        this.value = value;
    }
 
    T getValue() {
        return value;
    }
}

Usage:

Box<String> box = new Box<>("Java");
 
System.out.println(box.getValue());

Output

Java

Type Inference with the Diamond Operator

Java provides the diamond operator:

<>

It allows the compiler to infer the generic type.

Instead of:

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

we can write:

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

The compiler infers String.

Generics with Collections

Generics are widely used with the Collections Framework.

ArrayList

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

Set

Set<Integer> numbers = new HashSet<>();

Map

Map<String, Integer> marks = new HashMap<>();

Queue

Queue<String> queue = new LinkedList<>();

Generics make these collections type-safe.

Generic Map Example

import java.util.HashMap;
import java.util.Map;
 
public class HelloWorld {
 
    public static void main(String[] args) {
 
        Map<Integer, String> students = new HashMap<>();
 
        students.put(101, "John");
        students.put(102, "David");
 
        String name = students.get(101);
 
        System.out.println(name);
    }
}

Output

John

Here:

K → Integer
V → String

Bounded Type Parameters

Sometimes we don't want a generic type to accept every possible type.

For example, we may want a method to work only with numbers.

We can use a bounded type parameter.

Syntax

<T extends Number>

Example

class Calculator {
 
    public static <T extends Number> double square(T number) {
 
        double value = number.doubleValue();
 
        return value * value;
    }
}

Usage:

class Calculator {
 
    public static <T extends Number> double square(T number) {
 
        double value = number.doubleValue();
 
        return value * value;
    }
}
 
public class HelloWorld {
 
    public static void main(String[] args) {
 
        System.out.println(
                Calculator.square(10)
        );
 
        System.out.println(
                Calculator.square(5.5)
        );
    }
}

Output

100.0
30.25

Here T must be Number or a subclass of Number.

Upper Bounded Type

The syntax:

<T extends SomeType>

means:

T must be SomeType or a subclass/implementation compatible with the bound.

For classes:

<T extends Number>

allows types such as:

Integer
Double
Float
Long
Short
Byte

because these wrapper classes extend Number.

Multiple Bounds

A type parameter can have multiple bounds.

Example

<T extends Number & Comparable<T>>

The class bound, if present, must appear first, followed by interface bounds.

Example

class Calculator {
 
    public static <T extends Number & Comparable<T>>
    T process(T value) {
 
        return value;
    }
}

Multiple bounds are useful when a generic type needs to satisfy more than one requirement.

Wildcards

A wildcard is represented by:

?

It means an unknown type.

Example

List<?> list;

This means:

A list of some unknown type.

It could be:

List<String>
List<Integer>
List<Double>

and so on.

Unbounded Wildcard

The simplest wildcard is:

?

Example

public static void printList(List<?> list) {
 
    for (Object value : list) {
        System.out.println(value);
    }
}

Usage:

List<String> names =
        List.of("John", "David");
 
List<Integer> numbers =
        List.of(10, 20);
 
printList(names);
printList(numbers);

Output

John
David
10
20

The method can accept lists of different types.

Upper-Bounded Wildcard

An upper-bounded wildcard uses:

? extends Type

Example

List<? extends Number>

This represents a list whose element type is Number or a subtype of Number.

It can accept:

List<Integer>
List<Double>
List<Float>

Example

public static double sum(
        List<? extends Number> numbers) {
 
    double total = 0;
 
    for (Number number : numbers) {
        total += number.doubleValue();
    }
 
    return total;
}

Usage:

List<Integer> numbers =
        List.of(10, 20, 30);
 
System.out.println(sum(numbers));

Output

60.0

Lower-Bounded Wildcard

A lower-bounded wildcard uses:

? super Type

Example

List<? super Integer>

This means the list can have a type of:

Integer
Number
Object

Example

public static void addNumbers(
        List<? super Integer> list) {
 
    list.add(10);
    list.add(20);
}

Usage:

List<Number> numbers =
        new ArrayList<>();
 
addNumbers(numbers);
 
System.out.println(numbers);

Output

[10, 20]

extends vs super

One of the most important concepts in Java generics is understanding:

? extends T

and:

? super T

? extends T

Used when you mainly want to read values as T.

Example

List<? extends Number>

? super T

Used when you need to add T values.

Example

List<? super Integer>

A commonly used guideline is:

Producer Extends, Consumer Super (PECS)

If a generic structure produces values for you to read, extends is often appropriate.

If a generic structure consumes values that you want to add, super is often appropriate.

Example of PECS

Suppose we have:

public static double sum(
        List<? extends Number> numbers) {
 
    double total = 0;
 
    for (Number number : numbers) {
        total += number.doubleValue();
    }
 
    return total;
}

The list is a producer of Number values, so:

? extends Number

is appropriate.

For a method that adds integers:

public static void addValues(
        List<? super Integer> numbers) {
 
    numbers.add(10);
    numbers.add(20);
}

The list is consuming Integer values, so:

? super Integer

is appropriate.

Generic Class vs Wildcard

These two concepts are related but different.

Generic Class

class Box<T> {
 
    T value;
}

T represents a specific type parameter.

Wildcard

Box<?> box;

? represents an unknown type.

Generic Array

Creating arrays of a type parameter directly is not allowed in the normal form:

T[] array = new T[10];

This results in a compilation problem because Java does not allow direct creation of generic arrays like this.

Instead, common alternatives include:

Object[] array = new Object[10];

or using collections such as:

List<T>

For generic programming, collections are often more convenient than generic arrays.

Generic Exception Classes

Java does not allow a generic class to directly extend Throwable.

For example, this is not allowed:

class MyException<T> extends Exception {
}

This restriction exists because Java's exception handling and runtime type system do not support generic throwable types in this form.

Primitive Types and Generics

Generics work with reference types, not primitive types.

This is invalid:

List<int> numbers;

Instead, use the wrapper class:

List<Integer> numbers;

Similarly:

int     → Integer
double  → Double
float   → Float
long    → Long
boolean → Boolean
char    → Character
byte    → Byte
short   → Short

Java's autoboxing and unboxing make this convenient.

Example

List<Integer> numbers = new ArrayList<>();
 
numbers.add(10);

The primitive 10 is automatically boxed into an Integer.

Type Erasure

One of the most important internal concepts of Java generics is type erasure.

Java generics are primarily a compile-time type-safety feature.

Consider:

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

The compiler uses the generic information to enforce type safety.

At runtime, generic type arguments are generally erased.

Conceptually, the runtime representation is closer to:

List

rather than retaining String as the collection's runtime element type.

The compiler inserts appropriate casts where necessary.

Why Does Java Use Type Erasure?

Type erasure allowed Java to introduce generics while maintaining compatibility with much of the existing Java ecosystem and bytecode model.

For example:

List<String>

and:

List<Integer>

are not distinct runtime classes.

Both are instances of:

ArrayList

or another implementation of:

List

Generic Type Information at Runtime

Consider:

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

You cannot normally do:

if (names instanceof List<String>) {
}

This is not allowed because the generic type argument is not available in that form for runtime instanceof checking.

You can check:

if (names instanceof List<?>) {
}

because List<?> represents a list of an unknown type.

Raw Types

A raw type is a generic type used without specifying its type parameter.

Example

List list = new ArrayList();

This is a raw List.

Although it is allowed for compatibility with older Java code, it should generally be avoided in new code.

Prefer:

List<String> list =
        new ArrayList<>();

Raw types remove much of the compile-time type safety provided by generics.

Raw Type Example

List list = new ArrayList();
 
list.add("Java");
list.add(100);
 
String value = (String) list.get(1);

This can result in:

ClassCastException

because the retrieved object is an Integer.

With generics:

List<String> list =
        new ArrayList<>();
 
list.add("Java");

the compiler prevents:

list.add(100);

Generic Inheritance

Generic classes can participate in inheritance.

Example

class Box<T> {
 
    T value;
}
 
class StringBox extends Box<String> {
 
}

Here:

Box<T>
   ↓
Box<String>
   ↓
StringBox

The subclass fixes the generic type to String.

Generic Interface Implementation

Example

interface Repository<T> {
 
    void save(T value);
}

Implementation:

class UserRepository
        implements Repository<String> {
 
    @Override
    public void save(String value) {
 
        System.out.println(
                "Saving: " + value
        );
    }
}

Usage:

Repository<String> repository =
        new UserRepository();
 
repository.save("John");

Output

Saving: John

Generic Interface with Generic Implementation

Instead of fixing the type, the implementing class can remain generic.

interface Repository<T> {
 
    void save(T value);
}
 
class DatabaseRepository<T>
        implements Repository<T> {
 
    @Override
    public void save(T value) {
 
        System.out.println(
                "Saving: " + value
        );
    }
}

Now:

Repository<String> stringRepository =
        new DatabaseRepository<>();
 
Repository<Integer> integerRepository =
        new DatabaseRepository<>();

The same implementation works with different types.

Generic Methods vs Generic Classes

These are not the same.

Generic Class

class Box<T> {
 
    T value;
}

The type parameter belongs to the class.

Generic Method

class Utility {
 
    public <T> void print(T value) {
 
        System.out.println(value);
    }
}

The type parameter belongs only to the method.

A non-generic class can contain generic methods.

Complete Generic Utility Example

import java.util.List;
 
class Utility {
 
    public static <T> void printList(
            List<T> list) {
 
        for (T value : list) {
            System.out.println(value);
        }
    }
}
 
public class HelloWorld {
 
    public static void main(String[] args) {
 
        List<String> names =
                List.of("John", "David");
 
        List<Integer> numbers =
                List.of(10, 20, 30);
 
        Utility.printList(names);
 
        System.out.println("---");
 
        Utility.printList(numbers);
    }
}

Output

John
David
---
10
20
30

Benefits of Generics

Type Safety

Incorrect types are detected during compilation.

Reusability

One implementation can work with many types.

Reduced Casting

Generic code often eliminates explicit casts.

Better Readability

The expected type is visible directly in the code.

Better API Design

Generic classes and methods allow developers to create flexible and type-safe APIs.

Limitations of Generics

1. Cannot Use Primitive Types Directly

Use:

Integer

instead of:

int

2. Type Erasure

Generic type arguments are generally not available in the same way at runtime.

3. Cannot Directly Create Generic Arrays

new T[10]

is not allowed.

4. Cannot Use Generic Type Arguments with Certain Runtime Operations

For example:

obj instanceof List<String>

is not allowed.

5. Generic Type Parameters Cannot Be Used Everywhere

Java has specific restrictions around static members, arrays, exceptions, and runtime type checks.

Common Mistakes

Mistake 1: Using Raw Types

Avoid:

List list = new ArrayList();

Prefer:

List<String> list =
        new ArrayList<>();

Mistake 2: Using Primitive Types

Incorrect:

List<int> numbers;

Correct:

List<Integer> numbers;

Mistake 3: Confusing T and ?

Consider:

List<T>

and:

List<?>

T represents a type parameter that is part of the generic declaration.

? represents an unknown type.

Mistake 4: Assuming extends Means Only Classes

In generic bounds:

<T extends SomeType>

extends can also be used with interfaces.

For example:

<T extends Comparable<T>>

Here Comparable<T> is an interface.

Mistake 5: Adding Values to List<?>

Consider:

List<?> list;

You cannot generally add an arbitrary value:

list.add("Java");

because the actual element type is unknown.

You can safely add:

list.add(null);

because null is compatible with reference types.

Important Generic Syntax

Generic class

class Box<T> {
}

Multiple type parameters

class Pair<K, V> {
}

Generic method

<T> T method(T value) {
    return value;
}

Upper bound

<T extends Number>

Unbounded wildcard

List<?>

Upper-bounded wildcard

List<? extends Number>

Lower-bounded wildcard

List<? super Integer>

Quick Comparison

ConceptExampleMeaning
Type parameterTGeneric type declared by class/method
Type argumentStringActual type supplied
Generic classBox<T>Class works with a type
Generic method<T> T get()Method works with a type
Wildcard?Unknown type
Upper bound? extends NumberNumber or subtype
Lower bound? super IntegerInteger or supertype
Raw typeListGeneric type without type argument
Diamond operator<>Compiler infers type arguments

Generics Interview Questions

What are generics in Java?

Generics provide a way to write reusable and type-safe code by parameterizing types.

What is the benefit of generics?

The main benefits include type safety, code reuse, reduced casting, and better readability.

What is a type parameter?

A type parameter is a placeholder for a type.

Example

class Box<T>

Here T is the type parameter.

What is a type argument?

The actual type supplied to a generic type.

Example

Box<String>

Here String is the type argument.

What is a wildcard?

? represents an unknown type.

Example

List<?>

What is an upper-bounded wildcard?

? extends Number

It represents Number or one of its subtypes.

What is a lower-bounded wildcard?

? super Integer

It represents Integer or one of its supertypes.

What is type erasure?

Type erasure is the mechanism by which Java removes most generic type-argument information from the runtime representation while preserving compile-time type checking.

Can we use primitive types with generics?

No.

Use wrapper classes instead:

List<Integer>

rather than:

List<int>

What is PECS?

PECS stands for:

Producer Extends, Consumer Super.

It is a guideline for choosing between extends and super when working with wildcards.

Key Points

  • Generics provide compile-time type safety.
  • Generics make code reusable.
  • Generic types use type parameters such as T, E, K, and V.
  • A class can be generic.
  • An interface can be generic.
  • A method can be generic.
  • Multiple type parameters are supported.
  • Generic types work extensively with Java collections.
  • Primitive types cannot be used directly as generic type arguments.
  • Wrapper classes are used instead.
  • ? represents an unknown type.
  • ? extends T represents an upper-bounded wildcard.
  • ? super T represents a lower-bounded wildcard.
  • Bounded type parameters restrict the types that can be used.
  • Raw types should generally be avoided.
  • Java generics use type erasure.
  • The diamond operator <> allows type inference.
  • PECS means Producer Extends, Consumer Super.

Conclusion

Generics are one of the most important features of Java's type system. They allow developers to create code that is both reusable and type-safe.

The basic idea can be summarized as:

Generics
   ↓
Parameterize Types
   ↓
Compile-Time Type Safety
   ↓
Reusable Code
   ↓
Less Casting

Once the basic syntax is understood, the most important concepts to master are:

Generic Class
      ↓
Generic Method
      ↓
Bounded Type
      ↓
Wildcard
      ↓
? extends
      ↓
? super
      ↓
PECS
      ↓
Type Erasure

Generics are particularly important when working with the Java Collections Framework, APIs, libraries, and enterprise applications because they allow developers to define flexible APIs without sacrificing compile-time type safety.

See you in next page....🙂

← AnnotationsInner Classes →
← AnnotationsInner Classes →