Summary
Type parameters
- scopes with method level declaration
java
class A<T> { // generic class with type parameter T
public T funcA() { ... // non-generic method that returns type T
}
public <T> T funcB() { ... // generic method with type parameter T, different from T in the outer scope
}
public <S> T funcC() { ... // generic method with type parameters T and S
}
}
Instantiating generic classes/calling generc methods
java
A<Integer> a = new A<Integer>(); // after symbol, before parameters
a.<String>funcB(); // after '.', before symbol
Type erasure
- pre-compilation after type checking
- Erasure -> Compilation -> Run-time
java
class A<T> { // unbounded type parameter
private T obj;
public T func() { ...
}
}
A<String> a = new A<String>();
String s = a.func();
// After type erasure
class A {
private Object obj;
public Object func() { ...
}
}
A a = new A();
String s = (String) a.func(); // added type cast
Bounding
- the type parameter has to be a subtype of a certain reference type
java
class A<S, T extends Number> {
public S funcA() { ...
}
public T funcA() { ...
}
}
A<String, Integer> a = new A<String, Integer>();
String s = a.funcA();
Integer i = a.funcB();
// After type erasure
class A {
public Object funcA() { ... // <- type parameter's replaced by bound
}
public Number funcA() { ...
}
}
A a = new A();
String s = (String) a.funcA(); // <- added type cast whenever its methods are invoked
Integer i = (Integer) a.funcB();
// Restrictions at compile-time
new A<String, String>(); // compile-time error: String </: Number
new A<String, Integer>(); // works: Integer <: Number
Multiple bounds
- declare as first type, any methods from the other type is used, do a cast to that type
java
class A {
public void funcA();
}
interface B {
public void funcB();
}
class C<T extends A & B> { // similar to extending classes/interfaces, one class but multiple interfaces
public void funcC(T t) {
t.funcA();
t.funcB();
}
}
// After type erasure
class C {
public void funcC(A t) {
t.funcA();
((B) t).funcB(); // cast to B when B functions are called
}
}
Unchecked warning
- arises when casting to a generic type parameter
- to get around this, instantiate
Object
or the lower bound of the type parameter - run-time errors may still occur
java
class A<T> {
private T obj;
private T[] array;
public A(int size) {
// typical use case, since we cannot instantiate generic types
this.obj = (T) new Object(); // unchecked warning
this.array = (T[]) new Object[size]; // unchecked warning
}
// Warning suppressed
public A(int size) {
// this.obj private so it cannot be set outside of the class
// we ensure that any internal methods that set this.obj is type safe
// so its safe to cast Object to T
@SuppressWarnings("unchecked")
T obj = (T) new Object();
this.obj = obj;
// ...
// so its safe to cast Object[] to T[]
@SuppressWarnings("unchecked")
T[] array = (T[]) new Object[size];
this.array = array;
}
}
@SuppressWarnings
can only apply to the declaration and not the assignment, it should be used in the most limited scope
Rawtype warning
- arises when declaring a raw generic type
java
class A<T> {
}
class B {
public void funcA() {
@SuppressWarning("rawtypes")
A a = new A<Integer>(); // rawtype warning
}
@SuppressWarning("rawtypes")
public void funcB(A a) { ... // rawtype warning
}
}
Concept
Make classes more flexible
- a framework for compiler type checks(code sharing)
- parametric polymorphism
Instantiating generics
- cannot instantiate an array of generics/generic classes
- cannot use primitive types
- can use interfaces
java
new Pair<String,Integer>[2]; // error
new Pair<S,T>[2]; // error
new T[2]; // error
new Pair<int>(); // error
Bridge methods
- preserves overriding when inheriting methods from a declared generic class
- created for run-time consistency with dynamic binding, ensures overriding rather than overloading
- inserted by the compiler after type erasure, before compilation
- view added bridge methods in the jvm bytcode with https://godbolt.org/
java
class A<T> {
public void func(T t) { ...
}
}
class B extends A<String> {
public void func(String s) { ... // appears to override before type erasure
}
public void func(Integer i) { ... // appears to overload before type erasure
}
}
// After type erasure
class A {
public void func(Object t) { ...
}
}
class B extends A {
public void func(String s) { ... // overloads after type erasure
}
public void func(Integer i) { ... // overloads implicitly
}
public void func(Object o) { // overrides A::func(Object) and calls B::func(String)
this.func((String) o);
}
}
Generic static
- generic types is only associated with the instance
- but non-generic statics are still associated with the raw class, regardless of type
T
java
class B<T> {
T x; // instance field, works
static T y; // class field, error
}
B<Integer> = new B<Integer>();
B<Double> = new B<Double>();
// no way to know what T will be
Application
Pair
java
// Object implementation, weak type checking
class Pair {
private Object first;
private Object second;
public Pair(Object first, Object second) {
this.first = first;
this.second = second;
}
public Object getFirst() {
return this.first;
}
public Object getSecond() {
return this.second;
}
}
// Generic implementation, leverages the compiler to type check
class Pair<S,T> {
private S first;
private T second;
public Pair(S first, T second) {
this.first = first;
this.second = second;
}
public S getFirst() {
return this.first;
}
public T getSecond() {
return this.second;
}
}
Sequence
java
class Seq<T> {
private T[] array;
Seq(int size) {
// The only way we can put an object into
// array is through the method set() and we
// only put object of type T inside.
// So it is safe to cast `Object[]` to `T[]`.
@SuppressWarnings("unchecked")
T[] temp = (T[]) new Object[size];
this.array = temp;
}
public void set(int index, T item) {
this.array[index] = item;
}
public T get(int index) {
return this.array[index];
}
public T[] getArray() {
return this.array;
}
}
Suppressing both unchecked and rwatypes warnings
java
class Seq<T extends Comparable<T>> {
private T[] seq;
public Seq(int size) {
// New objects of type T can only be added to the array
// via set() which is type safe
@SuppressWarnings("rawtypes")
Comparable[] arr1 = (Comparable[]) new Comparable[size];
@SuppressWarnings("unchecked")
Comparable<T>[] arr2 = (Comparable<T>[]) arr1;
@SuppressWarnings("unchecked")
T[] seq = (T[]) arr2;
this.seq = seq;
}
// Combined suppress warnings
public Seq(int size) {
@SuppressWarnings({"rawtypes", "unchecked"})
T[] seq = (T[]) new Comparable[size];
this.seq = seq;
}
}
Bridge methods
- overriding use
this
, else usesuper
java
class A<T> {
public void fun(T x) {
System.out.println("A");
}
}
// After type erasure
class A {
public void fun(Object x) {
System.out.println("A");
}
}
// (i)
class B extends A<String> {
public void fun(String i) { // appears to override, but actually overloads
System.out.println("B");
}
}
// After type erasure
class B extends A {
public void fun(String i) {
System.out.println("B");
}
public void fun(Object o) { // bridge method, preserves the idea of overriding
this.fun((String) o);
}
}
//
A<String> a = new B();
a.fun("2"); // CTT(a) = A, use A::fun(Object) -> RTT(a) = B, invoke B::fun(Object) -> B::fun(String) is called by bride method -> prints "B"
// (ii)
class B extends A<String> {
public void fun(Object i) {
System.out.println("B");
}
}
// After type erasure
class B extends A {
public void fun(Object i) {
System.out.println("B");
}
public void fun(Object o) { // bridge method, compile-time error: 2 methods with same signature
...
}
}
//
A<String> a = new B();
a.fun("2"); // fails to compile
// (iii)
class B extends A<String> {
public void fun(Integer i) {
System.out.println("B");
}
}
// After type erasure
class B extends A {
public void fun(Integer i) { // does not override
System.out.println("B");
}
// bridge method not needed for overloading, but include it in the thought process
public void fun(Object o) { // bridge method, forwarding to super type/up the type hierachy
super.fun((String) o);
}
}
//
A<String> a = new B();
a.fun("2"); // CTT(a) = A, use A::fun(Object) -> RTT(a) = B, look for B::fun(Object) -> invokes A::fun(Object) -> prints "A"
Overloading with type erasure
java
class T1 {}
class T2 extends T1 {}
class T3 extends T2 {}
class T4 extends T3 {}
class A<T extends T1> {
void fun(T x) {
System.out.println(1);
}
<T extends T3> void fun(T x) {
System.out.println(2);
}
}
// After type erasure
class A {
void fun(T1 x) {
System.out.println(1);
}
void fun(T3 x) {
System.out.println(2);
}
}
//
class Main {
public static void main(String[] args) {
A<T1> a = new A<>();
a.fun(new T2()); // CTT(new T2()) = T2, use A::fun(T1) prints 1
a.fun(new T3()); // CTT(new T3()) = T3, use A::fun(T3) -> prints 2
a.fun(new T4()); // CTT(new T3()) = T4, use A::fun(T3) -> prints 2
}
}
Comparable
java
class A implements Comparable<A> {
@Override
public int compareTo(A o) { ... // applies the concept of bridging methods to preserve overriding
}
}