【问题标题】:ClassCastException when extending a generic class扩展泛型类时出现 ClassCastException
【发布时间】:2015-07-07 20:07:59
【问题描述】:

假设我有一个类Text,它实现了一个名为Drawable 的接口,其定义如下:

public interface Drawable {

  public void draw (DrawConfig drawConfig);
}

我想要一个对象,它的作用类似于一个包含项目的数组,但它也实现了Drawable,以便能够编写drawables.draw(drawConfig) 并将调用转发给它的所有子代,而不必执行 for环形。所以我首先创建了一个如下所示的包装类:

public class Drawables implements Drawable {

  private final Array<Drawable> items = new Array<Drawable>();

  public void add (final Drawable value) {
    this.items.add(value);
  }

  @Override
  public void draw (final DrawConfig drawConfig) {
    for (final T item : this.items)
      item.draw(drawConfig);
  }
}

第一个问题是因为它是一个包装器,它没有任何 Array 类方法,我必须手动添加我需要的方法(例如示例中的 add()以上)。

第二个问题是如果我只存储Text对象在里面,我不能写:

for (Text t : drawables)
    t.setText("blablabla");

这意味着我必须创建 another 数组,我最终会为此创建两个数组。我想避免这种情况,所以我考虑使用泛型,因为 Array 类是泛型的,我将 Drawables 类变成了这个:

public class Drawables<T extends Drawable> extends Array<T> implements Drawable {

  @Override
  public void draw (final DrawConfig drawConfig) {
    for (final T item : this.items) // Line 17
      item.draw(drawConfig);
  }
}

现在我可以写了:

Drawables<Text> drawables = new Drawables<Text>();

drawables.add(new Text());
drawables.add(new Text());

for (Text t : drawables)
    t.setText("blablabla");

drawables.draw(drawConfig);

然后编译!! 除了在运行时我收到以下错误:

java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [La.b.c.ui.Drawable;
    at a.b.c.Drawables.draw(Drawables.java:17)
    at a.b.c.ui.components.Text_UseTest.render(Text_UseTest.java:80)
    at com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop(LwjglApplication.java:215)
    at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:120)

请参阅我的代码中第 17 行的注释。

有什么问题?甚至有可能实现我想做的事情吗?

【问题讨论】:

标签: java arrays generics libgdx


【解决方案1】:

我认为您的设计不正确。 Drawable 不应将绘制单个项目和绘制多个项目分开,而应仅在其合同中指定它根据某些配置进行绘制。它如何做到这一点,或者它使用什么其他对象来做到这一点,并不重要。

因此,我会先这样做:

public interface Drawable {
    void draw (DrawConfig drawConfig);    
}

现在看来您希望您的Drawable 维护一个私人成员。这个私人成员对事物的绘制方式有一定的影响。有趣的是,它可能是被绘制的东西本身,或者它可能包含需要绘制的东西,或者它可能完全是其他东西。我们目前不知道它将如何影响draw 的行为。那么我们该如何处理呢?那么我们可以创建一个抽象类来维护这个私有成员并实现Drawable:

public abstract class AbstractDrawable<T> implements Drawable {    
    protected T data;           
}

请注意,draw 没有实现。这是故意的,因为只有在具体实现中我们才会真正为draw 定义一个行为。您现在可以很容易地看到我们如何拆分两个关注点(维护状态和实现行为)。现在我们可以定义一个具体的实现来定义如何它将保持状态以及如何它将实现行为:

public class Drawables<T extends Drawable> extends AbstractDrawable<List<T>> {

    public Drawables() {
        this.data = new ArrayList<T>();
    }

    public void addDrawable(T item) {
        this.data.add(item);
    }

    @Override
    public void draw (final DrawConfig drawConfig) {
        for (final T item : this.data) {
            item.draw(drawConfig);
        }
    }
}

这里我们有一个非常具体的AbstractDrawable&lt;T&gt; 实现,它本身与一个可绘制列表一起工作。这个具体的实现定义了它如何管理状态(它使用Drawable 实例的内部列表)以及它如何管理行为(它的“绘制”行为是“绘制”它维护的每个Drawable 实例)。

你提到你想要所有的列表方法等等。扩展一个类只是为了访问它的方法不是一个好主意。您的可绘制项目列表并不是真正的列表。它是一个可绘制对象,恰好与可绘制对象列表一起使用。上面的方法隐含地使用了组合而不是继承,因为AbstractDrawable&lt;T&gt; 封装了一些内部数据,这些数据将以某种方式绘制(不重要如何完成;只是完成了)。因此,您现在可以维护一个内部列表,而不是扩展List&lt;T&gt;,然后您可以以您选择的方式“绘制”该列表。这正是具体实现Drawables&lt;T extends Drawable&gt;中发生的情况。

您的下一个问题是关于无法在 Text 实例上调用 t.setText()。唯一可以做到这一点的方法是,如果您有一个专门用于Text 实例的具体实例,或者如果您在draw 内部执行了instanceof 检查。这是因为你只有T extends Drawable;班级知道它是某种Drawable,并且它能够draw。除此之外,它不知道具体的实现。

【讨论】:

  • 为此,AbstractDrawable 必须声明为 abstract,data 必须声明为 protected。另外,我尝试了您的代码,但我无法实例化 Drawables 类,因为无论我将类型 T 传递给它,我都有一个错误。
  • @Flawyte 编辑了错别字。你永远不会直接实例化Drawable。我假设你有像Text 这样实现Drawable 的东西。所以你要创建的是Drawables&lt;Text&gt; 的一个实例。你也可以创建一个Drawables&lt;Drawable&gt; 的实例,并给它任何实现Drawable 的东西。
  • 是的,现在编辑您的帖子我可以做Drawables&lt;Text&gt; drawables = new Drawables&lt;Text&gt;(),但我有两个问题:1)我仍然需要手动将add()、remove() 方法等添加到AbstractDrawable 和2 ) 我不能再循环我的 drawables 对象了,因为它不是 Iterable。
  • 我不认为这些是问题。您必须考虑如何使用您的Drawables 对象。你更关心它里面的东西被绘制,还是你关心那些东西本身的访问权?将add 和remove 包括在内,然后委托给内部列表,并没有太多的工作。如果您真的想访问可绘制列表,您可以添加一个返回内部实例的方法。但我不明白为什么你不能维护一个外部列表,然后只向Drawables 提供元素。尽量不要将太多职责分配给一个类 (SRP)。
【解决方案2】:

类 Array 实现了 Iterable。所以你可以这样写。

@Override
public void draw (final DrawConfig drawConfig) {
  for (final T item : this) // Line 17
      item.draw(drawConfig);
}

【讨论】:

    【解决方案3】:

    擦除后的问题:

    for (final Drawable item : this.items) // this.items is of Object[]!!!
    

    现在 for 循环不知道如何转换(java 中没有隐式转换)。你不能简单地写Drawable item = new Object();。你必须自己做演员:

    for (Object o : this.items) {
      Drawable item = (Drawable) o;
      item.draw(drawConfig);
    }
    

    相关:Cast element in Java For Each statement

    【讨论】:

    • 铸造每件物品不是很贵吗?当我扩展Array&lt;T&gt; 时,我不明白为什么在实例化它时它被视为Object 的数组而不是Text 的数组。
    • @Flawyte,如果您想了解擦除以及为什么需要擦除,请查看this question。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-28
    相关资源
    最近更新 更多