Understanding Java Generics<T>

Photo by Patrick on Unsplash

Understanding Java Generics<T>

Before we dive into the specifics, it's crucial to understand the basics. The term "Generics" means parameterized types, which enable classes, interfaces, and methods to be created with placeholders for the types they handle.

To understand generics more clearly, let's consider a simple class hierarchy in the animal kingdom. We have a base class called Animal and two subclasses, Bird and Dog.

public class Animal {
    public void eat() {
        System.out.println("Animal is eating...");
    }
}

public class Bird extends Animal {
    public void fly() {
        System.out.println("Bird is flying...");
    }
}

public class Dog extends Animal {
    public void bark() {
        System.out.println("Dog is barking...");
    }
}

Now, let's create a Cage generic class that can hold any type of Animal:

public class Cage<T extends Animal> {
    private T animal;

    public void add(T animal) {
        this.animal = animal;
    }

    public T release() {
        return animal;
    }
}

The Cage class is a generic class, and it uses the type parameter T which extends Animal. This implies that the Cage class can only hold an Animal or any of its subclasses.

The type parameter above <T> can actually be anything <WhateverYouWant>.. But it is standardized to the letter "T" meaning -> Type.

Wildcard Bounds

There are times when we may not know the exact type to use with a class or method. Wildcards can be quite handy in such situations. Wildcards are represented by the question mark symbol ?.

Upper Bounded Wildcards

Upper-bounded wildcards restrict the unknown type to be a specific type or a subtype of that. They're expressed using the extends keyword.

Let's create a method that will accept a Cage of Animal or any subclass of Animal:

public void feedAnimals(Cage<? extends Animal> cage) {
    Animal animal = cage.release();
    animal.eat();
}

Lower Bounded Wildcards

Lower bounded wildcards restrict the unknown type to be a specific type or a superclass of that. They're expressed using the super keyword.

For instance, let's consider a method that can add any Animal or superclass of Animal into a Cage:

public void addToCage(Cage<? super Animal> cage, Animal animal) {
    cage.add(animal);
}

Unbounded Wildcards

In cases where the functionality is independent of the type argument, we can use unbounded wildcards. They're expressed using the question mark symbol ?.

public void printCageContent(Cage<?> cage) {
    System.out.println(cage.release());
}

Conclusion

Generics in Java add stability to your code by allowing compile-time type checks and reducing the risk of ClassCastException that was common while working with collections. Through examples from the animal kingdom, we've seen how we can use generics to create more robust and type-safe code. Whether you're creating a Cage for Birds or Dogs, or feeding animals from an unknown Cage, generics make your life as a Java engineer a little easier.

Did you find this article valuable?

Support Adrian Kodja by becoming a sponsor. Any amount is appreciated!