- Prior to jdk 5, collection of any type consisted of collection of objects and downcasting was necessary to retrieve the element of correct type
class Main{
public static void main(String[] args){
List words = new ArrayList();
words.add("Hello");
words.add(" world!");
String s = ((String)words.get(0)) + ((String)words.get(1));
System.out.print(s);
}
}
- In jdk 5, generic parameters were added to the declaration of collection classes, so the above code can be rewritten as
class Main{
public static void main(String[] args){
List<String> words = new ArrayList();
words.add("Hello");
words.add(" world!");
String s = words.get(0) + words.get(1);
System.out.print(s);
}
}
- Stronger type checks at compile time.
- No downcasting
- Reusable code for different data types
- Generics are implemented using Type Erasure approach where the compiler uses the generic type information to compile the code, but erases it afterwards.
- Generic information is not available at runtime. This enables generic code to be backward compatible with the legacy code that uses raw types.
- Once the compiler confirms that the generic type is used safely, it converts generic type to raw type.
-
Generic Subtyping is not covariant.
Manager is a subclass of Employee but ArrayList is not subclass of ArrayList
-
Array Subtyping is covariant.
Manager is a subclass of Employee and Manager[] is subclass of Employee[]
- Component type of an array is not allowed to be a type variable.
class Main{ public static void main(String[] args){ T[] arr = null; // ok arr = new T[5]; // compiler error T[] arr = (T[]) new Object[5]; // No Error } }
- Component type of an array is not allowed to be a type variable.
-
Component type of an array is not allowed to be a parametrized type.
class Pair<T> { private T k; private T v; Pair(T k, T v) { this.k = k; this.v = v; } public T getK() { return k; } public T getV() { return v; } @Override public String toString() { return "Pair{" + "k=" + k + ", v=" + v + '}'; } } class Main{ public static void main(String[] args){ List<String>[] lst = new ArrayList<String>[5]; // compiler error Pair<Integer>[] a = new Pair<Integer>[10]; // compiler error List<String>[] lst = new ArrayList[5]; // ok Pair<Integer>[] a = new Pair[10]; // ok lst[0] = new ArrayList<>(){ { add("hello"); } }; lst[1] = new ArrayList<>(){ { add("there"); add("hey"); } }; a[0] = new Pair<Integer>(1,2); a[1] = new Pair<Integer>(2,4); System.out.println(Arrays.toString(lst)); System.out.println(Arrays.toString(a)); } }
here, new
new ArrayList<String>[5]
is not allowed because Java does not allow the creation of arrays with a parameterized component type. however,new ArrayList[5]
creates an array of the raw type ArrayList and raw type effectively disables generic type checking. thus this is allowed with unchecked warning.
- Reifiable types are those that have type information fully available at runtime. it includes primitives, non-generic types, raw types.
String[] obj=new String[10];
will remain same in the runtime. - Non-Reifiable are those whose type informations are removed at compile time by type erasure.
List<String> list=new ArrayList<String> becomes List list=new ArrayList() at runtime
- Generic methods are the methods that has their own type parameters.
class Main{
public static <T> boolean contains1(List<T> list, T s){
for(T x:list){
if(x==null && s==null) return true;
if(s== null || x==null) continue;
if(x.equals(s)) return true;
}
return false;
}
public static void test(){
List<String> list = Arrays.asList("Bob","Joe","Tom");
System.out.println(Test.<String>contains1(list, "Tom")); // manually specifying type parameter
System.out.println(Test.contains1(list, "Tom")); // type parameter inferred
}
public static void main(String[] args) {
test();
}
}
-
? extends
Bounded wildcard
Since generic subtyping is not covariant, it is very inconvenient. this can be resolved by using bounded wildcards. ifManager is subclass of Employee
thenList<Manager> is subclass of List<? extends Employee>
.Limitation: with
? extends
, values can be gotten but not inserted.class Main{ public static void main(String[] args){ List<Integer> ints = new ArrayList<Integer>(); ints.add(1); ints.add(2); List<? extends Number> nums = ints; nums.add(null); // ok null is ultimate subtype nums.add(3); //compiler error } }
-
? super
Bounded wildcard
when super wildcard is used values can be inserted but cannot be gotten.class Main{ public static void main(String[] args){ List< ? super Integer> ints = new ArrayList<>(); ints.add(1); ints.add(2); System.out.println(ints.get(1)); // OK Object value1 = ints.get(1); // ok Object is ultimate supertype Integer value = ints.get(1); // compiler error } }
Note: Do not use wildcard if you need to both get and put values.
-
Unbounded wildcard
The wildcard?
without
thesuper
orextends
qualifier, a.k.aunknown type
. The important usecase of unbounded wildcard is to capture wildcard.Collection<?> is an abbreviation for Collection<? extends Object>
class Main{ public static void reverse(List<?> list) { List<Object> tmp = new ArrayList<Object>(list); for (int i = 0; i < list.size(); i++) { list.set(i, tmp.get(list.size()-i-1)); // error } } public static void main(String[] args){ } }
Using helper method to capture the wildcard
class Main{ public static void reverse(List<?> list) { reverseHelper(list); } private static <T> void reverseHelper(List<T> list){ List<T> tmp = new ArrayList<>(list); for (int i = 0; i < list.size(); i++) { list.set(i, tmp.get(list.size()-i-1)); } } public static void main(String[] args) { } }
Passing items into the helper method causes the unknown type ? to be “captured” as the type T. In the helper method, getting and setting values is legal because we are not dealing with wildcards in that method.