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.