← Back to Advanced Java Course | Chapter 1: Generics | Lesson 2 of 4

Java Generic Classes

A generic class is a blueprint with a blank for the type it holds, like a lunchbox that can be made to hold sandwiches or fruit. You fill in the type when you use it.
Syntax
java
class ClassName<T> {
    private T field;
    public void setField(T value) { this.field = value; }
    public T getField() { return field; }
}

Defining a Generic Class

To define a generic class, add angle brackets containing a type parameter, such as <T>, right after your class name. You can then use that parameter to define fields, constructors, and method signatures anywhere inside the class body.

Example: Defining a Generic Class

java
// Define the class Main
public class Main {
	// Define the class Box
	static class Box<T> {
		private T content;
		Box(T content) { this.content = content; }
		T getContent() { return content; }
	}
	// Program entry point: the JVM starts running here
	public static void main(String[] args) {
		// Create a new Box object and store it in intBox
		Box<Integer> intBox = new Box<>(99);
		// Print a line to the console
		System.out.println(intBox.getContent());
	}
}

Multiple Type Parameters

Generic classes can define multiple type parameters when a single placeholder isn't enough, separating them with commas inside the angle brackets, for example <K, V> as used by Map. Each parameter is substituted independently with a concrete type when the class is actually instantiated.

Example: Multiple Type Parameters

java
// Define the class Main
public class Main {
	// Define the class Pair
	static class Pair<K, V> {
		K key; V value;
		Pair(K key, V value) { this.key = key; this.value = value; }
	}
	// Program entry point: the JVM starts running here
	public static void main(String[] args) {
		// Create a new Pair object and store it in pair
		Pair<String, Integer> pair = new Pair<>("Age", 30);
		// Print a line to the console
		System.out.println(pair.key + " -> " + pair.value);
	}
}

Bounded Type Parameters

You can restrict the types allowed into your generic class using bounded type parameters, which is useful when your class needs to call methods that only exist on certain types. The extends keyword restricts the parameter to a class or interface and its subtypes, giving the compiler enough information to allow those method calls.

Example: Bounded Type Parameters

java
// Define the class Main
public class Main {
	// Define the class NumberBox
	static class NumberBox<T extends Number> {
		private T num;
		NumberBox(T num) { this.num = num; }
		double doubled() { return num.doubleValue() * 2; }
	}
	// Program entry point: the JVM starts running here
	public static void main(String[] args) {
		// Create a new NumberBox object and store it in box
		NumberBox<Integer> box = new NumberBox<>(10);
		// Print a line to the console
		System.out.println(box.doubled());
	}
}

Generic Interface Implementation

Classes can implement generic interfaces in one of two ways: you can either preserve the generic type parameter in your own class definition and stay generic yourself, or specify a concrete data type at implementation time and lock the interface down to just that type.

Example: Generic Interface Implementation

java
// Define the class Main
public class Main {
	// Define the interface Container
	interface Container<T> {
		T get();
	}
	// Define the class StringContainer
	static class StringContainer implements Container<String> {
		public String get() { return "Fixed to String"; }
	}
	// Program entry point: the JVM starts running here
	public static void main(String[] args) {
		// Create a new StringContainer object and store it in c
		Container<String> c = new StringContainer();
		// Print a line to the console
		System.out.println(c.get());
	}
}

Raw Types and Warnings

Using a generic class without specifying a type parameter creates what's called a 'raw type', a legacy behavior kept around purely for backward compatibility with pre-Generics code. While a raw type still compiles, it triggers compiler warnings and loses all of Generics' type-safety benefits, so it should be avoided in new code.

Example: Raw Types and Warnings

java
import java.util.ArrayList;
import java.util.List;
public class Main {
	public static void main(String[] args) {
		List rawList = new ArrayList(); // raw type -- compiles but triggers a warning
		rawList.add("no type safety");
		System.out.println(rawList.get(0));
	}
}
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Writing new T() or new T[10] inside a generic class, which does not compile because T is erased at runtime.
  2. Declaring a static field of type T, which is not allowed because static members are shared by all instantiations of the class.
  3. Using a raw Box without a type argument, which compiles with an unchecked warning and can fail later with a ClassCastException.
🔒

Chapter Quiz — Complete all 4 topics to unlock

0/4 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.