Hi! I'm Remsey, your Java instructor. Ready to master Java together?
Prefer to learn by listening? Hit play below and watch the audio come to life!
They let you write code that works with any type, safely.
ArrayList list = new ArrayList(); // no type safety
list.add("hello");
list.add(123); // π¬ might cause errors later
ArrayList<String> list = new ArrayList<>();
list.add("hello"); // β
list.add(123); // β compile error
public class Box<T> {
T value;
public void set(T val) { value = val; }
public T get() { return value; }
}
Use it:
Box<String> b = new Box<>();
b.set("Java");
System.out.println(b.get());
public static <T> void printItem(T item) {
System.out.println(item);
}
Use it:
printItem("Hello");
printItem(42);
<T> = type placeholder
Today we're diving into Generics β one of Java's most powerful features that makes your code safer, cleaner, and more reusable. π―
Imagine you have a magic box. You can put anything in it β apples, books, or even unicorns. π¦ But what if you want to make sure only apples go in? That's where Generics come in!
Generics let you write type-safe code that works with different types while catching errors at compile-time instead of runtime.
Generics allow you to write classes, interfaces, and methods that work with any type, while still maintaining type safety. Think of them as blueprints with placeholders.
Let's start with a simple example β a Box that can hold anything:
public class Box<T> {
private T item;
public void put(T item) {
this.item = item;
}
public T get() {
return item;
}
}
The <T> is a type parameter. It's like saying "T can be any type you
want!" When you use the Box, you specify what type T should be:
public class Main {
public static void main(String[] args) {
// A box that holds Strings
Box<String> stringBox = new Box<>();
stringBox.put("Hello Generics!");
System.out.println(stringBox.get());
// A box that holds Integers
Box<Integer> intBox = new Box<>();
intBox.put(42);
System.out.println(intBox.get());
// Type safety! This won't compile:
// stringBox.put(123); β Error!
}
}
Before Generics (pre-Java 5), you had to use Object and cast everywhere. It was messy and error-prone! π±
public class OldBox {
private Object item;
public void put(Object item) {
this.item = item;
}
public Object get() {
return item;
}
}
// Usage - lots of casting!
OldBox box = new OldBox();
box.put("Hello");
String text = (String) box.get(); // Manual cast π’
box.put(123);
String oops = (String) box.get(); // Runtime error! π₯
Box<String> box = new Box<>();
box.put("Hello");
String text = box.get(); // No cast needed! π
// box.put(123); β Compile error - caught early!
You can also create generic methods! They're perfect for utility functions that work with any type.
public class ArrayUtils {
// 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();
}
// Generic method to swap elements
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void main(String[] args) {
Integer[] numbers = {1, 2, 3, 4, 5};
String[] words = {"Java", "is", "awesome"};
printArray(numbers); // Works with Integer[]
printArray(words); // Works with String[]
swap(numbers, 0, 4);
printArray(numbers); // 5 2 3 4 1
}
}
Sometimes you want to restrict what types can be used. You can do this with bounds!
public class NumberBox<T extends Number> {
private T number;
public NumberBox(T number) {
this.number = number;
}
public double getDoubleValue() {
return number.doubleValue(); // Works because T extends Number
}
public static void main(String[] args) {
NumberBox<Integer> intBox = new NumberBox<>(42);
NumberBox<Double> doubleBox = new NumberBox<>(3.14);
System.out.println(intBox.getDoubleValue()); // 42.0
System.out.println(doubleBox.getDoubleValue()); // 3.14
// NumberBox<String> won't compile! β
Type safety
}
}
Let's create a generic Pair class that holds two values of potentially different types:
public 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 static void main(String[] args) {
// Name and Age
Pair<String, Integer> person = new Pair<>("Alice", 25);
System.out.println(person.getKey() + " is " + person.getValue() + " years old");
// Country and Capital
Pair<String, String> country = new Pair<>("Netherlands", "Amsterdam");
System.out.println(country.getKey() + " β " + country.getValue());
// Coordinates
Pair<Double, Double> coords = new Pair<>(52.3676, 4.9041);
System.out.println("π (" + coords.getKey() + ", " + coords.getValue() + ")");
}
}
Create a generic Container class that can hold a list of items and has methods
to add items and get the count. Try it with different types!
import java.util.ArrayList;
import java.util.List;
public class Container<T> {
private List<T> items;
public Container() {
items = new ArrayList<>();
}
// TODO: Add method to add an item
// TODO: Add method to get count
// TODO: Add method to display all items
public static void main(String[] args) {
// Test with Strings
Container<String> fruits = new Container<>();
// fruits.add("Apple");
// fruits.add("Banana");
// Test with Integers
Container<Integer> numbers = new Container<>();
// numbers.add(1);
// numbers.add(2);
}
}
Test your understanding of Generics with these exercises!
Task: Create a generic Stack<T> class
with methods push(T item), pop(), and isEmpty(). Use an
ArrayList internally.
ArrayList<T> to store itemsimport java.util.ArrayList;
public class Stack<T> {
private ArrayList<T> items;
public Stack() {
items = new ArrayList<>();
}
public void push(T item) {
items.add(item);
System.out.println("π₯ Pushed: " + item);
}
public T pop() {
if (isEmpty()) {
System.out.println("β Stack is empty!");
return null;
}
T item = items.remove(items.size() - 1);
System.out.println("π€ Popped: " + item);
return item;
}
public boolean isEmpty() {
return items.isEmpty();
}
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
stack.push("First");
stack.push("Second");
stack.push("Third");
stack.pop();
stack.pop();
}
}
Task: Create generic methods findMin and
findMax that work with any Comparable type. Test with Integers and Strings.
<T extends Comparable<T>>compareTo()public class MinMax {
public static <T extends Comparable<T>> T findMin(T[] array) {
if (array == null || array.length == 0) {
return null;
}
T min = array[0];
for (T item : array) {
if (item.compareTo(min) < 0) {
min = item;
}
}
return min;
}
public static <T extends Comparable<T>> T findMax(T[] array) {
if (array == null || array.length == 0) {
return null;
}
T max = array[0];
for (T item : array) {
if (item.compareTo(max) > 0) {
max = item;
}
}
return max;
}
public static void main(String[] args) {
Integer[] numbers = {5, 2, 9, 1, 7};
System.out.println("Min: " + findMin(numbers));
System.out.println("Max: " + findMax(numbers));
String[] words = {"zebra", "apple", "mango"};
System.out.println("Min: " + findMin(words));
System.out.println("Max: " + findMax(words));
}
}
Task: Create a generic Cache<K, V>
class that stores key-value pairs using a HashMap. Add methods to put, get, and check if a key
exists.
HashMap<K, V> internallyput(K key, V value), get(K key),
contains(K key)
import java.util.HashMap;
public class Cache<K, V> {
private HashMap<K, V> cache;
public Cache() {
cache = new HashMap<>();
}
public void put(K key, V value) {
cache.put(key, value);
System.out.println("πΎ Cached: " + key + " β " + value);
}
public V get(K key) {
if (contains(key)) {
System.out.println("β
Cache hit: " + key);
return cache.get(key);
}
System.out.println("β Cache miss: " + key);
return null;
}
public boolean contains(K key) {
return cache.containsKey(key);
}
public static void main(String[] args) {
Cache<String, Integer> ageCache = new Cache<>();
ageCache.put("Alice", 25);
ageCache.put("Bob", 30);
System.out.println("\nRetrieving values:");
ageCache.get("Alice");
ageCache.get("Charlie");
}
}
π‘ Pro Tip: Generics are everywhere in Java! ArrayList, HashMap, and most collections use Generics. Understanding them is key to writing modern, type-safe Java code! π