扩展泛型时出现 ClassCastException class
ClassCastException when extending a generic class
假设我有一个 class Text
,它实现了一个名为 Drawable
的接口,定义如下:
public interface Drawable {
public void draw (DrawConfig drawConfig);
}
我想要一个对象,它的行为类似于一个包含项目的数组,但它也实现了 Drawable
,以便能够编写 drawables.draw(drawConfig)
并将调用转发给它的所有子代不得不做一个for循环。所以我首先创建了一个包装器 class,它看起来像这样:
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
class 方法,我必须手动添加我需要的(如上例中的 add()
)。
第二个问题是如果我里面只存Text
个对象,我写不出来:
for (Text t : drawables)
t.setText("blablabla");
这意味着我必须制作 另一个 数组,为此我将得到两个数组。我想避免这种情况,所以我考虑使用通用性,因为 Array
class 是通用的,我把我的 Drawables
class 变成了这个:
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.run(LwjglApplication.java:120)
请参阅我的代码中第 17 行的注释。
问题是什么?甚至有可能实现我想做的事情吗?
擦除后的问题:
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
我认为您的设计不正确。 Drawable
不应将绘制单个项目和绘制多个项目分开,而应仅在其合同中指定根据某些配置进行绘制。它是如何做到这一点的,或者它使用什么其他对象来做到这一点并不重要。
因此,我首先会这样做:
public interface Drawable {
void draw (DrawConfig drawConfig);
}
现在看来您希望 Drawable
维护一个私有成员。这个私人成员对事物的绘制方式有一定影响。有趣的一点是,它可能是被绘制的东西本身,或者它可能包含需要绘制的东西,或者它可能完全是其他东西。目前我们不知道 它会如何影响 draw
的行为。那么我们如何处理呢?那么我们可以创建一个抽象 class 来维护这个私有成员并实现 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<T>
实现,它本身与可绘制对象列表一起使用。这个具体实现定义了它如何管理状态(它使用 Drawable
个实例的内部列表)以及它如何管理行为(它的 "draw" 行为是 "draw" 每个 Drawable
它维护的实例)。
您提到您需要所有列表方法等。扩展 class 只是为了访问其方法并不是一个好主意。您的可绘制项目列表并不是真正的列表。它是一个恰好与可绘制对象列表一起使用的可绘制对象。上面的方法隐含地使用组合而不是继承,因为 AbstractDrawable<T>
封装了一些将以某种方式绘制的内部数据(不重要 如何 这是完成的;只是它完成了) .因此,您现在可以维护一个内部列表,而不是扩展 List<T>
,然后您可以按照您选择的方式 "draw"。这正是具体实现中发生的事情 Drawables<T extends Drawable>
.
您的下一个问题是关于无法在 Text
个实例上调用 t.setText()
。唯一可以做到这一点的方法是,如果你有一个具体实例,专门用于 Text
个实例,或者如果你在 draw
中执行 instanceof
检查。这是因为你只有T extends Drawable
; class 知道它是某种 Drawable
,并且它能够 draw
。除此之外,它不知道具体的实现。
class数组实现了Iterable。所以你可以这样写。
@Override
public void draw (final DrawConfig drawConfig) {
for (final T item : this) // Line 17
item.draw(drawConfig);
}
假设我有一个 class Text
,它实现了一个名为 Drawable
的接口,定义如下:
public interface Drawable {
public void draw (DrawConfig drawConfig);
}
我想要一个对象,它的行为类似于一个包含项目的数组,但它也实现了 Drawable
,以便能够编写 drawables.draw(drawConfig)
并将调用转发给它的所有子代不得不做一个for循环。所以我首先创建了一个包装器 class,它看起来像这样:
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
class 方法,我必须手动添加我需要的(如上例中的 add()
)。
第二个问题是如果我里面只存Text
个对象,我写不出来:
for (Text t : drawables)
t.setText("blablabla");
这意味着我必须制作 另一个 数组,为此我将得到两个数组。我想避免这种情况,所以我考虑使用通用性,因为 Array
class 是通用的,我把我的 Drawables
class 变成了这个:
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.run(LwjglApplication.java:120)
请参阅我的代码中第 17 行的注释。
问题是什么?甚至有可能实现我想做的事情吗?
擦除后的问题:
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
我认为您的设计不正确。 Drawable
不应将绘制单个项目和绘制多个项目分开,而应仅在其合同中指定根据某些配置进行绘制。它是如何做到这一点的,或者它使用什么其他对象来做到这一点并不重要。
因此,我首先会这样做:
public interface Drawable {
void draw (DrawConfig drawConfig);
}
现在看来您希望 Drawable
维护一个私有成员。这个私人成员对事物的绘制方式有一定影响。有趣的一点是,它可能是被绘制的东西本身,或者它可能包含需要绘制的东西,或者它可能完全是其他东西。目前我们不知道 它会如何影响 draw
的行为。那么我们如何处理呢?那么我们可以创建一个抽象 class 来维护这个私有成员并实现 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<T>
实现,它本身与可绘制对象列表一起使用。这个具体实现定义了它如何管理状态(它使用 Drawable
个实例的内部列表)以及它如何管理行为(它的 "draw" 行为是 "draw" 每个 Drawable
它维护的实例)。
您提到您需要所有列表方法等。扩展 class 只是为了访问其方法并不是一个好主意。您的可绘制项目列表并不是真正的列表。它是一个恰好与可绘制对象列表一起使用的可绘制对象。上面的方法隐含地使用组合而不是继承,因为 AbstractDrawable<T>
封装了一些将以某种方式绘制的内部数据(不重要 如何 这是完成的;只是它完成了) .因此,您现在可以维护一个内部列表,而不是扩展 List<T>
,然后您可以按照您选择的方式 "draw"。这正是具体实现中发生的事情 Drawables<T extends Drawable>
.
您的下一个问题是关于无法在 Text
个实例上调用 t.setText()
。唯一可以做到这一点的方法是,如果你有一个具体实例,专门用于 Text
个实例,或者如果你在 draw
中执行 instanceof
检查。这是因为你只有T extends Drawable
; class 知道它是某种 Drawable
,并且它能够 draw
。除此之外,它不知道具体的实现。
class数组实现了Iterable。所以你可以这样写。
@Override
public void draw (final DrawConfig drawConfig) {
for (final T item : this) // Line 17
item.draw(drawConfig);
}