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.
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 .
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.
public void run(){
setFont("Courier-24");
//Define list as ArrayList<Integer>
ArrayList<Integer> list = new ArrayList<Integer>();
readList(list);
}
private void readList(ArrayList list){
list.add("Hello");
list.add(2);
println("list = "+list);
println("Type of list = "+list.get(0).getClass());
println("Type of list = "+list.get(1).getClass());
}
Result:
list = [Hello, 2]
Type of list = class java.lang.String
Type of list = class java.lang.Integer
Here is my code and result. My question is, how is it possible that ArrayList of type Integer can store String objects? What's the type of list now? And what mechanism is this?
Java's generics don't actually change the underlying class or object, they just provide (mostly) compile-time semantics around them.
By passing an ArrayList<Integer> into a method expecting an ArrayList (which can hold anything), you're bypassing the compiler's ability to provide you with that type safety.
The Java Generics Tutorial explains this, and why Java implements generics this way. This page, in particular, focusses on it:
Generics were introduced to the Java language to provide tighter type checks at compile time and to support generic programming. To implement generics, the Java compiler applies type erasure to:
Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
Insert type casts if necessary to preserve type safety.
Generate bridge methods to preserve polymorphism in extended generic types.
Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.
What that doesn't say is that this also allows code written with generics (like your run) to interact with code written without generics (like your readList), which is important when adding a feature to a very-well-established language with a huge library base (as they were when adding generics to Java).
When you declare:
private void readList(ArrayList list)
you are not specifying any type to this ArrayList so by default it is of type Object.
Both String and Integer (in fact all classes in java) are sub types of Object. Hence it is possible to add them to list.
For more information about generics without types please read here. In short generics types are for compile time checks only, so that you dont add wrong types (which can later cause exceptions. In this case your operations on String and Integer are compatible so luckily no errors).
In your readList method's parameter you haven't constrained it to only Integer value types, thus list doesn't get the benefits of compile-time checking and must resort to runtime type checking.
Declaration
ArrayList list
in the method readList is equivalent to the
ArrayList<Object> list
It's obvious that String is Object, as well as Integer. When passing to println, both will be toString'ed with their own methods.
I think that the ArrayList type without the generics specification you pass as argument is assument as ArrayList. Both String and Integer inherits Object so they both can be added in the list. However the ArrayList elements are of type Objects.
I recently came across that, Arrays are reified in Java. That is, they know the type information only during run time. But I am a little confused with this definition.
If Arrays are said to know the type information only during runtime, I should literally be able to assign any values to any arrays, since the typing is known only at run time and errors will be thrown at run time only. But that is not the case in real time. We get a compile time error for that.
So can someone throw light on "what does it mean by - arrays are reified"?
What I think that means is that the given lines of code will throw an exception:
String[] arrayOfStrings = new String[10];
Object[] arrayOfObjects = arrayOfStrings; // compiles fine
arrayOfObjects[0] = new Integer(2); // throws a runtime exception (ArrayStoreException IIRC)
Arrays are covariant: String[] extends Object[]. But the actual type of the array is known at runtime, and an attempt to store an instance which is not of the right type throws an exception.
I believe the term you are looking for is reifiable.
A reifiable type does not lose any type information due to type erasure at runtime. Examples of reifiable types include:
primitives
non-generic reference types
arrays or primitives or arrays
of non-generic reference types.
Reifiable does not mean a type is not known at compile time. What this does mean is that something like the following, cannot be typed checked:
List<Integer>[] myList;
Arrays carry runtime information about the types they store. Non-refiable types cannot be type checked at runtime, which does not make them good candidates for the component type of an array.
When using reifiable types as the component type of an array such as String[] the complete type information is available at runtime, so type checks can be performed.
String[] someArray = new String[2];
//some factory returns Integer upcasted to Object
someArray[0] = someFactory.getType("Integer"); //throws RuntimeException
Sources:
http://docs.oracle.com/javase/tutorial/java/generics/nonReifiableVarargsType.html
http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ106 (Good)
If Arrays are said to know the type information only during runtime, I should literally be able to assign any values to any arrays, since the typing is known only at run time errors will be thrown at run time only. But that is not the case in real time. We get a compile time error for that.
Reifiable types know their type at runtime and compile time, which is why the compiler will still prevent you making silly mistakes where it can (what's the point in letting them through?)
However, there's times when the compiler can't always work out for certain whether an assignment (for instance) will be valid because it doesn't know the exact types, and this is where reified types can check. For instance:
Object[] arr = new String[5];
arr[0] = 7;
...this will compile, because on the second line the compiler only knows the static type of the array as Object, whereas the dynamic type is something more specific. It will fail as an exception at Runtime, which it can only do because (unlike with the generic collection classes) the specific type is known at runtime.
As mentioned in doc:
A reifiable type is a type whose type information is fully available
at runtime. This includes primitives, non-generic types, raw types,
and invocations of unbound wildcards.
Non-reifiable types are types where information has been removed at
compile-time by type erasure — invocations of generic types that are
not defined as unbounded wildcards. A non-reifiable type does not have
all of its information available at runtime. Examples of non-reifiable
types are List and List; the JVM cannot tell the
difference between these types at runtime. As shown in Restrictions on
Generics, there are certain situations where non-reifiable types
cannot be used: in an instanceof expression, for example, or as an
element in an array.
So Arrays are reified and covariant but generics are invariant and type-erased by nature. Arrays provide runtime type safety and throw ArrayStore exception if element of correct type is not added.
I have a method that uses the varargs feature:
void add(Animal ...);
Now, instead of doing .add(dog, cat), I have an Animal list with unknown number of elements,
List<Animal> i = new ArrayList<Animal>();
i.add(dog);
i.add(cat);
and want to call add with the elements of this list.
I think I could use an array, but when I do .add(i.toArray()), it gives a compiler error.
What is the proper way to do it?
It's:
add(i.toArray(new Animal[i.size()]))
List.toArray returns an Object[], regardless of the type argument on the List: even though you might write new List<String>().toArray(), you will get an Object[]. However, the version of toArray that takes an array to fill returns an array with the correct type: if you write new List<String>().toArray(new String[0]), you will get an String[]. Note that the size of the array you pass in doesn't even have to match the size of the list, although it's good practice to ensure that it does.
This is ultimately due to a mildly tricky feature of generics. At first glance, you might think that String[] and List<String>mean similar things for their base types - one is an array of strings, the other is a list of strings.
However, they are in fact very different.
An array is a language primitive, and has its type baked into it. If you took a hex editor and looked at an array instance in memory in the JVM, you would be able to find (somewhere nearby) a record of the type of objects it holds. That means that if you take an instance of an array of some unknown component type, you can find out what that type is. Conversely, it means that if you're going to create an instance of an array, you need to know what component type you want.
The List, on the other hand, uses generics, which in Java is implemented with type erasure, which means that, roughly speaking, it is something that exists in the compiler, but not at runtime (the compiler can check that you get it right, but the JVM can't). This leads to a simple and efficient implementation (one simple enough to have been added to pre-generics Java without changing the JVM), but it has some shortcomings - in particular, that at runtime, there is no way to tell what the type argument on any particular instance of a generic class is, because type arguments only exist in the compiler. Because it is up to the List instance to handle toArray(), the only thing it can do is create an Object[]. It just doesn't know of a more specific type to use.
One way of looking at this is that arrays have a type argument as part of their class, whereas Lists have a type argument as part of their type, and since objects have classes but variables have types, you can't get the type argument of a List from an object, only from a variable holding an object (as an aside, you also can't get the type argument of an array from a variable holding an array (consider Object[] array = new String[0];), but that doesn't really matter because, the variable lets you get hold of an object - unless it's null).
To boil this down to code, the problem is:
public <E> E[] createSimilarlyTypedArray(List<E> list) {
Class<E> componentType = list.???; // there is no way to do this
return Arrays.newInstance(componentType, list.size());
}
when I do .add(i.toArray()) it gives an error, what is the proper way
to do it?
Use foo.addAll(i) and then convert foo to an array if need be.
Your method void add(Animal...) expects a object of the Animal class or a array with Animal objects in it. You give it an array with objects of the Object class. Give the List a generic type like so:
List<Animal> animals = new ArrayList<Animal>();
animals.add(dog);
animals.add(cat)
Then parse the list as argument, while converting it to an array, to your method like so:
add(animals.toArray(new Animal[animals.size()]);
More on generics can be found in the Java API
http://download.oracle.com/javase/1,5.0/docs/guide/language/generics.html