java-generics

Master Java's generics system for writing type-safe, reusable code with compile-time type checking, generic classes, methods, wildcards, and type bounds.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "java-generics" with this command: npx skills add thebushidocollective/han/thebushidocollective-han-java-generics

Java Generics

Master Java's generics system for writing type-safe, reusable code with compile-time type checking, generic classes, methods, wildcards, and type bounds.

Introduction to Generics

Generics enable types to be parameters when defining classes, interfaces, and methods, providing compile-time type safety.

Basic generic class:

public class Box<T> { private T content;

public void set(T content) {
    this.content = content;
}

public T get() {
    return content;
}

public static void main(String[] args) {
    // Type-safe box for String
    Box&#x3C;String> stringBox = new Box&#x3C;>();
    stringBox.set("Hello");
    String value = stringBox.get(); // No casting needed

    // Type-safe box for Integer
    Box&#x3C;Integer> intBox = new Box&#x3C;>();
    intBox.set(42);
    Integer number = intBox.get();
}

}

Generic with multiple type parameters:

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) {
    Pair&#x3C;String, Integer> pair = new Pair&#x3C;>("age", 30);
    String key = pair.getKey();
    Integer value = pair.getValue();
}

}

Generic Methods

Generic methods can be defined independently of generic classes.

Basic generic method:

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

// Generic method with return type
public static &#x3C;T> T getFirst(T[] array) {
    if (array.length > 0) {
        return array[0];
    }
    return null;
}

public static void main(String[] args) {
    String[] strings = {"a", "b", "c"};
    Integer[] numbers = {1, 2, 3};

    printArray(strings);  // T inferred as String
    printArray(numbers);  // T inferred as Integer

    String first = getFirst(strings);
    Integer firstNum = getFirst(numbers);
}

}

Generic method with multiple type parameters:

public class MultiplTypeParams { public static <K, V> Map<K, V> createMap(K key, V value) { Map<K, V> map = new HashMap<>(); map.put(key, value); return map; }

public static &#x3C;T, R> R transform(T input, Function&#x3C;T, R> transformer) {
    return transformer.apply(input);
}

public static void main(String[] args) {
    Map&#x3C;String, Integer> map = createMap("count", 10);

    String result = transform(42, num -> "Number: " + num);
    // Result: "Number: 42"
}

}

Bounded Type Parameters

Type bounds restrict the types that can be used as type arguments.

Upper bounded type parameters:

public class UpperBound { // T must be Number or subclass of Number public static <T extends Number> double sum(List<T> numbers) { double total = 0; for (T num : numbers) { total += num.doubleValue(); } return total; }

// Multiple bounds
public static &#x3C;T extends Comparable&#x3C;T> &#x26; Serializable> T max(T a, T b) {
    return a.compareTo(b) > 0 ? a : b;
}

public static void main(String[] args) {
    List&#x3C;Integer> integers = List.of(1, 2, 3, 4, 5);
    double sum = sum(integers); // 15.0

    List&#x3C;Double> doubles = List.of(1.5, 2.5, 3.5);
    double doubleSum = sum(doubles); // 7.5

    String maxStr = max("apple", "banana"); // "banana"
}

}

Class with bounded type parameter:

public class NumberBox<T extends Number> { private T number;

public NumberBox(T number) {
    this.number = number;
}

public double doubleValue() {
    return number.doubleValue();
}

public boolean isZero() {
    return number.doubleValue() == 0.0;
}

public static void main(String[] args) {
    NumberBox&#x3C;Integer> intBox = new NumberBox&#x3C;>(42);
    NumberBox&#x3C;Double> doubleBox = new NumberBox&#x3C;>(3.14);

    // Compile error: String is not a Number
    // NumberBox&#x3C;String> stringBox = new NumberBox&#x3C;>("fail");
}

}

Wildcards

Wildcards provide flexibility when working with generic types.

Unbounded wildcard:

public class UnboundedWildcard { // Accept any List public static void printList(List<?> list) { for (Object elem : list) { System.out.println(elem); } }

public static int size(List&#x3C;?> list) {
    return list.size();
}

public static void main(String[] args) {
    List&#x3C;String> strings = List.of("a", "b", "c");
    List&#x3C;Integer> integers = List.of(1, 2, 3);

    printList(strings);
    printList(integers);

    System.out.println(size(strings));  // 3
    System.out.println(size(integers)); // 3
}

}

Upper bounded wildcard:

public class UpperBoundedWildcard { // Accept List of Number or any subclass public static double sum(List<? extends Number> numbers) { double total = 0; for (Number num : numbers) { total += num.doubleValue(); } return total; }

public static void main(String[] args) {
    List&#x3C;Integer> integers = List.of(1, 2, 3);
    List&#x3C;Double> doubles = List.of(1.5, 2.5);
    List&#x3C;Number> numbers = List.of(1, 2.5, 3);

    System.out.println(sum(integers)); // 6.0
    System.out.println(sum(doubles));  // 4.0
    System.out.println(sum(numbers));  // 6.5
}

}

Lower bounded wildcard:

public class LowerBoundedWildcard { // Accept List of Integer or any superclass public static void addIntegers(List<? super Integer> list) { for (int i = 1; i <= 5; i++) { list.add(i); } }

public static void main(String[] args) {
    List&#x3C;Integer> integers = new ArrayList&#x3C;>();
    addIntegers(integers);
    System.out.println(integers); // [1, 2, 3, 4, 5]

    List&#x3C;Number> numbers = new ArrayList&#x3C;>();
    addIntegers(numbers);
    System.out.println(numbers); // [1, 2, 3, 4, 5]

    List&#x3C;Object> objects = new ArrayList&#x3C;>();
    addIntegers(objects);
    System.out.println(objects); // [1, 2, 3, 4, 5]
}

}

PECS Principle

Producer Extends, Consumer Super - guideline for using wildcards.

PECS in action:

public class PECSExample { // Producer - reading from source (extends) public static <T> void copy( List<? extends T> source, List<? super T> destination ) { for (T item : source) { destination.add(item); } }

// Producer - extends for reading
public static double sumNumbers(List&#x3C;? extends Number> numbers) {
    double sum = 0;
    for (Number num : numbers) { // Reading (producing values)
        sum += num.doubleValue();
    }
    return sum;
}

// Consumer - super for writing
public static void addNumbers(List&#x3C;? super Integer> list) {
    for (int i = 1; i &#x3C;= 3; i++) {
        list.add(i); // Writing (consuming values)
    }
}

public static void main(String[] args) {
    List&#x3C;Integer> source = List.of(1, 2, 3);
    List&#x3C;Number> destination = new ArrayList&#x3C;>();

    copy(source, destination);
    System.out.println(destination); // [1, 2, 3]
}

}

Generic Interfaces

Interfaces can be generic, providing contracts for generic types.

Generic interface:

public interface Repository<T, ID> { T findById(ID id); List<T> findAll(); void save(T entity); void delete(ID id); }

public class UserRepository implements Repository<User, Long> { private Map<Long, User> storage = new HashMap<>();

@Override
public User findById(Long id) {
    return storage.get(id);
}

@Override
public List&#x3C;User> findAll() {
    return new ArrayList&#x3C;>(storage.values());
}

@Override
public void save(User user) {
    storage.put(user.getId(), user);
}

@Override
public void delete(Long id) {
    storage.remove(id);
}

}

class User { private Long id; private String name;

public User(Long id, String name) {
    this.id = id;
    this.name = name;
}

public Long getId() { return id; }
public String getName() { return name; }

}

Comparable and Comparator:

public class Person implements Comparable<Person> { private String name; private int age;

public Person(String name, int age) {
    this.name = name;
    this.age = age;
}

@Override
public int compareTo(Person other) {
    return this.name.compareTo(other.name);
}

public static void main(String[] args) {
    List&#x3C;Person> people = new ArrayList&#x3C;>();
    people.add(new Person("Alice", 30));
    people.add(new Person("Bob", 25));

    // Natural ordering (by name)
    Collections.sort(people);

    // Custom comparator (by age)
    Comparator&#x3C;Person> ageComparator =
        Comparator.comparingInt(p -> p.age);
    people.sort(ageComparator);
}

}

Type Erasure

Java generics use type erasure - generic type information is removed at runtime.

Understanding type erasure:

public class TypeErasure { public static void main(String[] args) { List<String> strings = new ArrayList<>(); List<Integer> integers = new ArrayList<>();

    // At runtime, both are just List
    System.out.println(strings.getClass() == integers.getClass());
    // true

    // Cannot check generic type at runtime
    // if (list instanceof List&#x3C;String>) {} // Compile error

    // Can only check raw type
    if (strings instanceof List) {
        System.out.println("Is a List");
    }
}

}

Consequences of type erasure:

public class ErasureConsequences<T> { // Cannot create instance of type parameter // T instance = new T(); // Compile error

// Cannot create array of parameterized type
// T[] array = new T[10]; // Compile error

// Cannot use instanceof with type parameter
public boolean isInstance(Object obj) {
    // if (obj instanceof T) {} // Compile error
    return true;
}

// Workaround: pass Class&#x3C;T>
private Class&#x3C;T> type;

public ErasureConsequences(Class&#x3C;T> type) {
    this.type = type;
}

public T createInstance() throws Exception {
    return type.getDeclaredConstructor().newInstance();
}

@SuppressWarnings("unchecked")
public T[] createArray(int size) {
    return (T[]) Array.newInstance(type, size);
}

}

Generic Builders

Builder pattern with generics for fluent APIs.

Generic builder:

public class Query<T> { private final Class<T> type; private String where; private String orderBy; private int limit;

private Query(Class&#x3C;T> type) {
    this.type = type;
}

public static &#x3C;T> Query&#x3C;T> from(Class&#x3C;T> type) {
    return new Query&#x3C;>(type);
}

public Query&#x3C;T> where(String condition) {
    this.where = condition;
    return this;
}

public Query&#x3C;T> orderBy(String field) {
    this.orderBy = field;
    return this;
}

public Query&#x3C;T> limit(int count) {
    this.limit = count;
    return this;
}

public List&#x3C;T> execute() {
    // Execute query and return results
    return new ArrayList&#x3C;>();
}

public static void main(String[] args) {
    List&#x3C;User> users = Query.from(User.class)
        .where("age > 18")
        .orderBy("name")
        .limit(10)
        .execute();
}

}

Recursive Type Bounds

Type bounds can reference the type parameter itself.

Enum with recursive bound:

public class RecursiveBound { // Enum trick public static <E extends Enum<E>> void printEnum(Class<E> enumClass) { for (E constant : enumClass.getEnumConstants()) { System.out.println(constant); } }

// Comparable with recursive bound
public static &#x3C;T extends Comparable&#x3C;T>> T max(List&#x3C;T> list) {
    if (list.isEmpty()) {
        throw new IllegalArgumentException("Empty list");
    }

    T max = list.get(0);
    for (T item : list) {
        if (item.compareTo(max) > 0) {
            max = item;
        }
    }
    return max;
}

enum Color { RED, GREEN, BLUE }

public static void main(String[] args) {
    printEnum(Color.class);

    List&#x3C;String> words = List.of("apple", "banana", "cherry");
    String maxWord = max(words); // "cherry"

    List&#x3C;Integer> numbers = List.of(1, 5, 3, 9, 2);
    Integer maxNum = max(numbers); // 9
}

}

Builder with recursive bound:

public abstract class Builder<T, B extends Builder<T, B>> { protected abstract B self();

public abstract T build();

}

public class Person { private final String name; private final int age;

protected Person(PersonBuilder&#x3C;?> builder) {
    this.name = builder.name;
    this.age = builder.age;
}

public static PersonBuilder&#x3C;?> builder() {
    return new PersonBuilder&#x3C;>();
}

public static class PersonBuilder&#x3C;B extends PersonBuilder&#x3C;B>>
        extends Builder&#x3C;Person, B> {
    private String name;
    private int age;

    public B name(String name) {
        this.name = name;
        return self();
    }

    public B age(int age) {
        this.age = age;
        return self();
    }

    @Override
    @SuppressWarnings("unchecked")
    protected B self() {
        return (B) this;
    }

    @Override
    public Person build() {
        return new Person(this);
    }
}

}

When to Use This Skill

Use java-generics when you need to:

  • Write reusable code that works with multiple types

  • Enforce compile-time type safety

  • Eliminate casting and type errors at runtime

  • Create generic collections, algorithms, or utilities

  • Build type-safe APIs and frameworks

  • Implement generic design patterns

  • Work with Java Collections Framework

  • Define flexible method signatures with type parameters

  • Create bounded type hierarchies

  • Implement builder or factory patterns with type safety

Best Practices

  • Use meaningful type parameter names (T, E, K, V)

  • Prefer bounded type parameters over raw types

  • Use wildcards for flexibility in method parameters

  • Apply PECS principle (Producer Extends, Consumer Super)

  • Avoid raw types in new code

  • Use @SuppressWarnings("unchecked") sparingly

  • Document generic type constraints clearly

  • Prefer generic methods over generic classes when possible

  • Use bounded wildcards for maximum API flexibility

  • Consider type erasure implications

Common Pitfalls

  • Using raw types instead of parameterized types

  • Confusing extends and super wildcards

  • Trying to create arrays of generic types

  • Not understanding type erasure limitations

  • Overusing wildcards making code unreadable

  • Incorrect variance with wildcards

  • Forgetting that generics are compile-time only

  • Not handling unchecked warnings properly

  • Creating unnecessarily complex generic hierarchies

  • Misusing instanceof with generic types

Resources

  • Java Generics Tutorial

  • Effective Java: Generics

  • Java Generics FAQ

  • Oracle Generics Documentation

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

typescript-type-system

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

typescript-async-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

c-systems-programming

No summary provided by upstream source.

Repository SourceNeeds Review