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[].
Related
An error occurs at new T[5] during compile-time saying => error: generic array creation
and according to my understanding, the array is created during compile-time and since we don't know the type of T at compile-time we cannot instantiate an array.
But
if T gets erased at compile-time and changes to Object then still why this error occurs ? because we can create an array of Object.
// Before Compiling
public class GenericClass<T> {
GenericClass(){
T[] obj = new T[5];
}
}
// After Compiling
public class GenericClass {
GenericClass() {
Object[] obj = new Object[5];
}
}
Similar case, like,
public class GenericClass<T> {
GenericClass(){
T obj = new T(); }}
/* error :required: class
found: type parameter T
where T is a type-variable:
T extends Object declared in class GenericClass*/
according to my understanding, the array is created during compile-time
No, the array is created at runtime.
nd since we don't know the type of T at compile-time we cannot instantiate an array.
Correct.
But if T gets erased at compile-time and changes to Object then still why this error occurs ?
Because "it is erased at compile time and changes to Object" is oversimplified.
Also, generics and arrays don't play nice with each other. The problem is, where the generics part is erased, arrays do not work like that. You can do this:
String[] x = new String[10];
tellMeTheTypeOfMyArray(x);
void tellMeTheTypeOfMyArray(Object[] o) {
System.out.println("Your array's component type is: " + o.getClass().getComponentType());
}
This code will compile and work fine, without error, and prints:
Your array's component type is: java.lang.String
Contrast to generics where you cannot write such a method. You cannot possibly make this:
List<String> x = new ArrayList<String>();
tellMeTheTypeOfMyList(x);
void tellMeTheTypeOfMyList(List<?> o) {
System.out.println("Your list's component type is: " + ??????);
}
work. There's no java code possible here, nothing you can write in place of the ?????? to print String, because that information simply is not there at runtime anymore.
Imagine this code:
// This is written by Wahab today.
class Example<T> {
protected T[] arr;
Example() {
this.arr = new T[10];
}
}
and it worked like you wanted. Then I do:
// Written by me, a year later
class Uhoh extends Example<String> {
Uhoh() {
super();
}
void hmmm() {
System.out.println(this.arr.getComponentType());
}
}
I would obviously expect, nay, demand - that this prints java.lang.String, but it could not possibly do so. Because this is weird and confusing, java has a rule: If you compile your code and you do not see any warnings about generics problems (and did not #SuppressWarnings them away), then this kind of confusion is not likely to happen.
Allowing you to write new T[] and having that just be a silly way to write new Object[] is considered too far gone for this.
So how do I use arrays with generics types?
The same way java.util.ArrayList does it: Do not use generics here. Arrays should pretty much never have T types if you intend to create them inside the generic code. If you have a T[] anywhere in your codebase, then that means you should never be new-ing up anything for it - let the caller of your code do it for you. If you do want to new up new arrays yourself, don't use T, use Object[] as type, and cast to T where needed. This is literally how java's built-in ArrayList class works. Some excerpts copy-pasted straight from its source:
transient Object[] elementData; // non-private to simplify nested class access
public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
}
#SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
Here's an example, again straight from ArrayList's sources (or rather, java.util.Collection defines this, and ArrayList inherits it), where you let the caller provide you with code to make arrays:
default <T> T[] toArray(IntFunction<T[]> generator) {
return toArray(generator.apply(0));
}
Here the caller provides a function that transforms an int into a T[] - it takes the concept of doing new String[10] and turns it into a function, that you then pass along to the toArray method which will then use it (feel free to ignore how it uses it here, it's a bit of a bizarre solution. It works, just - not sure you should be learning lessons about that part).
You use it like this:
List<String> listOfStrings = ...;
String[] convertedToArray = listOfStrings.toArray(String[]::new);
Java arrays know their component type at runtime. When you create an array, you must provide the component type at runtime. But in your GenericClass, it cannot do that because it does not know what T is at runtime. If it creates an Object[], that object will have the wrong runtime class, and that instance is not compatible with the type T[] if T is anything other than Object. You are correct that, within the class, nothing is immediately wrong. But if the claim that the variable is T[] is exposed to an outside scope which expects T to be a more specific type, it can cause a ClassCastException:
// Before type erasure
class GenericClass<T> {
T[] obj;
GenericClass() {
obj = new T[5]; // if hypothetically you could do this
}
T[] getObj() {
return obj;
}
}
class MyCode {
public static void main(String[] args) {
GenericClass<String> foo = new GenericClass<>();
String[] strings = foo.getObj(); // no casts needed; no warnings
}
}
// After type erasure
class GenericClass {
Object[] obj;
GenericClass() {
obj = new Object[5];
}
Object[] getObj() {
return obj;
}
}
class MyCode {
public static void main(String[] args) {
GenericClass foo = new GenericClass();
String[] strings = (String[]) foo.getObj(); // ClassCastException at runtime
}
}
I've got this Generic class, with a method returning a Generic Array:
public class ZTagField<T> extends JTextPane {
public ZTagField(StringBasedFactory<T> factory) {
assert (factory != null);
this.factory = factory;
init();
}
public T[] getItems() {
...
T[] arrItems = (T[]) currentItems.toArray((T[])new Object[0]);
return arrItems;
}
And another one using it:
public class Xxx {
ZTagField<clTag> txtTags = null;
public Xxx() {
txtTags = new ZTagField<clTag>(createFactory());
}
public clTag[] getSelectedTags() {
return txtTags.getItems();
}
}
This latter txtTags.getItems()gives me an exception : ==> Exception [Object cannot be cast to [clTag ????
Can anyone explain me why ?
I've trie to apply as much of this How to create a generic array, to no avail.
I've got an ugly workaround :
return Arrays.asList(txtTags.getItems()).toArray(new clTag[0])
But I'd like to have it in then ZTagFieldClass.
Works as designed: at runtime there is no generic type.
It gets erased, and an array of Object is created. An array of Object can not be cast to another kind of array.
This is one of the restrictions of Java generics, based on the way how they are implemented.
That is way you are advised to be careful using arrays together with generics. You might prefer to use a generic List instead.
And just to be clear about this: yes, you can get arrays to work with generics, as elegantly shown by the other answers. But: you spend your time fighting symptoms doing so. Arrays and generics don't go together nicely in Java. Accept that, use generic lists and thus: fix the problem instead of working around it.
Arrays are reified. That means that they hold a reference to their component type, and when inserting an element, it uses that reference to check if the inserted element is actually a subtype of the component type.
Because of this, to create a T[], you need a concrete reference to the component type T, and since generics are erased, the generic T doesn't count. (That's also why you can't straight up create a T[] like T[] arr = new T[].)
The toArray method of a collection gets around this by having the user pass an array of the component type. But you try to cheat this by casting your Object[] to a T[], which doesn't actually create an array of T (where would the reference to the concrete T come from?). Such a cast would fail if it weren't unchecked, unless T was actually Object.
That's also where the ClassCastException comes from. You create an Object[], so the component type is Object, no matter if you cast it to T[], the component type stays Object. But later on, you know the actual component type you want (clTag):
public clTag[] getSelectedTags() {
return txtTags.getItems();
}
So the compiler will insert an implicit cast here to clTag[]:
public clTag[] getSelectedTags() {
return (clTag[]) txtTags.getItems();
}
But you can not cast an Object[] to a clTag[], just like you can not cast an Object to clTag.
Your workaround works, because you're actually supplying a reference to the component type:
Arrays.asList(txtTags.getItems()).toArray(new clTag[0]) // <-- 'clTag' here
A more modern solution than passing an array of the component type is to pass a IntFuntion<T[]>, which encapsulates an array constructor, to the method:
public T[] getItems(IntFunction<T[]> arrCons) {
...
T[] arrItems = currentItems.toArray(arrCons.apply(0));
return arrItems;
}
...
txtTags.getItems(clTag[]::new);
But you can't get around having to pass the component type in some way or another, unless you switch to returning a List<T> (as GhostCat also suggested). Since generics are not reified, you can create a List<T> without a reference to a component type:
public List<T> getItems() {
...
return new ArrayList<>(currentItems);
}
After compilation, the types are erased.
Since T is not bounded to a specific type, T will be replaced by Object.
So, this :
T[] arrItems = (T[]) currentItems.toArray((T[])...);
return arrItems;
will not create and return an array of the specific type used by the instance of the class at runtime but will only create an array of Object.
Besides, in Collection.toArray() you cannot pass either an array (new T[]) because it is not valid to create a generic array.
Consequently, if you want to use the toArray() method, you can finally only pass an array of Object in this way :
Object[] arrayObject = values.toArray(new Object[currentItems.size()]);
But an array doesn't work as a List type.
An array of a specific type cannot be cast to an array of another type even if the elements that it contains are of the type of the target of the cast.
So, you cannot cast an array of Object to an array of a specific type even if the array contains elements with this specific type such as.
So this will produce a ClassCastException :
clTag[] values = (clTag[]) arrayObject;
To solve your problem :
If you can use Java 8, using a functional interface is really a clean solution.
Jorn Vernee has given a very good answer illustrating it.
Otherwise, before Java 8, the single way to create an array of the same type that the parameterized type used in a generic collection is :
1) Creating a new array with the specified type.
The java.lang.reflect.Array.newInstance(Class clazz, int length) method
allows to create an array of the specified class and length.
2) Storing the class of the declared type in the instance of the generic class. You can do it by adding a class parameter in the constructor of it.
3) Populating it from the elements of the generic collection.
An easy way is using <Object, Object> Object[] java.util.Arrays.copyOf(Object[] original, int newLength, Class<? extends Object[]> newType) method but it is not effective as first you have to convert the collection into an array with toArray() to be able to pass it to the copyOf() method.
For example with a generic List, you could write :
public class ZTagField<T> {
private class<T> clazz;
private List<T> list = new ArrayList<>();
public ZTagField (class<T> clazz){
this.clazz = clazz;
}
public T[] get() {
T[] array = (T[]) Array.newInstance(clazz, list.size());
Class<? extends Object[]> clazzArray = array.getClass();
array = (T[]) Arrays.copyOf(values.toArray(), values.size(), clazzArray);
return array;
}
}
It works but as said it is not effective.
A more effective solution would be iterating on the list and adding elements in the new array instead of using Arrays.copyOf():
public T[] get() {
T[] array = (T[]) Array.newInstance(clazz, list.size());
for (int i = 0; i < values.size(); i++) {
array[i] = values.get(i);
}
return array;
}
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);
}
}
Here is the code I'm using
public class aClass<T> {
private T[] elements;
public aClass(T[] elements) {
this.elements = elements;
}
public void doSomething() {
T[] newArray = (T[]) new Object[5];
...
}
}
I've seen people saying that creating an array like this is a bad idea, due to it being not type safe. However, every time I use it, I have no problems with it. When would creating an array like this cause a problem?
Thanks
Here is an example that causes issues:
public class Main {
public static class List<E extends Number> {
private E[] myE;
public void hello(E e) {
E[] newArray = (E[]) new Object[5];
for (int i = 0; i < newArray.length; i++) {
newArray[i] = e;
}
myE = newArray;
}
}
public static <T> T[] createArray(int size) {
return (T[]) new Object[size];
}
public static void main(String[] args) throws IOException {
List<Integer> integers = new List<Integer>();
integers.hello(5);
}
}
Your code works because when you declare your generic parameter <T> it is unbound, meaning that it extends Object. When you cast your array to (T[])you are actually casting to (Object[]) because that is the best the compiler can do. Now, if you keep your array inside your code, you should not have too many problems. But if somebody outside your code can retrieve that array and has instantiated your class with a type other than object, he will have ClassCastException.
You cannot create an array of T because Java does not know, at run time what is the type of T. This is due to the fact that in Java generics is implemented with type erasure. This means that the compiler discards most of the generic type information after ensuring everything is Ok.
With arrays the story is different, because Java needs to know the exact type of T in order to create the given array, and since such thing cannot be determined you cannot create an array of a generic type.
What you can do is to provide a instance of the actual array that you want to use, and the Java compiler can ensure it is of the appropriate type:
public static <T> void fillWith(T[] destiny, List<? extends T> source){
for(int i=0; i<= destiny.length; i++){
destiny[i] = source.get(i);
}
}
The java.utils.Arrays.copy method offers an alternative carefully using generics and reflections that you can use as a reference for what you want to do.
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
Something that is not type safe doesn't create problems in itself. But it can hide problems at compile time that won't popup until the right moment.
You would be able to fully work in a not type safe environment without having problems, but it's a bad habit just because a type safe environment does guarantee you that you won't have a set of common runtime errors, you don't have any at the moment but you don't have any guarantee, and these are really important, any safety you can trust into means less effort.
The other people are wrong. There is no other way to create the array unless you have an instance of the type at creation time. See also How to create a generic array in Java?
If you have the type instance (= something that has the type Class<T>), you can call java.lang.reflect.Array.newInstance(Class<?>, int) but even then, you'd need the cast, so why bother?
I'd say things were different if the type of elements was Object instead of T but since that's not the case, the code is perfectly OK.
If you want to hide this further, you can write a helper method:
#SuppressWarnings( "unchecked" )
public static <T> T[] createArray( int size ) {
return (T[]) new Object[size];
}
That creates an array without needing a cast when you call it.
It's safer to use a Collection in this cases. Just make something like
...
Collection<T> newElements = new ArrayList<T>(5);
...
Anyway, from what I known creating a generic array like this won't give you real problems, but "smells bad", as it requires explicit type casting. Do you really need an array in this case?
I've seen people saying that creating an array like this is a bad
idea, due to it being not type safe. However, every time I use it, I
have no problems with it. When would creating an array like this cause
a problem?
People say it is a bad idea because, logically, it is not correct to cast an object whose runtime type is Object[] to type T[] if T is not Object.
However, you do not see any immediate problems because, inside the class (within the scope of T), T is erased. So the cast from Object[] to T[] is not checked.
If all you ever do is use this variable inside your class (inside the scope of the type variable T), and you never return it to people outside of the class and there is no way people outside of the class can get access to it, then it will not cause any problems.
In fact, there are benefits to the way you're doing it -- you get all the benefits of generic type checking when getting and setting elements of the array, which you would not get if you simply followed the completely "type-safe" solution of using a variable of type Object[], in which case you would have to manually cast things you get out of it.
You will get a problem if you ever return this array variable to the outside (or otherwise allow the outside to access it) as type T[], because the calling code will expect a certain type of array, and the generics will insert a cast in the calling code when it receives this array, and the cast will fail at runtime (since e.g. a Object[] is not a String[]; they are different types at runtime). Here is a simple example:
public class aClass<T> {
private T[] elements;
public aClass(T[] elements) {
this.elements = elements;
}
public void doSomething() {
elements = (T[]) new Object[5];
...
}
public T[] getArray() {
return elements;
}
}
// some other code...
aClass<String> foo = new aClass<String>(new String[2]);
foo.doSomething();
String[] bar = foo.getArray(); // ClassCastException here
You may have problems with comparissions and printing.. basically any time that knowing the type is important for formatting. the way you have it the system have no clue what data is in the array
I have defined a Java function:
static <T> List<T> createEmptyList() {
return new ArrayList<T>();
}
One way to call it is like so:
List<Integer> myList = createEmptyList(); // Compiles
Why can't I call it by explicitly passing the generic type argument? :
Object myObject = createEmtpyList<Integer>(); // Doesn't compile. Why?
I get the error Illegal start of expression from the compiler.
When the java compiler cannot infer the parameter type by itself for a static method, you can always pass it using the full qualified method name: Class . < Type > method();
Object list = Collections.<String> emptyList();
You can, if you pass in the type as a method parameter.
static <T> List<T> createEmptyList( Class<T> type ) {
return new ArrayList<T>();
}
#Test
public void createStringList() {
List<String> stringList = createEmptyList( String.class );
}
Methods cannot be genericised in the same way that a type can, so the only option for a method with a dynamically-typed generic return type -- phew that's a mouthful :-) -- is to pass in the type as an argument.
For a truly excellent FAQ on Java generics, see Angelika Langer's generics FAQ.
.
.
Follow-up:
It wouldn't make sense in this context to use the array argument as in Collection.toArray( T[] ). The only reason an array is used there is because the same (pre-allocated) array is used to contain the results (if the array is large enough to fit them all in). This saves on allocating a new array at run-time all the time.
However, for the purposes of education, if you did want to use the array typing, the syntax is very similar:
static <T> List<T> createEmptyList( T[] array ) {
return new ArrayList<T>();
}
#Test
public void testThing() {
List<Integer> integerList = createEmptyList( new Integer[ 1 ] );
}
#pauldoo
Yes, you are quite right. It is one of the weaknesses with the java generics imho.
I response to Cheekysoft I'd like to propose to also look at how it is done by the Java people themselves, such as T[] AbstractCollection#toArray(T[] a). I think Cheekysofts version is superior, but the Java one has the advantage of familiarity.
Edit: Added link.
Re-edit: Found a bug on SO :)
Follow-up on Cheekysoft:
Well, as it is a list of some type that should be returned the corresponding example should look something like:
static <T> List<T> createEmptyList( List<T> a ) {
return new ArrayList<T>();
}
But yes, passing the class object is clearly the better one. My only argument is that of familiarity, and in this exact instance it isn't worth much (in fact it is bad).