我正在尝试将我自己的自定义Hashtable作为理解数据结构的一种方式,并且遇到了许多其他人似乎遇到的问题;您不能像创建另一个数组一样创建通用数组。我理解这个的原因,但也知道Java的HashMap本身使用一个数组来存储Entry项。如果我的理解是正确的,Java的HashMap会创建一个object[]
,然后在每次调用put或get方法时将对象的每个元素强制转换为Entry类。它是否正确?
我已经阅读了有关泛型数组的答案,说可以改为将Entry[] table
作为类变量,然后在构造函数中使用table = (Entry[]) new Object[size];
,以避免必须进行转换在put和get方法中,这确实导致了ClassCastException,这是可以理解的,因为它必须检查Object数组的每个元素,以确保它们是Entry类。这是否意味着我不能在我的自定义Hashtable中使用此方法?
最后,另一种创建Entry数组的方法似乎是检查构造函数中的类类型并使用Entry[] table = (Entry[]) Array.newInstance(c, s);
这种方法是否更合适?
以下是与此问题相关的我自己的代码片段。我想知道我对上面所有内容的解释是否正确,如果这是一种可以接受我自己的Hashtable的方式。我也理解我使用确定给定hashCode索引的方法可能不正确,但这超出了我的问题范围:),而且我的put和get方法肯定是不完整的!
public class HashTable<K, V> {
Object[] buckets;
HashTable(int size) {
buckets = new Object[size];
this.size = size;
}
void put(K key, V value) {
int i = key.hashCode()%size;
buckets[i] = (Entry) new Entry(key, value, (Entry) buckets[i]);
}
K get(K key) {
int i = key.hashCode()%size;
Entry entry = (Entry) buckets[i];
return entry.key;
}
}
答案 0 :(得分:1)
看起来你的Entry
类是一个内部类,我推荐它反对,因为它使事情变得更复杂。首先,让我们假设我们没有内部阶级。
为了说明,我们有一个简单的泛型类:
class Foo<T> {}
这两种通用数组类型之间存在差异:
class Container<T> {
// creating an array with erasure of T[]
// vvvvvvvvvvvvv
T[] arrA = (T[]) new Object[N];
// creating an array with erasure of Foo<T>[]
// vvvvvvvvvv
Foo<T>[] arrB = (Foo<T>[]) new Foo[N];
// Note that the following would be slightly
// better because it doesn't use a raw type,
// but it doesn't work for this illustration
// because it's not the erasure of Foo[]:
// (Foo<T>[]) new Foo<?>[N];
}
Casting检查类型的擦除,所以假设我们创建一个新容器并将这些数组分配给外部世界:
Container<String> c = new Container<String>();
String[] arrA1 = c.arrA;
Foo<String>[] arrB1 = c.arrB;
// After erasure these assignments become:
String[] arrA1 = (String[]) arrA;
Foo[] arrB1 = arrB;
第一个作业arrA1 = c.arrA
会引发ClassCastException
,但第二个作业arrB1 = c.arrB
则没有。这是因为在第一种情况下转换是从Object[]
到String[]
,而在第二种情况下没有经过检查的转换,因为Foo<T>
的所有参数化在擦除后都变为Foo
这就是为了解释我的下一点,即创建一个参数化类型的数组比创建一个类型变量数组更容易接受。在类型变量数组的情况下,我们将Object[]
伪装成T[]
,但在参数化类型的情况下,我们实际上有一个Foo[]
的数组,它是&#39; s只是没有检查Foo
的类型参数。换句话说:
Container<String> c = new Container<String>();
// Recall that this assignment doesn't throw a ClassCastException
Foo<String> arrB = c.arrB;
Object[] arrBAsOBj = arrB;
// This assignment throws an ArrayStoreException
arrBAsObj[0] = new StringBuilder();
// This assignment does not throw an ArrayStoreException
arrBAsObj[0] = new Foo<Integer>();
尽管如此,我还是要注意,您不应该将通用数组暴露给外界。我只是这样做来说明解释。
无论如何,如果您正在编写类似哈希表的内容,那么创建一个参数化类型的未经检查的数组是可以接受的。我通常会写一个这样的辅助方法:
private static <K, V> Map.Entry<K, V>[] createUncheckedArray(int length) {
@SuppressWarnings("unchecked")
final Map.Entry<K, V>[] unchecked =
(Map.Entry<K, V>[]) new Map.Entry<?, ?>[length];
return unchecked;
}
不要将它归还给外界,因为我们实际上还没有一个通用数组,只是一个带有未经检查的类型参数的Map.Entry
数组。
在我们实际需要一个固定长度的容器时,对于这种情况,Java应该只有一个像Array<T>
这样的简单类。
对于内部类,您必须使用参数化类型作为限定符,如下所示:
private Entry[] createUncheckedArray(int length) {
@SuppressWarnings("unchecked")
final Entry[] unchecked =
(Entry[]) new HashTable<?, ?>.Entry[length];
return unchecked;
}
答案 1 :(得分:1)
如果我的理解是正确的,那就是Java的HashMap 创建一个
object[]
,然后将对象的每个元素转换为 每次调用put或get方法时的入口类。这是 正确的吗?
标准库的源代码可用。你可以check it for yourself。如果你这样做,你会发现不,那不是java.util.HashMap
所做的那样。
我已阅读有关通用数组的答案,说明有可能 而是做一些像Entry []表作为类变量和 然后使用table =(Entry [])new Object [size];
如果这些答案完全按照你的描述推荐,那就错了。但是,我怀疑你的&#34;类似于&#34;没有捕捉到你所看到答案的关键要素。
有两个潜在的问题
class MyClass<T> {
// CAN'T DO THIS:
T[] array = new T[2];
// can do this:
T[] array = (T[]) new Object[2];
// or this:
Object[] array = new Object[2]; // (and cast later)
}
class MyOtherClass<T> {
// CAN'T DO THIS, EITHER:
SomeType<T>[] array = new SomeType<T>[2];
// can do this:
SomeType<T>[] array = (SomeType<T>) new SomeType[2];
// or this:
SomeType[] array = new SomeType[2]; // (and cast later)
}
正如您将在JDK源代码中看到的那样( 按照上述链接,对吗?),HashMap
的问题属于第二类,以及它确实是创建一个合适的原始类型的数组,然后将其转换为所需的参数化类型 - 这将使编译器的类型安全警告绊倒,但实际上只要没有其他原始类型,它就是完全类型安全的或不同参数化的参考逃逸。
在构造函数中作为 避免在put和get中进行转换的方法 方法,但这确实导致了ClassCastException [...]。做这个 意味着我不能在我的自定义Hashtable中使用此方法吗?
是的,当然可以。您描述和演示的方法无效,正如例外情况所示。 Object[]
不是Entry[]
。但那并不是你所评论的答案所暗示的那样。
最后,创建Entry数组的另一种方法似乎是检查 构造函数中的类类型并使用Entry [] table =(Entry []) Array.newInstance(c,s);这种方法可能更合适吗?
对于任何事情来说,反射很少是更好的答案。只有在编译时没有所需的所有类型信息时才有意义,而这不是你的情况。