Explain Codes LogoExplain Codes Logo

Java Generics With a Class & Interface - Together

java
generics
type-parameters
best-practices
Nikita BarsukovbyNikita Barsukov·Sep 29, 2024
TLDR

For a type parameter to simultaneously extend a specific class and interface, specify the bounds using <T extends Class & Interface>. The class name should be written first, followed by the interface(s).

public class MyClass<T extends BaseClass & AnInterface> { // kittens go here }

Now, to define a Box that holds any Animal that's also a Pet (for example), here's what it should look like:

public class PetBox<T extends Animal & Pet> { // Where's the Schrödinger's Cat? }

Breaking down type parameters

When working with type parameters having multiple bounds — generally a class and an interface — use the syntax <T extends B & C>. This implies that the type argument for T should inherit from B and implement the interface C. Remember, you can include multiple interfaces but only one class in the list, and if a class is there, it must come first.

Real-world applications

The Collections framework

Java's Collections framework routinely uses generics having multiple bounds. An exemplary case is the Collections#max method:

public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) // It's a kind of magic!

With this method declaration, we ensure T is an Object that is Comparable to itself. This showcases how Java enforces strong type checks at compile time with generics.

Complex type constaints

Java's generics enable adding complex type constraints while preserving backward compatibility. However, such complexity has its perils and can result in confusing APIs if not treated with caution.

Naming type parameters

Use meaningful names for type parameters to indicate what is expected. It not only enhances the code's readability but also aids during creation of generic methods or classes:

public <U extends Number & Comparable<U>> void inspect(U u) { // Do not feed the code gremlins }

Best practices and common gotchas

Embrace simplicity

Despite their power, use generics with multiple bounds sparingly and when truly necessary. Overuse can complicate code, making it difficult to understand and maintain.

Watch out for type erasure

Generics in Java are implemented via type erasure. This means runtime type checks on generic types are not straightforward and can sometimes necessitate the use of reflection or class tokens.

Document well

Document usages of classes or methods having complex generic bounds meticulously. Clear documentation allows other developers (and your future self) to grasp its purpose and limits.