Why is Object[].class.isAssignableFrom(String[].class) == true, while String[].getSuperClass() or getGenericInterfaces() could not get Object[]?
I checked the source of JDK, but i don't think i can get the answer myself.
For now, I know JDK uses tree to store the relationship between Classes, and depth to indicate its level, Class::isAssignableFrom() searched the chain, so definately arrays are in that tree. and also String[] is connected to Object[].
Can i say that String[] is a subclass of Object[]?
Or is it just another weird thing of Java?
Class.isAssignableFrom() essentially checks the subtyping relation. "subtype" and "subclass" are two different concepts. The class hierarchy (i.e. subclassing) is only a part of subtyping.
Primitive types and array types have special cases for subtyping.
The rules for subtyping of array types are like this (note that ">1" means "is a directy subtype of"):
If S and T are both reference types, then S[] >1 T[] iff S >1 T.
Object >1 Object[]
Cloneable >1 Object[]
java.io.Serializable >1 Object[]
If p is a primitive type, then:
Object >1 p[]
Cloneable >1 p[]
java.io.Serializable >1 p[]
The important part for your question is the very first item: an array type X[] is a subtype of an array type Y[] if and only if the component type X is a subtype of the component type Y.
Also note that strictly speaking neither Object[] nor String[] are classes. They are "only" types. While every class implicitly is a type, the reverse is not true. Another example of types that are not classes are the primitive types: boolean, byte, char, short, int, long, float and double are types, but they are not classes.
Another cause for confusion is the fact that you can easily get java.lang.Class objects representing those types. Again: This does not mean that those types are classes.
In Java (and .NET), arrays are covariant. It means you can pass an instance of type Apple[] to a method that expects a Fruit[] if Apple inherits Fruit. The following line is valid:
Fruit[] fruits = apples; // apples is an Apple[]
This means a Fruit[] is assignable from Apple[].
This is not very safe, of course. Assume:
void someMethod(Object[] objects) {
objects[0] = "Hello World"; // throws at run time.
}
void test() {
Integer[] integers = new Integer[10];
integers[0] = 42;
someMethod(integers); // compiles fine.
}
This design decision is handy when you want to use arrays contents (e.g. print it) but not modify it.
Because String[] can actually be converted/widened to Object[].
You might be thinking that this tests if String[] is assignable from Object[], but it actually tests the reverse (if String[] can be assigned to Object[]).
This code compiles and executes as expected:
public static void main(String[] args) {
String[] strings = new String[]{ "hello", "world" };
printArray(strings);
}
public static void printArray(Object[] array) {
for (Object obj : array) {
System.out.println(obj);
}
}
If this object represents an array class then the Class object representing the Object class is returned.link
Related
Consider the following example:
public class Learn {
public static <T> T test (T a, T b) {
System.out.println(a.getClass().getSimpleName());
System.out.println(b.getClass().getSimpleName());
b = a;
return a;
}
public static void main (String[] args) {
test("", new ArrayList<Integer>());
}
}
In the main method, I am calling test with a String and an ArrayList <Integer> object. Both are different things and assigning an ArrayList to String (generally) gives a compile error.
String aString = new ArrayList <Integer> (); // won't compile
But I am doing exactly that in the 3rd line of test and the program compiles and runs fine. First I thought that the type parameter T is replaced by a type that's compatible with both String and ArrayList (like Serializable). But the two println statements inside test print "String" and "ArrayList" as types of a and b respectively. My question is, if ais String and b is ArrayList at runtime, how can we assign a to b.
For a generic method, the Java compiler will infer the most specific common type for both parameters a and b.
The inference algorithm determines the types of the arguments and, if available, the type that the result is being assigned, or returned. Finally, the inference algorithm tries to find the most specific type that works with all of the arguments.
You aren't assigning the result of the call to test to anything, so there is no target to influence the inference.
In this case, even String and ArrayList<Integer> have a common supertype, Serializable, so T is inferred as Serializable, and you can always assign one variable of the same type to another. For other examples, you may even find Object as the common supertype.
But just because you have variables of type T that are inferred as Serializable, the objects themselves are still a String and an ArrayList, so getting their classes and printing their names still prints String and ArrayList. You're not printing the type of the variables; you're printing the type of the objects.
test("", new ArrayList<Integer>());
This is equivalent to the following:
Learn.test("", new ArrayList<Integer>());
Which is also equivalent to the following:
Learn.<Serializable>test("", new ArrayList<Integer>());
This will not compile if you explicitly specify a generic type other than Serializable (or Object), such as String:
Learn.<String>test("", new ArrayList<Integer>()); // DOES NOT COMPILE
So essentially both parameters are treated as Serializable in your case.
I have been trying to understand Java generics properly.So in this quest I have come accross one principle " Principle of Truth In Advertising", I am tring to understand this in simple language.
The Principle of Truth in Advertising: the reified type of an array must be a subtype
of the erasure of its static type.
I have written sample code .java and .class files as follows.Please go through code and please explain what part(in code) designates/indicates what part of above statement.
I have written comments to I think I should not write description of code here.
public class ClassA {
//when used this method throws exception
//java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
public static <T> T[] toArray(Collection<T> collection) {
//Array created here is of Object type
T[] array = (T[]) new Object[collection.size()];
int i = 0;
for (T item : collection) {
array[i++] = item;
}
return array;
}
//Working fine , no exception
public static <T> T[] toArray(Collection<T> collection, T[] array) {
if (array.length < collection.size()) {
//Array created here is of correct intended type and not actually Object type
//So I think , it inidicates "the reified type of an array" as type here lets say String[]
// is subtype of Object[](the erasure ), so actually no problem
array = (T[]) Array.newInstance(array.getClass().getComponentType(), collection.size());
}
int i = 0;
for (T item : collection) {
array[i++] = item;
}
return array;
}
public static void main(String[] args) {
List<String> list = Arrays.asList("A", "B");
String[] strings = toArray(list);
// String[] strings = toArray(list,new String[]{});
System.out.println(strings);
}
}
Please try to explain in simple language.Please point out where I am wrong. Corrected code with more comments is appreciated.
Thank you all
I refer to Java Generics and Collections as the Book and the Book's authors as the Authors.
I would upvote this question more than once as the Book makes a poor job of explaining the principle IMO.
Statement
Principle of Truth in Advertising:
the reified type of an array must be a subtype of the erasure of its static type.
Further referred to as the Principle.
How does the principle help?
Follow it and the code will compile and run without exceptions
Do not follow it and the code will compile, but throw an exception at runtime.
Vocabulary
What is a static type?
Should be called the reference type.
Provided A and B are types, in the following code
A ref = new B();
A is the static type of ref (B is the dynamic type of ref). Academia parlance term.
What is the reified type of an array?
Reification means type information available at runtime. Arrays are said to be reifiable because the VM knows their component type (at runtime).
In arr2 = new Number[30], the reified type of arr2 is Number[] an array type with component type Number.
What is the erasure of a type?
Should be called the runtime type.
The virtual machine's view (the runtime view) of a type parameter.
Provided T is a type parameter, the runtime view of the following code
<T extends Comparable<T>> void stupidMethod(T[] elems) {
T first = elems[0];
}
will be
void stupidMethod(Comparable[] elems) {
Comparable first = elems[0];
}
That makes Comparable the runtime type of T. Why Comparable? Because that's the leftmost bound of T.
What kind of code do I look at so that the Principle is relevant?
The code should imply assignment to a reference of array type. Either the lvalue or the rvalue should involve a type parameter.
e.g. provided T is a type parameter
T[] a = (T[])new Object[0]; // type parameter T involved in lvalue
or
String[] a = toArray(s); // type parameter involved in rvalue
// where the signature of toArray is
<T> T[] toArray(Collection<T> c);
The principle is not relevant where there are no type parameters involved in either lvalue or rvalue.
Example 1 (Principle followed)
<T extends Number> void stupidMethod(List<T>elems) {
T[] ts = (T[]) new Number[0];
}
Q1: What is the reified type of the array ts is referencing?
A1: Array creation provides the answer: an array with component type Number is created using new. Number[].
Q2: What is the static type of ts?
A2: T[]
Q3: What is the erasure of the static type of ts?
A3: For that we need the erasure of T. Given that T extends Number is bounded, T's erasure type is its leftmost boundary - Number. Now that we know the erasure type for T, the erasure type for ts is Number[]
Q4: Is the Principle followed?
A4: restating the question. Is A1 a subtype of A3? i.e. is Number[] a subtype of Number[]? Yes => That means the Principle is followed.
Example 2 (Principle not followed)
<T extends Number> void stupidMethod(List<T>elems) {
T[] ts = (T[]) new Object[0];
}
Q1: What is the reified type of the array ts is referencing?
A1: Array creation using new, component type is Object, therefore Object[].
Q2: What is the static type of ts?
A2: T[]
Q3: What is the erasure of the static type of ts?
A3: For that we need the erasure of T. Given that T extends Number is bounded, T's erasure type is its leftmost boundary - Number. Now that we know the erasure type for T, the erasure type for ts is Number[]
Q4: Is the Principle followed?
A4: restating the question. Is A1 a subtype of A3? i.e. is Object[] a subtype of Number[]? No => That means the Principle is not followed.
Expect an exception to be thrown at runtime.
Example 3 (Principle not followed)
Given the method providing an array
<T> T[] toArray(Collection<T> c){
return (T[]) new Object[0];
}
client code
List<String> s = ...;
String[] arr = toArray(s);
Q1: What is the reified type of the array returned by the providing method?
A1: for that you need too look in the providing method to see how it's initialized - new Object[...]. That means the reified type of the array returned by the method is Object[].
Q2: What is the static type of arr?
A2: String[]
Q3: What is the erasure of the static type of ts?
A3: No type parameters involved. The type after erasure is the same as the static type String[].
Q4: Is the Principle followed?
A4: restating the question. Is A1 a subtype of A3? i.e. is Object[] a subtype of String[]? No => That means the Principle is not followed.
Expect an exception to be thrown at runtime.
Example 4 (Principle followed)
Given the method providing an array
<T> T[] toArray(Collection<T> initialContent, Class<T> clazz){
T[] result = (T[]) Array.newInstance(clazz, initialContent);
// Copy contents to array. (Don't use this method in production, use Collection.toArray() instead)
return result;
}
client code
List<Number> s = ...;
Number[] arr = toArray(s, Number.class);
Q1: What is the reified type of the array returned by the providing method?
A1: array created using reflection with component type as received from the client. The answer is Number[].
Q2: What is the static type of arr?
A2: Number[]
Q3: What is the erasure of the static type of ts?
A3: No type parameters involved. The type after erasure is the same as the static type Number[].
Q4: Is the Principle followed?
A4: restating the question. Is A1 a subtype of A3? i.e. is Number[] a subtype of Number[]? Yes => That means the Principle is followed.
What's in a funny name?
Ranting here. Truth in advertising may mean selling what you state you are selling.
In
lvalue = rvalue we have rvalue as the provider and lvalue as the receiver.
It might be that the Authors thought of the provider as the Advertiser.
Referring to the providing method in Example 3 above,
<T> T[] toArray(Collection<T> c){
return (T[]) new Object[0];
}
the method signature
<T> T[] toArray(Collection<T> c);
may be read as an advertisement: Give me a List of Longs and I will give you an array of Longs.
However looking in the method body, the implementation shows that the method is not being truthful, as the array it creates and returns is an array of Objects.
So toArray method in Example 3 lies in its marketing campaigns.
In Example 4, the providing method is being truthful as the statement in the signature (Give me a collection and its type parameter as a class literal and I will give you an array with that component type) matches with what happens in the body.
Examples 3 and 4 have method signatures to act as advertisement.
Examples 1 and 2 do not have an explicit advertisement (method signature). The advertisement and the provision are intertwined.
Nevertheless, I could think of no better name for the Principle. That is a hell of a name.
Closing remarks
I consider the statement of the principle unnecessarily cryptic due to use of terms like static type and erasure type. Using reference type and runtime type/type after erasure, respectively, would make it considerably easier to grasp to the Java layman (like yours truly).
The Authors state the Book is the best on Java Generics [0]. I think that means the audience they address is a broad one and therefore more examples for the principles they introduce would be very helpful.
[0] https://youtu.be/GOMovkQCYD4?t=53
Think of it that way:
T[] array = (T[]) new Object[collection.size()]; A new Array is created. Due to language design, the type of T is unkown during runtime. In your example you know for a fact T is String, but the from the viewpoint of the vm T is Object. All casting operations are happening in the calling method.
So in toArray an array Object[] is created. The type parameter is more or less syntactic sugar which has no consequence for the bytecode created.
So why can't an array of objects be casted to an array of strings?
Let's have an example:
void methodA(){
Object[] array = new Object[10];
array[0]=Integer.valueOf(10);
array[1]=Object.class;
array[2]=new Object();
array[3]="Hello World";
methodB((String[])array);
}
void methodB(String[] stringArray){
String aString=stringArray[1]; //This is not a String, but Object.class!
}
If you could cast an array, you'd say "all elements I've added before are of a valid subtype". But since your array is of type Object, the vm can't guarantee the array will always under all circumstances contain valid subtypes.
methodB thinks it deals with an array of Strings, but in reality the array does contain very different types.
The other way around does not work either:
void methodA(){
String[] array = new String[10];
array[0]="Hello World";
methodB((Object[])array);
//Method B had controll over the array and could have added any object, especially a non-string!
System.out.println(array[1]);
}
void methodB(Object[] oArray){
oArray[1]=Long.valueOf(2);
}
I hope this helps a little bit.
Edit: After reading your question again, I think you are mixing to things:
Arrays can't be casted (as I explained above)
The cited sentence does say in plain English: "If you create an array of type A, all elements in this array must be of type A or a of a subtype of A". So if you create an array of Object you can put any java object into to array, but if you create an array of Number the values have to be of type Number (Long, Double, ...). All in all the sentence is rather trivial. Or I didn't understand it either ;)
Edit 2: As a matter of fact you can cast an array to any type you want. That is, you can cast an array as you can cast any type to String (String s=(String)Object.class;).
Especially you can cast a String[] to an Object[] and the other way around. As I pointed out in the examples, this operation introduces potential bugs in great numbers, since reading/writing to the array will likely fail. I can think of no situation where it is a good decision to cast an array. There might be situations (like generalized utility classes) where it seems to be a good solution, but I still would suggest to overthink the design if you find yourself in a situation where you want to cast an array.
Thanks to newacct for pointing out the cast operation itself is valid.
This question already has answers here:
What's the reason I can't create generic array types in Java?
(16 answers)
Closed 7 years ago.
I'm studying for my Java midterm but I have some problems with the reified type. Here there is a class that is wrong, but I cannot understand why. Can someone help me and maybe give me some explanation? The error is, of course, related to the reified type.
class Conversion {
public static <T> T[] toArray(Collection<T> c) {
T[] a = new T[c.size()];
int i = 0;
for (T x: c) a[i++] = x;
return a;
}
}
An array is a reified type. This means that the exact type of the array is known at runtime. So at runtime there is a difference between, for example, String[] and Integer[].
This is not the case with generics. Generics are a compile-time construct: they are used to check the types at compile-time, but at runtime the exact types are not available anymore. At runtime, the type parameters are just Object (or if the type parameter has an upper bound, its upper bound). So at run-time, there is no difference in the type of Collection<String> and Collection<Integer>.
Now, there is a problem when you want to create an array of a type parameter. At runtime it is unknown what T is, so if you write new T[10], the Java runtime doesn't known what kind of array is to be created, a String[] or a Integer[]. That's why you cannot create an array in this way.
There are a few work-arounds, none of which is entirely satisfactory. The usual solution is to create an Object[], and cast it to the kind of array you want:
T[] theArray = (T[]) new Object[size];
However, you have to remember that this is very unsafe. You should only do this if the scope of the created arrow is small, so that you can manually make sure that the array will only contain T instances and will never be assigned to anything that cannot hold it. The following code demonstrates the problem:
public class Foo<T extends Comparable> {
T[] createArray() {
return (T[])new Object[1];
}
public static void main(String... args) {
Foo<String> foo = new Foo<>();
String[] ss = foo.createArray(); // here
}
}
The line marked with here throws an exception, because you are trying to cast an Object[] to a String[]!
If you really need an array of the correct run-time type, you need to use reflection. Obtain a type token (of type Class<T>) of the type you need, and use Array.newInstance(type, cize) to create the array, for example:
public T[] createArray(Class<T> type, int size) {
return (T[]) Array.newInstance(type, size);
}
Reifiable type is defined by the JLS as:
A type is reifiable if and only if one of the following holds:
It refers to a non-generic class or interface type declaration.
It is a parameterized type in which all type arguments are unbounded wildcards (§4.5.1).
It is a raw type (§4.8).
It is a primitive type (§4.2).
It is an array type (§10.1) whose element type is reifiable.
It is a nested type where, for each type T separated by a ".", T itself is reifiable.
See also the notes that follow §4.7, the reasoning is described in great detail.
Your type parameter is none of the above, therefore it is not reifiable. And thus can't be used in an array creation expression:
It is a compile-time error if the ClassOrInterfaceType does not denote
a reifiable type (§4.7). Otherwise, the ClassOrInterfaceType may name
any named reference type, even an abstract class type (§8.1.1.1) or an
interface type.
You can't create an array of a generic type. The compiler likely complains with something like "generic array creation". There's no nice way around this, but there is a way to do this:
public static <T> T[] toArray(Class<T> type, Collection<T> c) {
T[] a = (T[]) Array.newInstance(type, c.size())
…
}
You'll need Class<T> for this, but it does work :)
We cannot create the array of generic type, it's a well-known fact, so I'm not going to provide formal references to JLS here. But we can declare such arrays as follows:
static <E> void reduce() {
List<Integer>[] arr; //compiles fine
E[] avv; //compiles fine
avv = new E[10]; //doesn't compile
arr = new List<Integer>[10]; //doesn't compile
}
Anyone know the reason for such declarations?
First of all, I assume you meant
avv = new E[2]; //doesn't compile
arr = new List<Integer>[2]; //doesn't compile
in the last two lines of your method. You have to specify the size of the array when to create a new array. The code still doesn't compile, though.
Arrays predate generics. Arrays are present since the first version of Java, while generics were only added in version 1.5. To break no old code, the Java designers decided to erase generic types at runtime: at runtime, a type parameters are replaced by their upper bound. In your case, at runtime, E is the same as Object. It is not known which type E is really.
This is a problem, because, the array element type is not erased at runtime. Integer[] and String[] are different types, even at runtime. If you write new E[2], the Java runtime doesn't know what kind of array it must create. It could be String[] or Integer[], or any other array type. Therefore, you cannot create new arrays with generic elements.
Arrays with generic elements are still allowed as types, mainly in order to use them in method parameters:
<E> E doSomething(E[] param) { ... }
The actual array is created in another part of the program, where its type is known. You could call this method with
String result = doSomething(new String[2]);
for example.
It allows you to pass arrays of generic types as method parameters.
For example:
public class Foo<T>
{
public void bar (T[] arr) {}
}
...
Foo<String> foo = new Foo<String>();
String[] arr = {"aa","bb");
foo.bar (arr);
If T[] wasn't allowed, the method signature would have to be public void bar (Object[] arr), and the compiler would let you pass any type of array to that method.
Today I've written simple test about syntax of declaring array, so, there are 3 classes:
public class A {
}
public class B extends A {
}
public class C extends A {
}
and I've tried to create array using next syntax
A[] aa = new B[10];
So, It is possible, but we can added just instances of class B to this array , If you will try to add instances of A or C you receive java.lang.ArrayStoreException and question, Why can we create array using syntax like that and where is it can be used and make some profit ?
Thanks.
The array holds type Bs. Even though the reference aa could hold arrays of type A[], B[] or C[], (since C and B both extend A), at runtime, the array can only hold things which are Bs.
Class A is not a B. Class C is not a B. Hence the runtime exception.
EDIT:
There are many potential uses for code like this. For example, You can declare an array like this because you might not know until runtime the more explicit type you are working with.
For instance:
Animal[] animals;
if (useDogs) {
animals = new Dog[num];
} else {
animals = new Cat[num];
}
loadIntoCar(animals);
The reason this syntax is allowed in the language is that sometimes you don't care what subclass of objects are in the array. The Java Language Specification rules for array subtyping include:
If S and T are both reference types, then S[] >1 T[] iff S >1 T.
(where >1 means "direct subtype").
This allows one to write a method like this:
public void printArray(Object[] array) {
System.out.print('[');
boolean first = true;
for (Object obj : array) {
if (!first) {
System.out.print(", ");
} else {
first = false;
}
System.out.print(String.valueOf(obj));
}
System.out.print(']');
}
and then call it with:
String[] foo = { "a", "b", "c" };
printArray(foo);
This language feature does have the unfortunate effect of deferring ArrayStoreException problems to run time.
Arrays types are covariant, which means you can declare A[] a = new B[] (Collections, on the contrary, are not : you cannot declare List<A> a = new List<B>()).
This is why the compiler cannot preemptively check that you will only put elements of a valid type in the array, which explains why the check happens only at runtime, when you try to insert an element into the array.
If you want examples, take a look at Arrays. If Java didn't allow arrays polymorphism, we couldn't have functionalities such as sort(Object[] a), toString(Object[] a), binarySearch(Object[] a, Object key), etc. Remember that Band C are subtypes of Object, so you can use that functionality for all of them. Take note that arrays predate generics (Java 1.5.0) and that even generics have it's flaws (the trade-off for Java is that you can't know the generic type at runtime because of type-erasure).