Different Java behavior for Generics and Arrays - java

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);
}
}

Related

Generic Array initialisation

I have read through different articles which talks about why we cannot create generic array in java, but still I don't quite understand why.
For example, it this post, it assumed if generic array initialisation is possible, there will be casting issue after erasure. You can find the details in section 2. Considerations When Using Generic Arrays. In simplest term, the generic array becomes an Object Array after erasure, and if the generic type is String, java will fail to cast Object[] to String[].
However, I created a generic class with a simple function,
// Test.java
public class Test<T> {
public T[] getStrArr(T[] arr) {
return arr;
}
}
//Main.java
public static void main(String[] args) {
Test<String> test = new Test<>();
String[] strArr = test.getStrArr(new String[]{"A", "B", "C"});
}
After erasure, the getStringArr should return Object[], and it is able to cast to String[] without any problem.
Another stackoverflow post stated that:
arrays (unlike generics) contain, at runtime, information about its component type. So you must know the component type when you create the array. Since you don't know what T is at runtime, you can't create the array.
but erasure will change T into Object type, so compiler can create array with Object type.
There are other posts with similar explanation but cannot really resolve my doubt.
Please help!
After erasure, the getStringArr should return Object[], and it is able
to cast to String[] without any problem.
Return type of the getStrArr, after type erasure, would be Object[] but, in your code, it is returning arr which is of type String[]. That is why there is not ClassCastException in your code.
Consider the following method (suppose generic arrays were allowed):
public T[] foo() {
return new T[5];
}
After type erasure, new T[5] will be replaced by new Object[5]. Now if the calling code calls this method as:
String[] strArr = obj.foo();
It will lead to ClassCastException because Object[] cannot be casted to String[].

Java - weird generics behavior with arrays

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.

Java Collection Type Parameter to array

I want to create a helper method which gets Collection type parameter to return a list. This is what I have now:
public class Helper {
public static <T> T[] CollectionToArray(Collection<T> collection) {
return collection.stream().toArray(Object[]::new); // Error
}
public static <T> T[] ListToArray(List<T> list) {
return list.stream().toArray(Object[]::new); // Error
}
}
public class IamSoNoob {
public void PleaseHelpMe() {
List<String> list = new ArrayList<>();
Set<String> set = new HashSet<>();
String[] arrayFromList = Helper.CollectionToArray(list); // Error
String[] arrayFromSet = Helper.CollectionToArray(set); // Error
String[] array = Helper.ListToArray(list); // Error
}
}
My questions are:
Is it possible to complete CollectionToArray(Collection<T>)?
If so, how?
Also, is it possible to pass List and Set as a parameter in the first place?
Is it possible to complete ListToArray(List<T> list)?
If so, how?
But here are some restrictions due to my personal taste.
I don't want to use #SuppressWarnings
I really want to keep the part .stream().toArray(Object[]::new) (Java 8 part!)
And I have a feeling that I need to fix the part Object[]::new by using something like: <T extends Object> or <? extends T> but I can't really figure out.
Please help me out, and please provide an explanation as well, I am often confused by Generic and ?.
No, you absolutely cannot do it, if it were possible the library method Collection.toArray() would've given you the same type as your LHS but instead when you want the exact type as your LHS you have to use Collection.toArray(T[]) (even that comes with ArrayStoreExceptions i.e it is up to the programmer to provide the right type for the array), the reason being that in your toArray() you've specified Object[] to be your array and later you cannot cast it to any other type or else it will result in a ClassCastException.
The reason for all this hullabaloo is because of the way generics works i.e its a compile time thing and at runtime Java erases all type parameters to their upper bound types and hence losing type information which is required for creating arrays.
One safe way of doing it is by adding another paramter to you helper method as
public static <T> T[] CollectionToArray(Collection<T> collection, T[] arr) {
return collection.stream().toArray(value ->Arrays.copyOf(arr,collection.size()));
}
and using it as
String[] arrayFromList = Helper.CollectionToArray(list, new String[0]);
but then everybody's better off using
Collection#toArray(T[]).

How and when are variables casted to types declared in generic type parameters?

public static void main(String[] args) {
List<Integer> integers = new ArrayList<>();
integers.add(5); //element #0
List list = integers;
list.add("foo"); //element #1
integers.get(1); //no error
System.out.println(integers.get(1)); //no error, prints "foo"
Integer i = integers.get(1); //throws ClassCastException
}
I'm trying to understand the process of casting variables of type, declared as a generic type parameter, and I'm a bit confused.
So, you may see in the example I've provided, that after we create a non-parametrized List, which refers to the same object that List<Integer>, then we can add any objects to that list (OK, nothing surprising here) and, what confuses me so much, we can extract non-Integer values from List<Integer> integers. Why isn't ClassCastException thrown at the first or the second call of integers.get(1)?
I assumed that methods returning parameter types, in fact always return Object and those returned values are implicitly tried to be converted to
l-value type or method parameter type at runtime (as there are no generics at runtime), however the following test convinced me that Integer is always preferred over Object:
public static void main(String[] args) {
List<Integer> integers = new ArrayList<>();
integers.add(5); //element #0
List list = integers;
list.add("foo"); //element #1
print(integers.get(1));
}
private static void print(Object var) {
System.out.println(var);
}
//this method is entered
private static void print(Integer var) {
System.out.println(var);
}
private static void print(String var) {
System.out.println(var);
}
Another interesting fact is that although elements of ArrayList are stored in Object[] array, they are always converted to a type defined in type parameter before being returned in method get():
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
So, if anyone may point me to the documentation where these questions are explained step by step, I would be very thankful
The compiler inserts casts when casts are needed. The method System.out.println has a parameter of type Object, so no cast to Integer is required.
In the case of your three print methods, the method with a parameter of type Integer is chosen, so the compiler inserts a cast. The choice of which of the three methods to use occurs at compile time based on a complicated set of rules. These rules use the generic information to see that integers.get(1) has type Integer, and so the Integer version is chosen and the cast is needed. As a result, the code is more or less equivalent to Java 4 code
List integers = new ArrayList();
integers.add("foo");
integers.add(Integer.valueOf(5)); // No autoboxing in Java 4!
print((Integer) integers.get(1)); // Cast inserted by compiler
The cast to (E) in the final part of your question does not actually do anything at runtime, and so will not throw a ClassCastException. It is only needed to make the code compile. You are telling the compiler that, yes, you are sure the Object is really an E and won't cause an exception later on (although you have subverted that by mixing raw and generic types).

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