import java.util.ArrayList; import java.util.HashMap; /* Generics in java Tradeoff with C++: Runtime overhead, but with some static type checking of generic class/functions before they're instantiated, though not very well done. The principal technique for implementing Java generics is "Type Erasure". When you create a generic class such as class yourclass> { A x; B y; //void f() { y.compareTo(x); } // won't compile: A,B incompatible types } Java will immediately attempt to type check the class, before any instantiations (in contrast to C++). Sometimes it works: `y.compareTo(y)` compiles but not `y.compareTo(x)` because nothing is known about type A. Ideally, generic code should have the same level of static type safety as non-generic code. Theoeretically, this should be possible. However, Java actually creates another class for you that it uses to implement your "generic" class: */ class yourclass { Object x; Comparable y; } /* For every class/interface/function that appears to be "generic", there is actually a non-generic version in the background. The built-in Comparable interface is ostensibly interface Comparable { int compareTo(T x); } But the "real" Comparable interface is actually just interface Comparable { int compareTo(Object x); } In the non-generic versions every generic type variable (A, B, T) is replaced by the superclass that it "extends". In the case of B, Comparable is replaced by Comparable. All unconstrained type variables (A,T) become Object (the superclass of all classes). This is called type erasure. Type erasure means that yourclass and yourclass are indistinguishable at runtime. These types are "non-reifiable" (type info not fully available at runtime). The first versions of Java (before 2005) did not have generics, and people used inheritance exclusively to implement polymorphism. To have a polymorphic variable, you'd just say "Object x;". But using the type Object basically throws away static typing, because nearly everything is an object. To treat an Object as a specific type, you'd have to downcast. The following code compiles: Object x = "abc"; // strings are objects x = new bankaccount(1000); // bank accounts are also objects; Object y = new Integer( (Integer)x * 3); // will compile. Only at runtime will the downcast (Integer)x fail. No compile-time type checking is possible when you declare something of type Object. It's like using a non-statically typed language such as Python. Generics where supposed to change that. However, Java's designers chose the type erasure approach to implement generics. The java compiler makes a show of checking the generic code, but because of type erasure, what it can guarantee in terms of type safety is very limited. Type erasure also leads to two of the most frustrating aspects of Java generics: the inability to create a static variable of generic type, and the problem of "generic array creation". Let's look at more code: */ class generalclass> { A x; B y; A[] ar; // generic array //No static variables of generic type: //static A default_x; //won't compile - type vars are "not static" void f() {// generic code "type checked" //int z = x+1; // won't compile (will in c++) y = (B)x; // will this compile? IT WILL, with a warning (!) //x = (Integer)y+1; // won't compile (good), but Integer z = (Integer)y+1; // compiles with warning // No generic array creation in java //A[] array = new A[10]; // won't compile "generic array creation error" ar = (A[])new Object[10]; // workaround, gets compiler warning. B[] arr = (B[]) new Comparable[10]; // Unintended hypocrisy: can't create a generic array without an // unsafe typecast: compiler warns that it has no ability to // verify the sanity of the type cast the way it usually does. But // it has no choice but to compile it because of a mistake it made // in the past ... // Can we create an array of hashmaps? (java.util.HashMap) //HashMap[] array2 = new HashMap[4]; // Won't compile: this is also "generic array creation" even though // no generic type variable occurs (all types known!) ar[0] = (A)"abc123"; // LOOK MA, ar[1] = (A)((Double)0.25); // BOTH LINES COMPILED! arr[0] = (B)ar[0]; // A is not even Comparable, but casts to B? ArrayList al = new ArrayList(); //generic "arraylist" creation OK! ArrayList am = (ArrayList)new ArrayList(); //?? compiled! // ****type casting is not checked at all between generics. ar = (A[]) new String[10]; /// YIKES MA! THIS COMPILED TOO! // what if there's an instance // var n = new generalclass(); // then n.ar should be a Double[] array no? how can a Double[] // array be assigned to a String[]? //*********** What's Causing All This? // Arrays predate generics. Arrays, unlike generic objects, carry // the type information of their values at runtime. That is, // String[] A and Integer[] A are distinguishable at runtime, // unlike generics. Arrays are "reifiable" types. Java uses this // type information to verify array types (including casts) at runtime. // But arrays of generic type A[] is actually just an Object[]. // In generalclass and in generalclass, // the ar arrays of the two classes are both of type Object[], // contradicting the reifiability of Array types. This is why // generic arrays are not allowed. But you can still have them // using the type cast, albeit with a warning. So why all the fuss? // It comes down to the fact that arrays are allowed to be // "covariant" in Java, but they shouldn't be. // That is, String[] is considered a subtype of Object[]: Object[] oa = new String[10]; // compiles - arrays are "covariant" oa[0] = "abc"; oa[1] = (Double)3.5; // this compiles too, obviously not type safe. String[] sa = (String[]) new Object[10]; // this also compiles // Arrays are made covariant because pre-generics Java needed it. // But generic structures should observe the rules of type variance. // A type can be covariant only if it appears exclusively as a // returned type (in the codomain of functions), and contravariant // only if it appears exclusively as an argument type (in the // domain of functions.) If a type appears both as an argument type // and a return type, it can't be either (must be invariant). // When we assign to an array, A[i] = x, x is used as an input // argument (i.e. void set(T x)). But when we read from an array, // auto x=A[i], the value in the array is returned (i.e. T get()). // Any data structure with both a get-like method and a set-like // method must be invariant over its value type. //generalclass g = // (generalclass)new generalclass(); // won't compile even with cast, (not just warning but error). // However, the cast `(String[]) new Object[10]` is allowed. // So although java does not allow generic arrays, the *type cast* // `(T[])new Object[10]` is still allowed, albeit with a warning // that it can't verify the cast (of course it can't). // So in the end, the restriction of not allowing generic // array creation is FUTILE because of the original mistake // of allowing for covariant arrays. // But there is hope ... // ArrayList n= new ArrayList() does not compile, // so it's perfectly fine to use generic "ArrayLists" instead of // generic arrays. One of the benefits of ArrayList over arrays // is that they're more type safe, because the rules of variance are // properly observed. Unfortunately, Java does not allow operator // overloads so one cannot use the familiar array syntax, A[i], with // ArrayLists and other structures. }//function f }// a java generic class and it's problems. // compile with javac -Xlint:unchecked generics1.java