Generic types and wildcards

Generic types

We create generic classes and methods when we want the same code to work with different data types, instead of writing separate versions for each type. A generic type uses placeholders for data types, which are written inside the <> brackets after a class name or before a method name. These placeholders represent the real data types that will be provided later when the class or method is used. Inside the class or method, the placeholder types are used in place of concrete data types. Generic type parameters can also be bounded using the extends keyword, which restricts them to a specific class or interface and its subclasses. For example, TYPE extends ClassName ensures that the generic type must be ClassName or any of its subclasses, allowing the class to safely use methods and properties defined in ClassName. Multiple bounds are allowed, but only one class may be extended, while any additional bounds must be interfaces (in both cases the extends keyword is used).


public class Main {
    public static void main(String[] args) {
        generalTypeClass<String> x = new generalTypeClass<>();
        x.variable = "x";
        System.out.println(x.returnValue());

        generalTypeClass<Integer> y = new generalTypeClass<>();
        y.variable = 4;
        System.out.println(y.returnValue());

        generalTypeClass2<Integer, Integer> z = new generalTypeClass2<>();
        z.variable = 4;
        z.variable2 = 5;
        System.out.println(z.returnValue());
        System.out.println(z.returnValue2());
    }
}

class generalTypeClass <TYPE> {
    TYPE variable;
    public TYPE returnValue() {
        return this.variable;
    }
}

class generalTypeClass2  <TYPE, TYPE2> {
    TYPE variable;
    TYPE2 variable2;
    public TYPE returnValue() { return this.variable; }
    public TYPE2 returnValue2() { return this.variable2; }
}
                                    

Wildcards

Generic types let us create classes or methods that can work with different types of data without writing separate versions for each type. Sometimes, when using these generic types, the exact type does not matter. In such cases, wildcards can make the code more flexible. Wildcards are often used in method parameters to allow a method to accept a range of related types. The wildcard ? extends T allows any subtype of T, while ? super T allows any supertype of T. This lets us write methods that can work with many related types while still keeping the code type-safe.


import java.util.*;

public class Main {
    public static void main(String[] args) {
        ArrayList<SuperClass> list = new ArrayList<>();
        list.add(new SuperClass());
        list.add(new SubClass());

        ArrayList<SubClass> list2 = new ArrayList<>();
        SuperClass.method(list);
        SuperClass.method(list2); // this will work because SubClass is a subclass of SuperClass (extends SuperClass)
    }
}

class SuperClass {
    public static void method(List<? extends SuperClass> x) {}
}

class SubClass extends SuperClass {}