【问题标题】:How to correctly use Java generics for an array implementation of an ADT如何正确使用 Java 泛型来实现 ADT 的数组实现
【发布时间】:2014-03-12 16:03:03
【问题描述】:

我正在尝试教我的学生正确使用 Java 泛型的方法。我正在教授一门数据结构课程,所以我希望他们使用数组和它们自己的链表,而不是使用 Java 集合(例如没有 ArrayList)。一个典型的问题是为 ADT 提供数组实现。这是一个示例,我尝试使用实现为数组的堆来提供优先级队列。我想支持任何类型的数据对象的优先级队列,所以我使用通用的 我从一个界面开始:

public interface PriQue<E> {
    void insert(int pri, E data);
    E remove(); // return null when empty
    boolean isEmpty();
}

然后我实现了它:

public class ArrayHeap<E> implements PriQue<E> {
//private class Entry<E> {  hides E of ArrayHeap which means remove fails
    private class Entry {  // uses E of ArrayHeap
        int pri;
        E data;
    . . .
    } // end of Entry
    Entry heap[];
    int cnt;
    public ArrayHeap(int size) {
        // heap = new Entry[size]; can not create generic array error
        // heap = (Entry[])new Object[size]; blows up on a cast error
        heap = (Entry[])new Object[size];
        cnt = 0;
    }
    . . .
    public E remove() {
        E tmp = (E)heap[--cnt].getData(); //bad don't want to cast
        // trickle down code here
        return tmp;
    }

由于接口是参数化的,我在实现类中使用了相同的泛型——我认为这没问题。 我需要创建一个包含优先级和相应数据项的条目数组。所以我创建了一个私人课程。我的问题从这里开始。如果我不考虑参数,那么因为它是 ArrayHeap 的一部分,它应该在那里选择 E 泛型形式。当我尝试创建一个条目数组时,它会编译但会爆炸。或者,我可以有一个明确的通用参数来输入。如果我选择 E,它将从 ArrayHeap 中“隐藏”E,当我尝试从数组中删除一个 E 时,它不会意识到它与 ArrayHeap E 是同一个 E。如果我将它命名为其他名称,比如 V,那么它就不能将 E 分配给 V,我无法存储任何数据。我认为这是实现数据结构时相当常见的情况。
我试过阅读http://www.angelikalanger.com/GenericsFAQ/FAQSections/ParameterizedTypes.html#FAQ104 但它不包括嵌套类(主要是关于什么不起作用) 我尝试阅读 Java 集合映射实现的代码,但看不出有什么不同。

【问题讨论】:

  • 你用它的自己的泛型类型声明你的内部类——比如V。然后,当您声明 instance 时,如果内部类使用 E - Entry&lt;E&gt; heap[];。这是当您将V 设置为等于E 时。例如,看看the source for AbstractMap,它具有完全相同的模式。您不能创建通用数组;这似乎是你的主要问题。
  • 当您使用Entry&lt;E&gt; 而不是Entry 时,remove() 如何失败?
  • 你看过GrepCode上HashMap的源码了吗?它使用带有泛型的嵌套类:grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… 另外,当您说“应该在那里选择 E 泛型形式”时,我不明白您的意思。你能澄清一下吗?
  • @BoristheSpider 是对的。你必须声明它 Entry heap[];
  • 如果我声明条目,请在 @Rohit Jain 评论上

标签: java arrays generics adt


【解决方案1】:

这对我有用:

public class ArrayHeap<E> implements PriQue<E> {
    private static class Entry<E> {
        int pri;
        E data;

        // ...

        public E getData() {
            return data;
        }
    }

    Entry<E>[] heap;
    int cnt;

    public ArrayHeap(int size) {
        heap = (Entry<E>[])new Entry<?>[size];
        // or: heap = new Entry[size];
        cnt = 0;
    }

    public E remove() {
        E tmp = heap[--cnt].getData();
        return tmp;
    }

    // ...
}

关键是Entry 被更改为静态嵌套类,而不是非静态内部类(仍将取决于外部类的类型参数)。然后,您实际上可以创建一个 Entry&lt;?&gt; 数组,并从中进行转换。

【讨论】:

  • 您甚至可以使用Entry&lt;E&gt;[] heap = new Entry[size] 并用强制转换换取编译器警告。值得指出的是,OP 也应该停止使用旧的数组表示法。
  • @BoristheSpider 是的,确实如此。 (事实上​​,即使是强制转换也会导致编译器警告。)
  • @BoristheSpider:除了应该避免使用原始类型
  • @newacct 如果您从事滥用数组以获得泛型类型的业务,那么 rawtypes 可能是您遇到的最少问题。虽然我完全同意,但 rawtypes 是邪恶的。
  • 是的,所有这些@BoristheSpider cmets 都表明 Java 可能不是非常可用。但是,不知何故人们使用它。
【解决方案2】:

问题在于,因为 Java 在编译时会删除泛型类型参数,所以创建包含泛型类型的数组是不安全的。您将需要在某个地方进行演员表:您能做的最好的事情就是将其隐藏在辅助函数中。

例如,如果您查看 ArrayList 的 JDK 实现,您会发现它的核心是 Object[]

【讨论】:

  • 我很清楚,在编译结束时删除泛型类型的决定是为了避免不得不更新被认为广泛分布于更改的 JVM,这意味着 Java 中的所有内容(其他比原语)在运行时最终成为一个对象。问题是 - 这个问题是否足以使用 Java 来教授 ADT。鉴于 Java 集合类似乎工作正常,似乎有一些方法可以做到这一点。
猜你喜欢
  • 1970-01-01
  • 2020-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多