Related
I can't seem to create an array of class instances that have a field of a generic.
Like so:
class Main {
public static void main(String args[]) {
Foo<String> foo = new Foo<>();
}
}
class Foo<K> {
public Bar[] arr;
Foo() {
arr = (Bar[]) new Object[10];
}
void push(int index, K value) {
arr[index].value = value;
}
class Bar {
K value;
}
}
REPL
Which gives me
Exception in thread "main" java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [LFoo$Bar; ([Ljava.lang.Object
But I do know for a fact that an array of generics, like so
T[] arr = (T[]) new Object[10];
can be created.
But I do know for a fact that an array of generics, like so
T[] arr = (T[]) new Object[10];
can be created.
Nope. This is incorrect, and that is the root of your confusion.
You can't create 'generic arrays'. Generics are a figment of the compiler's imagination. The JVM itself doesn't have a clue as to what they are. Most generics information poofs out of existence during the compilation step; the few generics that survive (in signatures), are treated as comments by the JVM: The JVM does not know what it means and doesn't care.
T[] arr = (T[]) new Object[10];
is a fancy way of telling the compiler not to complain, and to inject a few casts here and there. That is all that line does.
You've created a new object array. It's not a T[] array. The cast operator does not convert anything. It merely asserts types (is this thing indeed of that type? If yes, great, do nothing. If no, throw an exception). Given that this is by definition a runtime thing (if the compiler knows some expression is of some type, the cast obviously isn't needed), and generics fundamentally do not exist at runtime - then this cast operation truly does nothing. There's nothing to check. It cannot fail and has no bytecode. It is there just to tell the compiler that you take responsibility, as the compiler can no longer ascertain type safety for you.
What you've done is made an array of objects, and assigned it to a variable of type T[]. Local variables don't even exist in class files, they are 'compiled out', so to speak. Hence, you most definitely did not just 'make' a new 'array of T', in any sense you care to take that sentence.
So, having covered that:
Collections of any stripe (be they java.util.List or an array) are invariant. invariant is a concept in typing systems. You are presumably more used to covariant type systems: Java's basic type system is covariant. Covariant means: A subtype of a thing is just as good. In other words, this:
Object o = "hello";
is fine, because String is a subtype of Object, and is covariant - subtypes are fine. This isn't simply 'because the java spec says so' - it makes a more fundamental sense.
But in (writable) collections it just breaks down. Imagine that the 'component type' of a collection was covariant, then I could do this:
Integer[] integers = new Integer[10];
Number[] numbers = integers;
numbers[0] = 5.5;
System.out.println(integers[0]); // hmmmmm!!!
Go through those lines step by step - they explain why covariance is wrong. Hence, for the sake of collection types, java's typing system is invariant. You cannot assign an Integer[] to a Number[]. Unfortunately, 30 years ago when the initial java spec that introduced arrays was written, this wasn't thought through all that far and arrays have turned to be really really weird constructs as a consequence: Their toString, equals and hashCode implementations are well defined, but the definition is: These methods are effectively completely useless, and they can't grow or shrink.
collections do a much better job at it - you can't assign a List<Integer> list to a variable of type List<Number> either, but unlike with arrays, you can ask java for covariance and even contravariance: List<? extends Number> gives you covariance and the compiler acts accordingly - for example, you can't add anything to a List<? extends Number> because there's no way to know what you could possibly add - perhaps that variable is pointing at a List<Integer>, perhaps it is pointing at a List<Double>, and no value is both a Double and Integer simultaneously (except trivially and not useful, but for completeness: null, literally - which actually works; you can call list.add(null) if list's type is List<? extends Number> - it is the only thing you can add).
One of those weird things about arrays is that any array can be assigned / is type-compatible with Object[], even though this is wrong. It's a throwback in order to allow working with objects (what you really need is a ?[] - just like you can have a List<?>, but that syntax did not exist at the time).
Arrays, unlike generics, are not a figment of the compiler's imagination: The runtime actually knows about them, tracks them, etcetera. You can e.g. do this:
Object[] o = new String[10]; // weird, but legal java.
o[0] = 5; // compiles, but throws an ArrayStoreException at runtime.
o.getClass().getComponentType(); // returns 'String.class'
and note how generics doesn't work like this at all:
List<Object> o = new ArrayList<String>(); // does not compile.
// .. but for funsies let's force the issue:
List<String> strings = new ArrayList<String>();
List /* raw */ hack = strings;
List<Object> objects = hack; // compiles with warnings.
hack.add(5.0); // perfectly fine, compiles and runs without error.
String y = strings.get(0); // compiles perfectly fine.... but throws ClassCastException at runtime.
objects.get????? // there is no way to get 'String.class' from this thing. At all.
In other words, what you fundamentally want to do (treat an array of Dogs as an array of Animals) doesn't work - not because of java, but because of the universe: You can add parrots to an array of animal, hence why you can't treat an array of Dogs as an array of Animals. A java specific hacky thing with arrays is that you CAN treat an array of anything as an array of Object specifically (and only Object[], that is hardcoded in the spec), which is wrong and leads to all sorts of broken code, but it's in the language solely to give you an option to work with arrays as a generalized concept, because java 1.0 through 1.4 didn't have generics.
TL;DR: Do not use arrays. They are weird and mostly useless. Make List<T>s instead.
The exception caused by this line :
arr = (Bar[]) new Object[10];
To simplify the explanation i just want to replace your code with this example :
public class Main {
public static void main(String args[]) {
Bar b = (Bar) new Object();
}
}
class Bar{
}
This line Bar b = (Bar) new Object(); compile because Bar extends Object , an this inheritence relation let your code compile, but at RunTime Java find that you are trying to cast an instance of Object class ( created using new Object()) ,and that's impossible because Object is a superType of Bar .
You did the same thing just you added the [ ].
With generics , any T class must extends (directly or indirectly) Object class , T can be Object himSelf , so the code compile and can throw a ClassCastException .
Let's say I have a class with raw type declaration as List (list1). It's just a simple example:
public class Wildcards {
public boolean contains(List list1, List<?> list2){
/*for(Object element: list1) {
if (list2.contains(element)) {
return true;
}
}*/
list1.add("12sdf34"); //insert String
return false;
}
}
In list1 I insert String value. (If I use unbounded wildcards for list1 as for list2 it would be more secure and it would be compilation error). However here is a raw type.
Now let's use this method as following:
List<Integer> list1 = new ArrayList<Integer>();
List<Double> list2 = new ArrayList<Double>();
System.out.println("Contains? " + (new Wildcards()).contains(list1, list2));
System.out.println("List1 element: " + list1.get(0));
I will not get any errors and receive the following result:
Contains? false
List1 element: 12sdf34
Could anyone explain how it might be as I initialized list1 as List of Integers?
At runtime, generic type parameters are erased, which you can effectively think of as meaning that parameter types are swapped for Object. Thus, if compile has succeeded, you can add any object of any type to any list.
All generic type-checking is done at compile time, and the compiler can't raise an error for raw types, otherwise code written for Java 1.4 (pre-generics) or older wouldn't compile. Hence, instead, it raises the "rawtypes" warning.
The answer is two-folded. First, generics are erased during compilation. The second part is the actual implementation of ArrayList. Let's start with type erasure.
At compile time, all generic types are replaced with their upper bound. For example a generic parameter <T extends Comparable<T>> collapses to Comparable<T>. If no upper bound is given, it is replaced with Object. This makes generics an efficient tool for type-checking at compile-time, but we loose all type information at runtime. Project Valhalla may or may not fix that in the future. Since your method deals with raw- and unbounded types, the compiler assumes Object as generic type and thus list1.add("12sdf34"); passes type checking.
So why don't you get some exception at runtime? Why does ArrayList not "recognize" that the value you give it is of wrong type? Because ArrayList uses an Object[] as its backing buffer. Next logical question is: Why does ArrayList use an Object[] instead of an T[]? Because of type erasure: we cannot instantiate anything of T or T[] at runtime. Furthermore, the fact that arrays are covariant and retained, while generics are invariant and erased, makes for an explosive product if these two are mixed.
For your program, that means that neither a compilation-error nor a runtime exception will be thrown and thus your ArrayList<Integer> may contain a String. You will, however, get into troubles by writing
...
System.out.println("Contains? " + (new Wildcards()).contains(list1, list2));
System.out.println("List1 element: " + list1.get(0))
int i = list1.get(0);
From the view of the lexer, the code is still valid. But at runtime, the assignment will generate a ClassCastException. This is one of the reasons why raw types should be avoided.
From Effective Java by Joshua Bloch,
Arrays differ from generic type in two important ways. First arrays are covariant. Generics are invariant.
Covariant simply means if X is subtype of Y then X[] will also be sub type of Y[]. Arrays are covariant As string is subtype of Object So
String[] is subtype of Object[]
Invariant simply means irrespective of X being subtype of Y or not ,
List<X> will not be subType of List<Y>.
My question is why the decision to make arrays covariant in Java? There are other SO posts such as Why are Arrays invariant, but Lists covariant?, but they seem to be focussed on Scala and I am not able to follow.
Via wikipedia:
Early versions of Java and C# did not include generics (a.k.a. parametric polymorphism).
In such a setting, making arrays invariant rules out useful polymorphic programs.
For example, consider writing a function to shuffle an array, or a function that tests two arrays for equality using the Object.equals method on the elements. The implementation does not depend on the exact type of element stored in the array, so it should be possible to write a single function that works on all types of arrays. It is easy to implement functions of type
boolean equalArrays (Object[] a1, Object[] a2);
void shuffleArray(Object[] a);
However, if array types were treated as invariant, it would only be possible to call these functions on an array of exactly the type Object[]. One could not, for example, shuffle an array of strings.
Therefore, both Java and C# treat array types covariantly. For instance, in C# string[] is a subtype of object[], and in Java String[] is a subtype of Object[].
This answers the question "Why are arrays covariant?", or more accurately, "Why were arrays made covariant at the time?"
When generics were introduced, they were purposefully not made covariant for reasons pointed out in this answer by Jon Skeet:
No, a List<Dog> is not a List<Animal>. Consider what you can do with a List<Animal> - you can add any animal to it... including a cat. Now, can you logically add a cat to a litter of puppies? Absolutely not.
// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new List<Dog>();
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?
Suddenly you have a very confused cat.
The original motivation for making arrays covariant described in the wikipedia article didn't apply to generics because wildcards made the expression of covariance (and contravariance) possible, for example:
boolean equalLists(List<?> l1, List<?> l2);
void shuffleList(List<?> l);
The reason is that every array knows its element type during runtime, while generic collection doesn't because of type erasure.
For example:
String[] strings = new String[2];
Object[] objects = strings; // valid, String[] is Object[]
objects[0] = 12; // error, would cause java.lang.ArrayStoreException: java.lang.Integer during runtime
If this was allowed with generic collections:
List<String> strings = new ArrayList<String>();
List<Object> objects = strings; // let's say it is valid
objects.add(12); // invalid, Integer should not be put into List<String> but there is no information during runtime to catch this
But this would cause problems later when someone would try to access the list:
String first = strings.get(0); // would cause ClassCastException, trying to assign 12 to String
May be this help:-
Generics are not covariant
Arrays in the Java language are covariant -- which means that if Integer extends Number (which it does), then not only is an Integer also a Number, but an Integer[] is also a Number[], and you are free to pass or assign an Integer[] where a Number[] is called for. (More formally, if Number is a supertype of Integer, then Number[] is a supertype of Integer[].) You might think the same is true of generic types as well -- that List<Number> is a supertype of List<Integer>, and that you can pass a List<Integer> where a List<Number> is expected. Unfortunately, it doesn't work that way.
It turns out there's a good reason it doesn't work that way: It would break the type safety generics were supposed to provide. Imagine you could assign a List<Integer> to a List<Number>.
Then the following code would allow you to put something that wasn't an Integer into a List<Integer>:
List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));
Because ln is a List<Number>, adding a Float to it seems perfectly legal. But if ln were aliased with li, then it would break the type-safety promise implicit in the definition of li -- that it is a list of integers, which is why generic types cannot be covariant.
An important feature of parametric types is the ability to write polymorphic algorithms, i.e. algorithms that operate on a data structure regardless of its parameter value, such as Arrays.sort().
With generics, that's done with wildcard types:
<E extends Comparable<E>> void sort(E[]);
To be truly useful, wildcard types require wildcard capture, and that requires the notion of a type parameter. None of that was available at the time arrays were added to Java, and makings arrays of reference type covariant permitted a far simpler way to permit polymorphic algorithms:
void sort(Comparable[]);
However, that simplicity opened a loophole in the static type system:
String[] strings = {"hello"};
Object[] objects = strings;
objects[0] = 1; // throws ArrayStoreException
requiring a runtime check of every write access to an array of reference type.
In a nutshell, the newer approach embodied by generics makes the type system more complex, but also more statically type safe, while the older approach was simpler, and less statically type safe. The designers of the language opted for the simpler approach, having more important things to do than closing a small loophole in the type system that rarely causes problems. Later, when Java was established, and the pressing needs taken care of, they had the resources to do it right for generics (but changing it for arrays would have broken existing Java programs).
Arrays are covariant for at least two reasons:
It is useful for collections that hold information which will never change to be covariant. For a collection of T to be covariant, its backing store must also be covariant. While one could design an immutable T collection which did not use a T[] as its backing store (e.g. using a tree or linked list), such a collection would be unlikely to perform as well as one backed by an array. One might argue that a better way to provide for covariant immutable collections would have been to define a "covariant immutable array" type they could use a backing store, but simply allowing array covariance was probably easier.
Arrays will frequently be mutated by code which doesn't know what type of thing is going to be in them, but won't put into the array anything which wasn't read out of that same array. A prime example of this is sorting code. Conceptually it might have been possible for array types to include methods to swap or permute elements (such methods could be equally applicable to any array type), or define an "array manipulator" object which hold a reference to an array and one or more things that had been read from it, and could include methods to store previously-read items into the array from which they had come. If arrays were not covariant, user code would not be able to define such a type, but the runtime could have included some specialized methods.
The fact that arrays are covariant may be viewed as an ugly hack, but in most cases it facilitates the creation of working code.
I think they made a wrong decision at the first place that made array covariant. It breaks the type safety as it described here and they got stuck with that because of backward compatibility and after that they tried to not make the same mistake for generic.
And that's one of the reasons that Joshua Bloch prefers lists to arra ys in Item 25 of book "Effective Java(second edition)"
Generics are invariant: from JSL 4.10:
...Subtyping does not extend through generic types: T <: U does not
imply that C<T> <: C<U> ...
and a few lines further, JLS also explains that Arrays are covariant (first bullet):
4.10.3 Subtyping among Array Types
My take: When code is expecting an array A[] and you give it B[] where B is a subclass of A, there's only two things to worry about: what happens when you read an array element, and what happens if you write it. So it's not hard to write language rules to ensure that type safety is preserved in all cases (the main rule being that an ArrayStoreException could be thrown if you try to stick an A into a B[]). For a generic, though, when you declare a class SomeClass<T>, there can be any number of ways T is used in the body of the class, and I'm guessing it's just way too complicated to work out all the possible combinations to write rules about when things are allowed and when they aren't.
From Effective Java by Joshua Bloch,
Arrays differ from generic type in two important ways. First arrays are covariant. Generics are invariant.
Covariant simply means if X is subtype of Y then X[] will also be sub type of Y[]. Arrays are covariant As string is subtype of Object So
String[] is subtype of Object[]
Invariant simply means irrespective of X being subtype of Y or not ,
List<X> will not be subType of List<Y>.
My question is why the decision to make arrays covariant in Java? There are other SO posts such as Why are Arrays invariant, but Lists covariant?, but they seem to be focussed on Scala and I am not able to follow.
Via wikipedia:
Early versions of Java and C# did not include generics (a.k.a. parametric polymorphism).
In such a setting, making arrays invariant rules out useful polymorphic programs.
For example, consider writing a function to shuffle an array, or a function that tests two arrays for equality using the Object.equals method on the elements. The implementation does not depend on the exact type of element stored in the array, so it should be possible to write a single function that works on all types of arrays. It is easy to implement functions of type
boolean equalArrays (Object[] a1, Object[] a2);
void shuffleArray(Object[] a);
However, if array types were treated as invariant, it would only be possible to call these functions on an array of exactly the type Object[]. One could not, for example, shuffle an array of strings.
Therefore, both Java and C# treat array types covariantly. For instance, in C# string[] is a subtype of object[], and in Java String[] is a subtype of Object[].
This answers the question "Why are arrays covariant?", or more accurately, "Why were arrays made covariant at the time?"
When generics were introduced, they were purposefully not made covariant for reasons pointed out in this answer by Jon Skeet:
No, a List<Dog> is not a List<Animal>. Consider what you can do with a List<Animal> - you can add any animal to it... including a cat. Now, can you logically add a cat to a litter of puppies? Absolutely not.
// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new List<Dog>();
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?
Suddenly you have a very confused cat.
The original motivation for making arrays covariant described in the wikipedia article didn't apply to generics because wildcards made the expression of covariance (and contravariance) possible, for example:
boolean equalLists(List<?> l1, List<?> l2);
void shuffleList(List<?> l);
The reason is that every array knows its element type during runtime, while generic collection doesn't because of type erasure.
For example:
String[] strings = new String[2];
Object[] objects = strings; // valid, String[] is Object[]
objects[0] = 12; // error, would cause java.lang.ArrayStoreException: java.lang.Integer during runtime
If this was allowed with generic collections:
List<String> strings = new ArrayList<String>();
List<Object> objects = strings; // let's say it is valid
objects.add(12); // invalid, Integer should not be put into List<String> but there is no information during runtime to catch this
But this would cause problems later when someone would try to access the list:
String first = strings.get(0); // would cause ClassCastException, trying to assign 12 to String
May be this help:-
Generics are not covariant
Arrays in the Java language are covariant -- which means that if Integer extends Number (which it does), then not only is an Integer also a Number, but an Integer[] is also a Number[], and you are free to pass or assign an Integer[] where a Number[] is called for. (More formally, if Number is a supertype of Integer, then Number[] is a supertype of Integer[].) You might think the same is true of generic types as well -- that List<Number> is a supertype of List<Integer>, and that you can pass a List<Integer> where a List<Number> is expected. Unfortunately, it doesn't work that way.
It turns out there's a good reason it doesn't work that way: It would break the type safety generics were supposed to provide. Imagine you could assign a List<Integer> to a List<Number>.
Then the following code would allow you to put something that wasn't an Integer into a List<Integer>:
List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));
Because ln is a List<Number>, adding a Float to it seems perfectly legal. But if ln were aliased with li, then it would break the type-safety promise implicit in the definition of li -- that it is a list of integers, which is why generic types cannot be covariant.
An important feature of parametric types is the ability to write polymorphic algorithms, i.e. algorithms that operate on a data structure regardless of its parameter value, such as Arrays.sort().
With generics, that's done with wildcard types:
<E extends Comparable<E>> void sort(E[]);
To be truly useful, wildcard types require wildcard capture, and that requires the notion of a type parameter. None of that was available at the time arrays were added to Java, and makings arrays of reference type covariant permitted a far simpler way to permit polymorphic algorithms:
void sort(Comparable[]);
However, that simplicity opened a loophole in the static type system:
String[] strings = {"hello"};
Object[] objects = strings;
objects[0] = 1; // throws ArrayStoreException
requiring a runtime check of every write access to an array of reference type.
In a nutshell, the newer approach embodied by generics makes the type system more complex, but also more statically type safe, while the older approach was simpler, and less statically type safe. The designers of the language opted for the simpler approach, having more important things to do than closing a small loophole in the type system that rarely causes problems. Later, when Java was established, and the pressing needs taken care of, they had the resources to do it right for generics (but changing it for arrays would have broken existing Java programs).
Arrays are covariant for at least two reasons:
It is useful for collections that hold information which will never change to be covariant. For a collection of T to be covariant, its backing store must also be covariant. While one could design an immutable T collection which did not use a T[] as its backing store (e.g. using a tree or linked list), such a collection would be unlikely to perform as well as one backed by an array. One might argue that a better way to provide for covariant immutable collections would have been to define a "covariant immutable array" type they could use a backing store, but simply allowing array covariance was probably easier.
Arrays will frequently be mutated by code which doesn't know what type of thing is going to be in them, but won't put into the array anything which wasn't read out of that same array. A prime example of this is sorting code. Conceptually it might have been possible for array types to include methods to swap or permute elements (such methods could be equally applicable to any array type), or define an "array manipulator" object which hold a reference to an array and one or more things that had been read from it, and could include methods to store previously-read items into the array from which they had come. If arrays were not covariant, user code would not be able to define such a type, but the runtime could have included some specialized methods.
The fact that arrays are covariant may be viewed as an ugly hack, but in most cases it facilitates the creation of working code.
I think they made a wrong decision at the first place that made array covariant. It breaks the type safety as it described here and they got stuck with that because of backward compatibility and after that they tried to not make the same mistake for generic.
And that's one of the reasons that Joshua Bloch prefers lists to arra ys in Item 25 of book "Effective Java(second edition)"
Generics are invariant: from JSL 4.10:
...Subtyping does not extend through generic types: T <: U does not
imply that C<T> <: C<U> ...
and a few lines further, JLS also explains that Arrays are covariant (first bullet):
4.10.3 Subtyping among Array Types
My take: When code is expecting an array A[] and you give it B[] where B is a subclass of A, there's only two things to worry about: what happens when you read an array element, and what happens if you write it. So it's not hard to write language rules to ensure that type safety is preserved in all cases (the main rule being that an ArrayStoreException could be thrown if you try to stick an A into a B[]). For a generic, though, when you declare a class SomeClass<T>, there can be any number of ways T is used in the body of the class, and I'm guessing it's just way too complicated to work out all the possible combinations to write rules about when things are allowed and when they aren't.
Tiger class is extends from Animal Class.
When I declare: List<Animal> tiger = new ArrayList<Tiger>();. I will error at compile-time.
But, I think this line is true for polymorphism. Who can explain for me, please.
you cannot do
List<Animal> tiger = new ArrayList<Tiger>();
that in java. Generic type on left have to be exacly equal (or may not have to be equal, if wild cards are in game - ? extends T or ? super T) to generic type on right.
If it was possible then it would be impossible to add new Lion to list declared as list of Animals - that would make no sense.
What you can do is:
List<Animal> tigers = new ArrayList<Animal>();
tigers.add(new Tiger());
(all family of Animals, including Tigers)
or:
List<? extends Animal> tigers = new ArrayList<Tiger>();
tigers.add(new Tiger()); // Adding is immpossible now - list can be read only now!
(only subclasses of Animal) - list can be read only now!
A List<Animal> would allow you to add a cute little puppy. Which the tigers in the ArrayList<Tiger> would then eat.
Polymorphically speaking, you would have
List<Tiger> tigers = new ArrayList<Tiger>();
Which would allow you to replace use any implementation of List<Tiger> if you so desired, relying upon and using the functionality as defined by the interface. What you are trying to do isn't polymorhpism, it is simply an unsafe conversion (particularly for the aforementioned puppy) and is not going to work for reasons illustrated above.
The reasons for this are based on how Java implements generics. The best way I have found to explain it is by using arrays first.
An Arrays Example
With arrays you can do this:
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
But, what would happen if you try to do this?
Number[0] = 3.14; //attempt of heap pollution
This last line would compile just fine, but if you run this code, you could get an ArrayStoreException.
This means that you can fool the compiler, but you cannot fool the runtime type system. And this is so because arrays are what we call reifiable types. This means that at runtime Java knows that this array was actually instantiated as an array of integers which simply happens to be accessed through a reference of type Number[].
So, as you can see, one thing is the real type of the object, an another thing is the type of the reference that you use to access it, right?
The Problem with Java Generics
Now, the problem with Java generic types is that the type information is discarded by the compiler and it is not available at run time. This process is called type erasure. There are good reason for implementing generics like this in Java, but that's a long story, and it has to do with binary compatibility with pre-existing code.
But the important point here is that since, at runtime there is no type information, there is no way to ensure that we are no committing heap pollution.
For instance,
List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts;
myNums.add(3.14); //heap polution
If the Java compiler does not stop you from doing this at compile time, the runtime type system cannot stop you either, because there is no way, at runtime, to determine that this list was supposed to be a list of integers only. The Java runtime would let you put whatever you want into this list, when it should only contain integers, because when it was created, it was declared as a list of integers.
As such, the designers of Java made sure that you cannot fool the compiler. If you cannot fool the compiler (as we can do with arrays) you cannot fool the runtime type system either.
As such, we say that generic types are non-reifiable.
Evidently, this would hamper pollymorphism as well pointed out. The solution is to learn to use two powerful features of Java generics known as covariance and contravariance.
Covariance
With covariance you can read items from a structure, but you cannot write anything into it. All these are valid declarations.
List<? extends Number> myNums = new ArrayList<Integer>();
List<? extends Number> myNums = new ArrayList<Float>()
List<? extends Number> myNums = new ArrayList<Double>()
And you can read from myNums:
Number n = myNums.get(0);
Because you can be sure that whatever the actual list contains, it can be upcasted to a Number (after all anything that extends Number is a Number, right?)
However, you are not allowed to put anything into a covariant structure.
myNumst.add(45L);
This would not be allowed, because Java cannot guarantee what is the actual type of the real object. It can be anything that extends Number, but the compiler cannot be sure. So you can read, but not write.
Contravariance
With contravariance you can do the opposite. You can put things into a generic structure, but you cannot read out from it.
List<Object> myObjs = new List<Object();
myObjs.add("Luke");
myObjs.add("Obi-wan");
List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);
In this case, the actual nature of the object is a List of Objects, and through contravariance, you can put Numbers into it, basically because numbers have Object as the common ancestor. As such, all Numbers are objects, and therefore this is valid.
However, you cannot safely read anything from this contravariant structure assuming that you will get a number.
Number myNum = myNums.get(0); //compiler-error
As you can see, if the compiler allowed you to write this line, you would get a ClassCastException at runtime.
Get/Put Principle
As such, use covariance when you only intend to take generic values out of a structure, use contravariance when you only intend to put generic values into a structure and use the exact generic type when you intend to do both.
The best example I have is the following that copies any kind of numbers from one list into another list.
public static void copy(List<? extends Number> source, List<? super Number> destiny) {
for(Number number : source) {
destiny.add(number);
}
}
Thanks to the powers of covariance and contravariance this works for a case like this:
List<Integer> myInts = asList(1,2,3,4);
List<Integer> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();
copy(myInts, myObjs);
copy(myDoubles, myObjs);
I agree it's confusing. Here's what could go wrong if that type of statement were allowed:
List<Tiger> tigers = new ArrayList<Tiger>(); // This is allowed.
List<Animal> animals = tigers; // This isn't allowed.
tigers.add(new Lion()); // This puts a Lion in tigers!
Oh. Yes, you true. your code is right in Polymorphism thinking. But, look at my code that I use for a long time when I just a novie.
And you will see why you should thank to Collection:
class Animal{
}
class Tiger extends Animal{
}
public class Test {
public static void main (String[] args){
List<Animal> animal = new ArrayList<Animal>(); //obvious
List<Tiger> tiger = new ArrayList<Tiger>(); //obvious
List<Animal> tigerList = new ArrayList<Tiger>(); //error at COMPILE-TIME
Animal[] tigerArray = new Tiger[2]; //like above but no error but....
Animal tmpAnimal = new Animal();
/*
* will meet RUN-TIME error at below line when use Array
* but Collections can prevent this before at COMPILE-TIME
*/
tigerArray[0] = tmpAnimal; //Oh NOOOO. RUN-TIME EXCEPTION
/*
* Below examples WRONG for both Collection and Array
* Because here is Polymorphism problem. I just want to make more clearer
*/
List<Tiger> animalList = new ArrayList<Animal>();
Tiger[] animalArray = new Animal[2];
}
}
As you see my above code, Collections is so "intelligent" when prevent you use List<Animal> tigerList = new ArrayList<Tiger>();
You should imagine if someone use: tigerList.add(a Lion, a Cat,......); --->ERROR.
So, to Sumarize, here is the different:
ARRAY: check at RUN-TIME. You will feel more comfortable but DANGEROUS
COLLECTIONS: check at COMPILE-TIME. You will feel angry because it
notice error. But, you will prevent errors when Running. !!!!
Maybe below post is over of your question. But I suggest you use WildCard like:
List<? extends Animal> tigerList = new ArrayList<Tiger>();
Yes. You might see the idea behind this line. But, the MOST INTERESTING THING is: it will prevent you change the List. in this case, add method.
For example:
tigerList.add(TIGER); ERROR
yes. It will prevent you add a tiger, too :)