Java - weird generics behavior with arrays - java

I was trying to implement some kind of custom serialization with reflection, and then I found out that you just cannot cast A[] to B[] if A isn't a subtype of B even if all the elements in the array are subtypes of B (It should work because of type erasing because at the end of the day both a[] and b[] are Object[]).
here is an example of what I mean:
public class Foo {
public static void main(String[] args) throws Exception {
new Foo().testGenerics();
}
public LinkedList<String> strList;
public String[] strArr;
public void testGenerics() throws Exception {
LinkedList<Object> objList = new LinkedList<Object>();
objList.add("foo");
Object[] objArr = new Object[1];
objArr[0] = "bar";
Foo.class.getField("strList").set(this, objList);
System.out.println("list[0] = " + strList.get(0));
System.out.println("list len = " + strList.size());
Foo.class.getField("strArr").set(this, objArr);
System.out.println("arr[0] = " + strArr[0]);
System.out.println("arr len = " + strArr.length);
}
}
In this example the list works fine but the array throws an exception at the line that tries to set it.
is there any reason arrays ignore type erasing, and is there any good way to create an array and a list for a given type at runtime?

Arrays and generics are very different things. Generics are erased at runtime because the JVM doesn't know about them. However, the JVM does know about arrays. If you search in the JVM specification for "array", you'll see that section 3.9 is titled "Arrays". But you can't find anything about generics, or parameterised types. You can read section 3.9 in more detail to find out just how much the JVM knows about arrays. The Java Language Specification also talks about arrays and generics in very different sections.
Therefore, the JVM at runtime, can and does try to maintain the type safety of arrays, and stops you from assigning Object[] to String[]. This is analogous to the Java Compiler trying to maintain the type safety of LinkedLists at compile time by stopping you from assigning a LinkedList<Object> to a LinkedList<String>. It's not like the latter is somehow "safe to do" at runtime, it's just that the runtime doesn't know enough to stop you.
is there any good way to create an array and a list for a given type at runtime?
Given the Class<?> clazz and the length, you can create an array of it by:
Object o = Array.newInstance(clazz, length);
But having to do this smells like an XY problem... Make sure you know what you are doing.
You can create a list using the way you are using right now. It's not type safe, but that's a flaw of JVM/Java.

Related

Type T cannot be instantiated directly (tring to swap using generics)

I'm trying to swap using a generic type but I am getting the above error in the third last line
"new T[output.size()]);"
Does anyone know how I can fix this?
public class Exchange {
public static <T> T[] swap(T[] a, int i, int j){
int length = a.length;
ArrayList<T> output = new ArrayList<T>();
for (int b = 0; i<length;i++){
if (b==i){
output.add(a[j]);
}else if (b==j){
output.add(a[i]);
}else {
output.add(a[b]);
}
}
T[] outputs = output.toArray(new T[output.size()]);
return outputs;
}
}
It's because what you want is impossible, to an extent (but in this specific case, good news! You can sort of do this).
Generics are figments of the compiler's imagination. It's not in the class file, or part of the runtime type. The generics (the T) serves to 'link' things. It tells the compiler: Whenever you call this method, the return type and the first argument are 'linked' - they are the same thing.
Said differently, generics are not reified: Given:
List<String> list = new ArrayList<String>();
getStringOut(list); // this method is not possible
The reason it is not possible is because list is a variable that points at an instance of ArrayList and that instance doesn't actually know it's a list of strings. It's entirely javac doing this, checking that you never add non-strings to list. The list itself doesn't care (it wouldn't know if you tried to add an integer to it), the class verifier won't mind either. It's just javac that won't let you.
This in contrast to arrays which are reified:
String[] x = new String[10];
getStringOut(x); // this CAN be done!
That's why new T[] is completely impossible.
But, you said there was good news
You luck out here - you have an array (reified types!) and you want the output array to have the exact same component type, so you CAN actually do this:
Class<?> componentType = a.getComponentType();
T[] outputs = (T[]) java.lang.reflect.Array.newInstance(componentType, output.size());
It's a bit unfortunate that the cast will cause javac to spit out a warning, but for once you can ignore this kind of warning (usually you should not). javac is not sure that the output of newInstance is actually guaranteed to be an array of Ts, but, you can verify with your eyeballs that this must be the case, hence, you can #SuppressWarnings this away.
But you're lucky here - you have an array of Ts, which is about the only way you can ever do this. That or a factory function such as a Supplier<T>, otherwise this wouldn't have been possible.
NB: You could of course always just swap 'in-place', modifying the input array.

java.lang.ArrayStoreException: when assigning value with incorrect type to Object[] array [duplicate]

This question already has answers here:
Why are arrays covariant but generics are invariant?
(8 answers)
Closed 4 years ago.
I come from a C background. It doesn't make sense to me that I can't add an Object to a Object[] in foo().
I would expect the code in function foo to work at runtime but fail in main() when I attempt to do strings[0].toLowerCase(); because the object in strings[0] is not a String but is actually a StringBuilder which doesn't have a toLowerCase(). At the end of the day, I am storing a pointer into an array of pointers and the line objects[0] = other should not be invalid. It is clear my understanding of java is wrong (but I an fairly new at the moment).
class Main {
static void foo(Object[] objects, Object other) {
objects[0] = other;
}
public static void main(String[] args) {
String[] strings = { "stringValue" };
foo(strings, new StringBuilder());
}
}
EDIT: Thanks all for the answer. I googled "java covariance of arrays" thanks to #Andy Turner and came across this which discusses this behaviour. The trick is that the compiler and runtime treat the code very differently. Compiler is fine with the code, runtime isn't.
From the documentation of ArrayStoreException:
Thrown to indicate that an attempt has been made to store the wrong type of object into an array of objects. For example, the following code generates an ArrayStoreException:
Object x[] = new String[3];
x[0] = new Integer(0);
Just because you're passing a String[] into a method that expects an Object[] doesn't mean that it's actually an Object[]. Because you create a String[], you can only add instances of String to it, and you're trying to add a StringBuilder, which doesn't make sense.
Notice how it does not throw an exception if you change the type of String[] to Object[]:
public static void main(String[] args) {
Object[] strings = { "stringValue" };
foo(strings, new StringBuilder()); // Perfectly fine
}
It is evaluated at runtime that your String Array is in fact trying to assign a different type. You would an ArrayStoreException, which clearly says that
Thrown to indicate that an attempt has been made to store the wrong
type of object into an array of objects. For example, the following
code generates an ArrayStoreException:
Object x[] = new String[3];
x[0] = new Integer(0);
It is not generally a good practice to accept Object types as parameters or even return values of methods/functions. They could be polymorphic interfaces, but Object is at the highest levels of abstraction, and is not ideal for this purpose.
You are trying to add the Object "StringBuilder" to an Array of the object "String". Under some circumstances this might actually work, if you were to assign the StringBuilder Value but I am not to shure about that, as I never used this myself.
Here's a good read: https://docs.oracle.com/javase/7/docs/api/java/lang/StringBuilder.html
Usually Java takes care of pointers for you which makes it really hard to mess it up.
I hope I could help you :)

Different Java behavior for Generics and Arrays

Why does java allows inconsistent type to be entered into a generic object reference but not in an array?
For Eg:
When initializing array:
int[] a = {1, 2, 3};
And, if I enter:
int[] a = {1, 2, "3"}; //Error for incompatible types
While for generics,
import java.util.ArrayList;
public class Test {
private static ArrayList tricky(ArrayList list) {
list.add(12345);
return list;
}
public static void main(String[] args) {
int i = 0;
ArrayList<String> list = new ArrayList<>();
list.add("String is King");
Test.tricky(list);
}
}
The above code will let you add any Type in the list object, resulting in a run time exception in some cases.
Why is there such a behavior?? Kindly give a proper explanation.
When you use the tricky method to insert data into your ArrayList Collection, it doesn't match the specified type i.e String, but still This is compatible because of Generics compatibility with older Legacy codes.
If it wouldn't have been for this i.e if it would have been the same way as of arrays, then all of the pre-java generic code would have been broken and all the codes would have to be re-written.
Remember one thing for generics, All your type-specifications are compile time restrictions, so when you use the tricky method to insert data in your list reference, what happens is the compiler thinks of it as a list to which ANYTHING apart from primitives can be added.
Only if you would have written this:
...
public class Test {
private static ArrayList tricky(ArrayList<String> list) {
list.add(12345); //Error, couldn't add Integer to String
return list;
}
...
}
I have written a documented post on this, Read here.
The method's parameter has no generic so all classes are allowed.
You may google 'type erasure' for more information.
If you add the generic type to your method you will get a compiler error:
private static ArrayList<String> tricky(ArrayList<String> list) { // ...
By the way, you do not need to return the list because you modify the same instance.
Here's why:
The reason you can get away with compiling this for arrays is because
there is a runtime exception (ArrayStoreException) that will prevent
you from putting the wrong type of object into an array. If you send a
Dog array into the method that takes an Animal array, and you add only
Dogs (including Dog subtypes, of course) into the array now referenced
by Animal, no problem. But if you DO try to add a Cat to the object
that is actually a Dog array, you'll get the exception. Generic
Methods (Exam Objectives 6.3 and 6.4) 615 616 Chapter 7: Generics and
Collections
But there IS no equivalent exception for generics, because
of type erasure! In other words, at runtime the JVM KNOWS the type of
arrays, but does NOT know the type of a collection. All the generic
type information is removed during compilation, so by the time it gets
to the JVM, there is simply no way to recognize the disaster of
putting a Cat into an ArrayList and vice versa (and it becomes
exactly like the problems you have when you use legacy, non-type safe
code)
Courtesy : SCJP Study guide by Kathy Sierra and Bert Bates
When you declare you ArrayList like ArrayList list = ... you do not declare the type of object your list will contain. By default, since every type has Object as superclass, it is an ArrayList<Object>.
For good practices, you should declare the type of your ArrayList<SomeType> and, thereby, avoid adding inconsistant elements (according to the type)
Because you haven't defined the generic type of your list it defaults to List<Object> which accepts anything that extends Object.
Thanks to auto-boxing a primitive int is converted to an Integer, which extends Object, when it is added to your list.
Your array only allows int's, so String's are not allowed.
This is because in your method parameter you did not specify a particular type for ArrayList so by default it can accept all type of objects.
import java.util.ArrayList;
public class Test {
//Specify which type of objects you want to store in Arraylist
private static ArrayList tricky(ArrayList<String> list) {
list.add(12345); //This will give compile time error now
return list;
}
public static void main(String[] args) {
int i = 0;
ArrayList<String> list = new ArrayList();
list.add("String is King");
Test.tricky(list);
}
}

Convert an Object[] to a Class<?> type at runtime?

I am having deep trouble and this is advanced Java (I am using Reflection API).
Two questions.
I have the following:
Class<?> clazz = String.class;
Object[] values = new Object[] { "abc", 50L, 20 } // notice the different types
I want to be able to Object[] -> clazz[]. Why am I asking this? Because the type of "clazz" is known at runtime via reflection.
// This works
String[] params = Arrays.asList(values).toArray(new String[values.length]);
// This doesnt
String[] params = Arrays.asList(values).toArray(new clazz[values.length]);
There's something else: Let's say that, at runtime, the class is java.lang.String. In the middle of the creating of the array above, how can I use String.valueOf() on each element?
How can I achieve this?
Thank you!
Explaning more what I am trying to achieve
I have this class:
class A {
public void doIt(String... params) {
...
}
}
And also this class:
class B {
public void doIt(Long... params) {
...
}
}
As you can see, A and B have the same method doIt() but with different argument types.
I the code below will fail:
Object[] params = {1, 2, 3};
new A().doIt(params);
With the exception:
java.lang.ClassCastException: java.lang.Object[] cannot be cast to java.lang.String[]
So, what I am trying to do, is find the type of doIt()'s first param and I am trying to convert Object[] to String/Long/Integer[]. Is is more clear now?
Skipping the comments on whether that kind of coding yields the best program, but answering your very question...
You cannot do new clazz[] as new in Java takes a class name, not an expression which evaluates to a Class object.
More pragmatically: new String[10] is not the same as new String.class[10]. The latter is what your example does. And is simply not supported.
You can do Array.newInstance(clazz, values.length), that seems like it would be what you are trying to do. But I don't see how it helps you. If the elements of the array are of different types, and you end up with an array of Strings (albeit dynamically created), you won't be able to put all of the elements into it anyhow.
Are you looking for Array.newInstance(Class<?>, int)? That's the way to reflectively build an array with a Class object rather than a compile-time type.
You cannot write new clazz[values.length], but you can write Array.newInstance(clazz, values.length).

Java generics and arrays construction

Suppose I have a generic class with a generic parameter T which is a Number subclass. I would like to initialize an array of T during class construction. Is it possible? If yes how? If not why?
public class AClass<T extends Number>{
private T array[];
private int arrayOfInt[];
public AClass(int size){
arrayOfInt = new int[size];
array = ? //what should I put here?
}
}
T is only know at compile time. It is not know at runtime and thus you cannot initilise the contents of the array. However you can create the array, every value will be null.
array = (T[]) new Number[size];
EDIT: The problem with creating instances of any type is you need to know what is the default value you want and which constructor you want to call. e.g. there is no new Double()
As mentioned below, double[] will be more efficient and faster than Number[] and unless you need large long values, it will be able to store every possible value.
If you want to use arrays, there are two options:
Peter Lawrey's answer, array = (T[]) new Number[size];. You have to make sure never to return or pass this variable to code outside of the class that expect it to be an array of a particular type, which will cause an exception.
Declare array as type Number[], then just do array = new Number[size];. The downside of this is that when you get anything out of it you will need to explicitly cast to T to use it as such.
The two are the same after type erasure, and they will both cause unchecked cast warning, so it's really a matter of personal preference. The former is more convenient, while the latter is more formally correct (you are not pretending it's a type it's not).
Alternately, some people will tell you to use an ArrayList<T> instead. But internally, an ArrayList is still implemented using one of these two options.
This is not possible.
Because Java generics use type erasure, the type of T isn't known at runtime, so you can't create an array of it.
Other options than mentioned are to use toArray(T[]) or java.lang.reflect.Array:
public class AClass<T extends Number>{
private T array[];
public AClass(final int size, T[] a){
array = (new ArrayList<T>() {{
for (int i = 0; i < size; i++) {
add(null);
}
}}).toArray(a);
}
public AClass(int size, Class<T[ ]> clazz) {
array = clazz.cast(java.lang.reflect.Array.newInstance(
clazz.getComponentType( ), size));
}
public static void main(String[] args) {
System.out.println("toArray: "
+ new AClass<Double>(42, new Double[]{}).array.length);
System.out.println("java.lang.reflect.Array: "
+ new AClass<Double>(42, Double[].class).array.length);
}
}
PS. solution using reflection is close to one suggested in Langer's Generics FAQ (Utilities.createBuffer): How do I generically create objects and arrays?

Categories

Resources