I'm getting the error "Cannot create a generic array" for the following code:
public class MapImpl<K, V> {
private static int DEFAULT_CAPACITY = 16;
private int size;
// array holding the entries of the map
private Entry[] entries;
public MapImpl() {
entries = new Entry[DEFAULT_CAPACITY]; // error at this line: Cannot create a generic array of MapImpl<K,V>.Entry
}
// represents an entry in the map
private class Entry {
private K key;
private V value;
public Entry(K key, V value) {
this.key = key;
this.value = value;
}
}
Surprisingly, this works fine:
public class MapImpl<K, V> {
private static int DEFAULT_CAPACITY = 16;
private int size;
// array holding the entries of the map
private Entry<K, V>[] entries;
public MapImpl() {
entries = new Entry[DEFAULT_CAPACITY];
}
// represents an entry in the map
private class Entry<K, V> {
private K key;
private V value;
//but here K and V are being hidden.
public Entry(K key, V value) {
this.key = key;
this.value = value;
}
}
}
I do understand that we can't create an array of generic type or a type that takes type parameters. But, here in my code the Entry class is not of a generic type. Am i overlooking something ?
5 Answers 5
Problem here is that non-static nested class has access to all members of its outer classes, which includes information about generic types used in outer class, like
class Outer<T>{
private T t;
class Inner{
void method(T tt){//we can use same type T as used in outer class
t = tt;
}
}
}
So in reality Inner
class type is more like Outer<T>.Inner
which makes form of it generic type and arrays can't be created from generic types because of type erasure which would prevent arrays from being able to test if added elements are valid.
Most common solution in that cases is to use collections instead of arrays like List<OurType>
.
But if you really want to have only arrays then other possible solution (but you should try to avoid it) is to use raw type, so instead of
new Entry[DEFAULT_CAPACITY];
which is equivalent of
new MapImpl<K, V>.Entry[DEFAULT_CAPACITY];
you could use
new MapImpl.Entry[DEFAULT_CAPACITY];
// ^no generic type -> it is raw type
Solution with
private class Entry<K, V> {
private K key;
private V value;
//but here K and V are being hidden.
public Entry(K key, V value) {
this.key = key;
this.value = value;
}
}
works probably (I can't find any relevant JLS describing this) because as you said, you have shadowed original K
and V
from outer class, which means you can't access them and now
void method(T tt){
t = tt;
}
method will not compile because T
from inner class is not the same as T
from outer class. Because of that, Entry
is no longer MapImpl<K,V>.Entry
but MapImpl.Entry<K,V>
and when you write it as
new Entry[...]
you are explicitly making it raw type which will work (with compilation warning about rawtypes when you declare private Entry[] entries
)
-
1Thanks for reading my question carefully and giving the perfect explanation.geekprogrammer– geekprogrammer2015年05月04日 12:59:55 +00:00Commented May 4, 2015 at 12:59
Declare Entry
class as static. Currently it's not static, so it's implicitly linked to the MapImpl instance and to its generic arguments.
Edit: I mean
private static class Entry<K, V>
-
1It does not help. In this case OP will have to make it generic implicitly.AlexR– AlexR2015年05月04日 11:33:22 +00:00Commented May 4, 2015 at 11:33
-
@Tagir Valeev, not really. In that case I can't make a reference of K and V in Entry class.geekprogrammer– geekprogrammer2015年05月04日 11:34:47 +00:00Commented May 4, 2015 at 11:34
-
Added an explanation.Tagir Valeev– Tagir Valeev2015年05月04日 11:39:13 +00:00Commented May 4, 2015 at 11:39
-
In that case , the type parameters K&V of Entry class hides that of MapImpls's K&V, which probably is not correct.geekprogrammer– geekprogrammer2015年05月05日 04:27:59 +00:00Commented May 5, 2015 at 4:27
Since Entry
is an inner class of a generic class MapImpl
, it is also parametrized by K
and V
. To make an array, you will have to create it with raw type:
entries = new MapImpl.Entry[DEFAULT_CAPACITY];
-
thanks for the answer. May I know the difference between new Entry[DEFAULT_CAPACITY] and new MapImpl.Entry[DEFAULT_CAPACITY] ?geekprogrammer– geekprogrammer2015年05月04日 11:59:02 +00:00Commented May 4, 2015 at 11:59
-
As I wrote in the answer, by declaring
Entry
as an inner class ofMapImpl
, you are tyingEntry
to an instance ofMapImpl
. That makesEntry
generic byMapImpl
's parametric types. The full name of classEntry
isMapImpl<K,V>.Entry
. By creating array ofMapImpl.Entry
, you make the type raw rather then generic.Misha– Misha2015年05月04日 12:25:25 +00:00Commented May 4, 2015 at 12:25
It's because Java's 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 K
and V
are at runtime, you can't create the array.
Try below code instead
package com.vibhs.stack.overflow.generics;
public class MapImpl<K, V> {
private static int DEFAULT_CAPACITY = 16;
private int size;
// array holding the entries of the map
private Entry[] entries;
public MapImpl() {
entries = new Entry[DEFAULT_CAPACITY]; // error at this line: Cannot
// create a generic array of
// MapImpl<K,V>.Entry
}
// represents an entry in the map
private class Entry<K,V> {
private K key;
private V value;
public Entry(K key, V value) {
this.key = key;
this.value = value;
}
}
}
-
Yes, I know that it works.I just edited my post. But i want to know the reason behind it.geekprogrammer– geekprogrammer2015年05月04日 11:40:42 +00:00Commented May 4, 2015 at 11:40
Entry
an inner class? See stackoverflow.com/questions/70324/….