190 likes | 382 Views
Java Generics. The Dark Ages: Before Java 5. Java relied only on inclusion polymorphism A polymorphism code = Using a common superclass Every class is a subclass of java.lang.Object So there's always at least one common superclass Collections were actually collections of objects
E N D
The Dark Ages: Before Java 5 • Java relied only on inclusion polymorphism • A polymorphism code = Using a common superclass • Every class is a subclass of java.lang.Object • So there's always at least one common superclass • Collections were actually collections of objects • You can put anything into a collection • When you extract something, its static type is Object
Java 5 and Beyond • Parametric polymorphism introduced in Java 5 • September 2004 • Most significant enhancement since Java's birth • May resemble C++ templates, but: • Implemented differently • “Compile once and for all” • Better error messages • Type constraints are explicit • Less power (e.g., cannot inherit from a type parameter) • Parameters can only be types • No executable blowup
Generics – Before and After // Without generics List list = new ArrayList(); list.add(new Integer(0)); Integer x = (Integer) list.get(0); // Programmer must downcast list.add("abc"); Integer y = (Integer) list.get(1); // Run -time exception // With Generics – The compiler “knows” the type of the list elements List<Integer> list = new ArrayList<Integer>(); list.add(new Integer(0)); Integer x = list.get(0); list.add("abc");// Compiler Error – Expected ineteger
A Generic Method public static<T> List<T> dup(T t, int n) { List<T> result = new ArrayList<T>(); for(int i = 0; i < n; ++i) result.add(t); return result; } ... List<String> list = dup("abc", 2); // Implicit instantiation // Explicit not supported ...
Recap: java.lang.Integer, Number package java.lang; public abstract class Number { public abstract int intValue(); public abstract long longValue(); public abstract float floatValue(); public abstract double doubleValue(); public byte byteValue() { return (byte) intValue(); } public short shortValue() { return (short) intValue(); } } package java.lang; public final class Integer extends Number { private final int n; public Integer(int v) { n = v; } public int intValue() { return n; } public long longValue() { return (long) n; } public float floatValue() { return (float) n; } public double doubleValue() { return (double) n; } }
The Cell<T> class public class Cell<T> { private T value; public T get() { return value; } public void set(T t) { value = t; } // T is at least an Object, so it supports toString() public String toString() { return value.toString(); } }
Using Cell<T> static void main(String[] args) { Cell<Integer> ci = new Cell<Integer>(); ci.set(new Integer(5)); System.out.println(ci.get()); int n = ci.get(); // auto-unboxing n = n*n; ci.set(n); // auto-boxing System.out.println(ci.get()); Cell<Number> cn = new Cell<Number>(); cn.set(ci.get()); }
Type Parameters with Upper Bounds public class NumberCell<T extends Number> { private T value; public T get() { return value; } public void set(T t) { value = t; } public String toString() { return value.toString(); } // T is at least a Number, so it supports intValue() public int sum(int x) { return value.intValue() + x; } } • The extend keyword specifies an upper bound for T • Can be used with both classes and interfaces • Can have multiple bounds: • T extends MyClass & MyInterface
Cell<Integer> is not compatible with Cell<Object> static void assign(Cell<Object> co, Object o) { co.set(o); } void main() { Cell<Integer> ci = new Cell<Integer>(); assign(ci, new Integer(10)); // Compiler error:// Cannot convert from Cell<Integer> to Cell<Object>// Otherwise, the following code would break at run time assign(ci, "abc"); Integer n = ci.get(); System.out.println(n.intValue()); } • The same conformance issue as with covariant input arguments
A better version of assign() static<T> void assign(Cell<T> co, T o) { co.set(o); } void main() { Cell<Integer> ci = new Cell<Integer>(); assign(ci, new Integer(10)); // Now it works }
The Wildcard: <?> static<T, C extends Cell<T>> boolean isNullA(C c) { T t = c.get(); return t == null; } static<C extends Cell<?>> boolean isNullB(C c) { return c.get() == null; } static boolean isNullC(Cell<?> c) { return c.get() == null; } • If a type parameter is used exactly once – and this occurrence is inside an upper bound – it can be replaced with a wildcard • If a type parameter is used exactly once – and this occurrence is as a type of formal parameter of the method – it can be replaced with its upper bound
Wildcard: Type Checking Rules static boolean isNullC(Cell<?> c) { ... } • Inside isNullC(): • ? Cannot be used to declare the type of a variable • c.get() return an Object • c.set(o) is not allowed, even if o is of type Object. • Exception to this rule: c.set(null) is allowed • For every type Y, the type X<?> is a supertype of X<Y>
Lower Bounds public static class Cell<T> { private T value; public T get() { return value; } public void set(T t) { value = t; } public void copyTo(Cell<? super T> c) { c.set(value); } } • The super keyword specifies a lower bound for a wild-card • Cannot be used with regular type parameters
Implementation of Generics: Type Erasure • Compiling a Generic class: Cell<T> • Check: Type correctness • Erase: Replace T with its upper bound (Object) • Compile: to byte code • Compiling an instantiation: Cell<String> c; • Replace: The instantiated type with the raw type • Annotate: add a “footnote” specifying the substitution:c: T = String • The annotation is saved in the class file declaring c • Compiling a field access or a message send: c.get() • Obtain: The annotation of the receiver variable, c • Check: Actual method parameters against the actual type parameters • (None in this example) • Downcast: insert a cast of the return type to the actual type parameter, String
Type Erasure • Benefit of Erasure: • Binary compatibility with older libraries: List<String> is translated to type List (raw type ) • Code compiled using a "pre-generics" class works correctly • Code written using a "pre-generics" class still compiles and work • Drawback of Erasure • generic type information is not known at runtime • List<Integer> and List<String> refer to List • type variables cannot be used in newexpressions • Type is unknown at runtime
Type Erasure: Example • Generic form class Foo<T extends Number> { T m(Set<? Extends T> s) { } } • Erased form: class Foo { Number m(Set s){} }
Erasure: Loophole List<Integer> list = new ArrayList<Integer>(); Object o = list; List<Object> err = (List<Object>) o; // Warning: Unchecked Conversion err.add("abc"); Integer i = list.get(0); // Runtime error: cast from String to Integer • Unchecked conversion warning • Issued by the compiler when we use an instantiated type in a position where only the raw type is available • Usually appears when we cast to an instantiated generic type • The downcast check will be partial • Indicates a danger of type errors at run time • Avoid at all costs