使用Array作为自定义通用Hashtable中的表来存储条目

时间:2017-05-23 13:13:05

标签: java arrays generics hashmap

我正在尝试将我自己的自定义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;
}
}

2 个答案:

答案 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;没有捕捉到你所看到答案的关键要素。

有两个潜在的问题

  1. Creating an array whose element type is drawn from a type parameter
  2. 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)
    }
    
    1. Creating an array whose element type is parameterized
    2. 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);这种方法可能更合适吗?

      对于任何事情来说,反射很少是更好的答案。只有在编译时没有所需的所有类型信息时才有意义,而这不是你的情况。