我决定在我的设计中使用泛型,这很好用。只有当我进行抽象时,我才会陷入困境。我正在收集使用不同泛型实现相同界面的不同实例。为了将它们保存在列表中,据我所知,我需要使用通配符。所以我目前的代码是这样的:
import java.util.List;
import java.util.ArrayList;
public class Main {
private List<IView<?>> views = new ArrayList<IView<?>>();
public void addView(IView<?> view)
{
this.views.add(view);
}
public void handle()
{
for (IView<?> view : this.views)
{
// ## ERROR ##
view.render(
view.cast(new Object())
);
}
}
}
public interface IView<T> {
public T cast(Object entity);
public void render(T entity);
}
public class FirstView implements IView<One> {
// .. Added to Main-collection views
}
public class SecondView implements IView<Two> {
// .. Added to Main-collection views
}
我还尝试了一种替代方法,从IView返回一个Class,然后我用它来调用class.cast。同样的问题。
编译器不接受此操作 - 类型IView(Capture#2-of?)中的Render(Capture#2-of?)方法不适用于参数(Capture#3-of?)。
我部分地想要理解这个问题,但我认为无法解决问题。如果有人可以帮助我重新开始,那将会非常受欢迎。
答案 0 :(得分:2)
我不相信你可以为?
“类型”变量赋值。一个无界限的“?”表示“变量的原始类型未知”,这意味着没有已知的类型可以安全地分配给它。这并不意味着在运行时检索type参数的实际值。
使用泛型方法进行强制转换的整个想法在Java泛型中没有意义。以类型安全的方式对未知类型对象进行强制转换的唯一方法是使用实际强制转换。
唯一的解决方案是摆脱cast()方法,并使用原始类型:
public void handle()
{
for (IView view : this.views)
{
view.render(new Object());
}
}
假设cast()方法只进行转换而不进行类型转换。如果最终被调用的实现方法是FirstView.render(一个对象),我相信Java实际上会将参数转换为One。 (这也可能导致有趣的错误,在没有强制转换的代码行上抛出ClassCastException。)
如果cast()
可以进行类型转换(在这种情况下,“cast”是操作的错误名称),这意味着每个IView 必须能够接受Object,在这种情况下,如果render()
的参数是开头的通用类型,则没有多大意义。我将使用的设计类似于以下内容:
interface IView<T> {
void renderObject(Object o);
void render(T);
T coerce(Object o);
}
abstract class ViewBase<T> implements IView<T> {
void renderObject(Object o) {
render(coerce(o));
}
}
class FirstView extends ViewBase<One> {
// …
}
封装“强制转换”操作。
没有静态类型安全的方法来处理不同类型的对象集合 - 在Java中,IView<One>
和IView<Two>
的类型与String
和Integer
不同并且两者之间没有安全的转换。
答案 1 :(得分:1)
因为我想知道代码view.render(view.cast(new Object());
中的两个捕获,
我在Angelika Langer's Generics FAQ中阅读了相应的部分。它声明:
我们不能通过参数化的无界通配符来调用方法 采用“未知”类型参数的类型。
PERIOD。
因此,使用通配符声明可以防止使用参数化参数,即使类型推断很简单,例如:(因此允许view.cast(new Object();
,但view.render(view.cast(new Object());
不允许。
只是为了澄清一个例子,Angelika给出了以下内容:
Box<?> box = new Box<String>("abc");
box.contains("abc"); // error
其中contains
将参数化类型Box作为参数。她说:
如果通过引用变量执行,则调用是非法的 输入
Box<?>
。 [将contains方法定义为参数 类型对象,而不是T,将避免这种影响。在这种情况下 contains方法不会采用“未知”的对象,而是采用 “any”类型的对象,允许通过a调用它Box<?>
类型的引用变量。]
所以她建议回到Object
(类似于我的其他答案中的建议)。顺便说一句,她不鼓励使用原始类型,Josh Bloch在Effective Java's第23项“不要在新代码中使用原始类型”中也是如此。
答案 2 :(得分:0)
我不理解编译器错误,即为什么view.render(view.cast(new Object());
产生两次捕获。 (我已尝试使用view
为最终的此代码,并显示相同的错误消息)。
但由于IView<One>
和IView<Two>
只有Object
作为常见类型,因此解决它的方法是使用List<IView<Object>>
。
答案 3 :(得分:0)
我通过返回一个类型化的对象来替换了转换(我稍微通过用IEntity替换新的Object来改变了示例);
public class Main {
public static List<IView<? extends IEntity<?>>> views = new ArrayList<IView<? extends IEntity<?>>>();
public static <T extends IEntity<T>> void register(IView<T> view)
{
Main.views.add(view);
}
public static void handle()
{
for (IView<? extends IEntity<?>> view : Main.views)
{
// ## Error ##
/* Bound mismatch: The generic method view(IView<T>) of the type Main
* is not applicable for the arguments (IView<capture#1-of ? extends IEntity<?>>).
* The inferred type capture#1-of ? extends IEntity<?> is not a valid
* substitute for the bounded parameter <T extends IEntity<T>>
*/
Main.view(view);
}
}
public static <T extends IEntity<T>> void view(IView<T> view)
{
IKey<T> key = view.getKey(/* Some arg */);
T entity = key.getEntity();
view.render(entity);
}
}
public interface IView<T> {
public IKey<T> getKey(/* Some args */);
public void render(T entity);
}
public interface IKey<T> {
public T getEntity();
}
通配符有点受限,但是它会产生另一个问题(实际上可能是相同的):有没有办法检查两个通配符是否匹配,即:
for (IView<? extends IEntity<?>> view : Main.views)
在哪里?只能是一个(=相同)(通用)类型?