What are generics for?
This just restates the Oracle Doc’s Tutorial on Generics lol. Maybe read that instead. But, I do include answers to questions I had while going through the docs.
Java is a statically typed language. Which basically means the compiler needs to know the type of an object at compile time!
But there are some cases where you want a Class or Interface to be able to work with parameters of any type.
In such cases we could just accept any type by accepting the type Object
as so:
public interface MyComparator {
int compare(Object a, Object b);
Object max(Object a, Object B);
}
This works! But we lose the strong type system of Java:
- The Interface cannot define if parameter a and b must be of the same type
- The Interface cannot enforce the return type in the method signature (for the
max()
function)
Instead, all of the type safety is left to the implementing classes to decide, which can break how the interface is meant to be used, and leads to increased boilerplate code and potential for errors.
Let’s explore how that would look like!
We’d have to create an intermediary abstract class to enforce type safety via runtime reflection. The implementation of each function will then be done in a concrete class.
public abstract class MyPartialImplementation implements MyComparator {
// Implement Interface method and implement runtime type checking.
// But, defer implementation logic to abstact methods for subclasses to implement
@override
public int compare(Object a, Object b) {
if (!a.getClass().equals(b.getClass()))
throw new IllegalArgumentException("Parameters are not of the same type");
return doCompare(a, b);
}
// Implement Interface method and implement runtime type checking.
// But, defer implementation logic to abstact methods for subclasses to implement
@override
public Object max(Object a, Object b) {
if (!a.getClass().equals(b.getClass()))
throw new IllegalArgumentException("Parameters are not of the same type");
Object result = doMax(a, b);
if (!result.getClass().equals(a.getclass()))
throw new IllegalArgumentException("Parameters and return type are not of the same type");
return result;
}
// Define abstract methods for subclasses to define implementation logic
protected abstract int doCompare(Object a, Object b);
protected abstract Object doMax(Object a, Object b);
}
public class MyFullImplementation extends MyPartialImplementation {
@override
int doCompare(Object a, Object b) {
//... concrete implementation
}
@override
Object doMax(Object a, Object b) {
//... concrete implementation
}
}
Although this works, due to the type safety being enforced at runtime, the compiler won’t be able to help enforce types at compile time and you won’t get type hints from the Java Language Server…
For example, let’s try to use this MyFullImplementation
Class wrongly!
This code will show no no errors and compile just fine! But it makes a critical mistake, mixing arguments of different types in the max()
function which will result in a runtime exception!!!
public static void main(String[] args) {
String a = "hehe";
Integer b = 123;
Object m = new MyFullImplementation().max(a, b);
}
This is where Generics come in! Generics are a way to utilise Java’s type system to enforce type safety at compile time! and greatly reduce the amount of boilerplate code needed to enforce those types!
Invariance
Generics in Java are invariant! What does that mean?
Let’s take a look:
public static void main(String[] args) {
Object[] a = new String[1]; // ok!
ArrayList<Object> b = new ArrayList<String>(); // ERROR!!!
}
Compilation Error
incompatible types: ArrayList<String> cannot be converted to ArrayList<Object>
In the assignment of Object[] a
, we see that following the ideas of polymorphism, Java allows for subtypes to be treated as instances of their supertype (this is known as covariance)!
However, in the next case - Java throws an error. generics were purposefully made to be invariant, meaning the types must match exactly. Why? well…
How and Where Generics Can Be Used
java-generic-types
java-generic-raw-types
java-generic-methods
java-generic-bounded-type-parameters
java-generic-wildcards
java-generics-type-erasure
https://dev.java/learn/generics/restrictions/