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 :)
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 .
It seems either this concept is not straight forward to understand, or the so many long articles on the web are not really explaining well. I appreciate if someone can explain in a clear and short way.
I have read the examples in this blog and this video.
The conclusion I draw so far is:
In Java, arrays are covariant and generics are invariant.
Arrays are covariant:
Number[] myNums = new Integer[3]; // compile ok
but.. if I do this, run time error though compile ok:
myNums[0] = 2.1; // compile ok, run time not ok
What is the point of having array covariant if run time will be NOT ok? This question may actually refer to "What's the point of covariant?"
Generics are invariant:
List<Number> myNums = new ArrayList<Integer>(); // compile not ok
But amazingly, there's a way to make generics covariant/cotravariant, use wildcard:
List<? extends Number> myNums1 = new ArrayList<Integer>(); // convariance, compile ok
List<? super Integer> myNums2 = new ArrayList<Number>(); // contravariance, compile ok
Even there's the way to make it covariant or contravariant, I still cannot do things like
myNums1.add(New Integer(1));
What is the point of all of this?
Please, is there anyone help me to clear out all this confusion?
In Java, arrays are covariant and generics are invariant.
Yes for arrays. For Java, it is right for generics without wildcard but generics with wildcard goes over that limitation.
What is the point of having array covariant if run time will be NOT
ok?
No value. It is an array specific drawback that was not reproduced such as in generics.
What is the point of all of this?
About Java generics, the thing is that these are not absolutely invariant, covariant or contravariant because the invariant is the safer way but it is also the most restrictive in terms of programming features. I illustrate below.
So Java (as well as other strong typed languages) made the choice to provide distinct generics flavors : invariant, covariant and contravariant while ensuring the type safety in any case (while you respect compiler warnings of course).
But amazingly, there's a way to make generics covariant/cotravariant,
use wildcard:
Not really amazing.
Without covariance, you lose all possibilities to pass a variable declared with generic type in a method that accepts a supertype of this variable. If this method does only reading on the generic variable that is passed as parameter and that the compiler can ensure that : you have no type storing issue at runtime since you don't add anything in it. So you want to use an upper bounded wildcard.
List<Integer> integers = ...;
List<Long> longs = ...;
List<Float> floats = ...;
doThat(integers); // compile ok
doThat(longs); // compile ok
doThat(floats); // compile ok
//...
public void doThat(List<? extends Number> list){
for (Number n : list){ // compile ok : reading
//...
}
// but the compiler doesn't allow to store
list.add(Integer.valueOf(1));
}
Similarly without contravariance you lose all possibilities to add safely things in a generic type. If the compiler ensures that the contravariance is safe because we can only add supertype things in the generic type variable passed to , why do you want to lose this ability ? So you want to use a lower bounded wildcard.
List<Integer> integers = ...;
List<Long> longs = ...;
List<Float> floats = ...;
List<Number> numbers = ...;
doThat(integers); // compile ok
doThat(numbers); // compile ok
doThat(longs); // compile fails
doThat(floats); // compile fails
//...
public void doThat(List<? super Integer> list){
// the compiler allows to add that as type safe
list.add(Integer.valueOf(1));
// but it doesn't allow to add no safe things
list.add(Float.valueOf(1f));
}
Consider the following code.
List<Animal> x = new ArrayList<>();
x.add(new Tiger());
for(Animal d : x) {
System.out.println(d.getClass());
}
The above prints out
Tiger
Another example is
public static <E> void containsElement(E [] elements, E element){
for (E e : elements){
System.out.println(e.getClass());
}
}
Always prints out the correct class
If I recall correctly, java cannot find the generic type at run time because it performs type erasure. Am I setting this up wrong?
In runtime, you will always get correct class.
Type erasure happens during the compilation time when compiler generates the byte code. When the type is unbounded, compiler replaces with Object and when its bounded, it replaces with the higher bound. You can get the details of type erasure nicely in this tutorial https://docs.oracle.com/javase/tutorial/java/generics/genTypes.html
The List has type erasure, but the Object's class doesn't have anything to do with Java Generics and type erasure.
The same would happen without using generics at all.
For example:
Animal a = new Tiger();
System.out.println(a.getClass());
would also print out Tiger.
The LIST no longer knows what its generics param is at runtime. In other words, if I hand 'x' to a method, you can tell that it is a List (for example, x.getClass() would give you something listy, and x instanceof List would be true), but there is no way to tell that it is a list of Animals. There's no .getComponentType() method or any such.
The objects inside the list know they are animals, so, once you get one object out (which the for loop you wrote in your code does), you can ask the object what it is.
Imagine that list had nothing in it, for example. No way to tell. Consider also, this:
List<Number> numbers = new ArrayList<Number>();
List<Integer> integers = new ArrayList<Integer>();
numbers.add(Integer.valueOf(1));
integers.add(Integer.valueOf(1));
From here, you can ask for the first element in either list and it'll be of type java.lang.Integer. But there's no way to know that the list numbers was defined to take any Number (Integer is a subtype of Number), and integers was defined to take only integers.
Every object knows its own type at runtime (accessible through getClass()). Type erasure is where generic types (i.e. List<T>) "forget" what type they have at runtime, which means that the JVM can't tell the difference between a List<Integer> and a List<String>; it just has two Lists. This can be demonstrated by the fact that new ArrayList<Integer>().getClass() == new ArrayList<String>().getClass() evaluates to true.
Consider the following code:
MyClass myClass= new MyExtendedClass();
myClass.method();
where MyExtendedClass be a subtype of MyClass. As i understood on a compile state compiler on the second string compiler checked of existence method method()in a source code of MyClass. Is it correct reasoning? Now consider
List<Integer> ints= new ArrayList<Integer>();
ints.add(2);
List<? extends Integer> lst = ints;
lst.get(0);
Where i can see the sources of List<? extends Integer>?
Now consider:
new ArrayList<Integer>().getClass().equals(
new ArrayList<String>().getClass());// return true.
So
In a run time classes ArrayList<Integer> and ArrayList<String> are equals, but on a compile state it is not true. Why? Where are the sources of ArrayList<String> and ArrayList<Integer>?
Search for type erasure. To start you can refer http://docs.oracle.com/javase/tutorial/java/generics/erasure.html
For myClass.method(); to be legal, the MyClass class or interface must declare the method method. That is correct.
The reason that the getClass results are equal for ArrayList<Integer> and ArrayList<String> is that at runtime, type erasure erases the generic type parameters, leaving ArrayList in both cases, and of course they are both equal. It is only the compiler that can distinguish between ArrayList<Integer> and ArrayList<String>. The JVM never knew about generics, so type erasure occurs to be backwards compatible with pre-generics (pre-Java 1.5) applications.
Source code for List
Source code for ArrayList
Java does not work the way that C++ does. In C++ a "template" causes a complete generation of a new class with that referenced type in it. Java, however, does not generate a new class for every templated instantiation. Instead, the type information is carried as a setting at compile and run time. So ArrayList<Integer> and ArrayList<String> make use of the same actual class.
The type checking is in the compiler, and for simple classes this is easy.
class Gem {...}
class Ruby extends Gem {...}
class Diamond extends Gem {...}
Gem a;
Ruby b;
Diamond c;
a = b; //no problem allowed
b = a; //type problem, need to cast it!
b = c; //never allowed!
But parameterized classes are much more complicated
List<Gem> la;
List<Ruby> lb;
List<Diamond> lc;
la = lb; //not allowed because type is different even though Ruby extends Gem
List<? extends Gem> ld;
ld = la; //allowed
ld = lb; //also allowed
ld = lc; //also allowed
The wildcards are needed to allow you to have a collection variable that holds more than one kind of collection, where the differences are in the type parameter. In all cases the object class (and source code) remains the same.
In java one can cast instance of a class B into an instance of class A provided that B extends A.
Can this be done also for type-parametrized classes? For instance, for B extending A, I'd like to do the following:
List<A> l = new ArrayList<B>();
I think this should be legal, but the compiler doesn't agree with me on this point, so I tricked it with the following hack:
List<A> l = (List<A>)(List) new ArrayList<B>();
…but I think that I'll be hit by a velociraptor. Is there an elegant way of doing this?
I think this should be legal, but the compiler doesn't agree with me on this point
Generally, when humans and compilers disagree, it is safe to take the compiler's side. Instead of tricking the compiler, you should first understand why it does not allow the conversion.
Consider this: you make a List<B>, like this:
List<B> listB = new ArrayList<B>();
Everyone is happy so far. Now let's do what you think is correct:
// This does not compile, but let's pretend that it does
List<A> listA = (List<A>)listB;
At this point, we have listA that is entirely ours! Let's play with it: let's say we have another class C extending A. Can we do this?
listA.add(new C()); // Why not? The compiler should allow it!
But wait, do you see what we have done? Our listB contains a C now! This is why the compiler does not allow the conversion; this is also why your code is not safe in cases when you attempt to add anything to the list.
To work around this issue, you can use wildcards: List<? extends A>. However, this will limit your lists to read-only access. If you must allow writing (and you know that you are going to set only the right stuff) use the non-generic List instead.
List<? extends A> l = new ArrayList<B>();
You should read about generics in java here
Two ways to do it.......
- This way of handling the Collections are done, cause Array is checked during compile and as well as runtime, but Collections are checked only during Compile time.
- So there should NOT be an accidental addition of another type of object in to another type of Collection, ie Dog Object into a Cat Collection.
1.
public <T extends A> void go(ArrayList<T> aList){
}
2.
public void go(ArrayList<? extends A> aList){
}
In a non generic context, if B "is a" A, then B can be substituted for A, but A cannot be substituted for B.
With generics, List and List are both "is a" Collection<E>; that is, List is not a subclass of List, and therefore, you cannot substitute List for List.
The specific content on generics you're looking for can be found here, copied below for your convenience.
Given two concrete types A and B (for example, Number and Integer), MyClass has no relationship to MyClass, regardless of whether or not A and B are related. The common parent of MyClass and MyClass is Object.