Java泛型,为什么在泛型中使用继承是不合法的?

4

我有这个结构:

///Creep.java///
public interface Creep extends Movable<Position2D> {
  ...
}


///Movable.java///
public interface Movable<T extends Position2D> {
        ...
    void setMovementStrategy(MovementStrategy<Movable<T>> movementStrategy);
    MovementStrategy<Movable<T>> getMovementStrategy();
}


///MovementStrategy.java///
public interface MovementStrategy<T extends Movable<? extends Position2D>> {
  void executeMovement(T movable);
}


///CreepImpl.java///
public class CreepImpl implements Creep {
...

 @Override
 public void setMovementStrategy(MovementStrategy<Creep> movementStrategy) {
    // TODO Auto-generated method stub

 }

 @Override
 public MovementStrategy<Creep> getMovementStrategy() {
    return null;
 }

}

我的问题是泛型不支持MovementStrategy<Creep>,但它却接受MovementStrategy<Movable<Position2D>>。我认为这很奇怪,因为Creep扩展了Movable<Position2D>。这在方法public MovementStrategy<Creep> getMovementStrategy()public MovementStrategy<Creep> getMovementStrategy()的上下文中。

这可能不可能吗?或者我做错了什么?

任何帮助都将不胜感激!

编辑

忘记包含MovementStrategy源代码.. 唉!


你具体遇到了什么错误,出现在哪一行代码? - Michael Borgwardt
好的,没有错误,只是一些Eclipse警告和红色下划线 :)。我的问题更多地是为什么MovementStrategy<Movable<Position2D>>不等于MovementStrategy<Creep>,当CreepMovable<Position2D>的更具体类型时。 - netbrain
1
顺便提一下,MovementStrategy 不应该是通用的。它应该始终与可移动对象一起使用。 - Bozho
你是如何声明MovementStrategy类/接口的?我猜你在那里使用了错误的声明。 - Dorus
红色下划线表示编译器错误(可在“问题”视图中看到,或者如果将鼠标悬停在下划线代码上)。答案取决于您尝试使用类型的上下文。请展示实际有问题的代码和实际错误! - Michael Borgwardt
你使用这些泛型创建了很多复杂性。 - Bozho
4个回答

10
也许你甚至不需要使用泛型来定义MovementStrategy。尽量避免创建过多泛型,可以尝试直接使用MovementStrategy作为类型。
原始回答:你可以使用“extends”关键字:MovementStrategy。
这是为了保持编译时的安全性。
想象一下以下情况是可能的:Creep extends Movable,Wind extends Movable。
MovementStrategy<Movable> strategy = new MovementStrategy<Wind>();
strategy.setTargetObject(new Creep()); //fails

第二个 like 在运行时失败,因为它期望的是 Wind,但你给了 Creep。


但为什么是这样的?编译器为什么需要"? extends Creep"而不是简单的"Creep"? - Heisenbug
1
为了保证编译时的安全性。我会添加一个解释。 - Bozho
MovementStrategy 是一个接口,定义如下:public interface MovementStrategy<T extends Movable<? extends Position2D>> 根据你的建议将 T 改为 ? 会导致 IDE 报警告或错误。 - netbrain
正如你所建议的那样,解决方案是在MovementStrategy接口上删除泛型,因为我不需要它。但还是谢谢你为我澄清了这个问题! - netbrain
实际上,如果这是可能的,我不认为 strategy.setTargetObject(new Creep()) 会在运行时失败... 它会在后面失败。 - Sebastien Lorber
这取决于对“自动转换”依赖的位置。但是,当您移动对象时,它可以稍后进行。 - Bozho

2

请看下面的示例代码:

Movable<Position2D> moveable = new CreepImpl();
MovementStrategy<Movable<Position2D>> strategy=/*some strategy here */;
moveable.setMovementStrategy(strategy);

由于CreepImpl只接受MovementStrategy<Creep>,因此最后一行应该失败,尽管它是完全合法的,这意味着CreepImpl不是Moveable<Position2D>的有效替代品。

在Java中,当B扩展或实现A时,B类型的每个对象也可以分配给A类型的变量,并且B可能不会引入任何限制来限制A。


谢谢,你给了我一个“啊哈”时刻 ;) - netbrain

1

多态对于泛型并不像对于数组那样有效。

JVM在运行时不知道对象的类型,这被称为类型擦除,并且是为了向后兼容而完成的。只有编译器知道实例的类型。

泛型主要设计用于集合...问题在于你必须知道,如果你将一个整数的ArrayList传递给一个接受List的方法,那么插入一个字符串是可能的。

尝试一下以下代码,它会让你惊讶:

public static void main(String[] args) {
    Set<Integer> set = new HashSet<Integer>();
    for ( int i=0 ; i<10 ; i++ ) {
        set.add(i);
    }
    methode(set);
    for ( Integer i : set ) {
        System.out.println(i);
    }
}
public static void methode(Set set) {
    set.add("test");
}

它可以编译、运行,甚至在引发异常之前给出一些数字!

现在让我们来审查以下代码:

public static void main(String[] args) {
    Set<Integer> set = new HashSet<Integer>();
    for ( int i=0 ; i<10 ; i++ ) {
        set.add(i);
    }
    methode(set); // NOK
    for ( Integer i : set ) {
        System.out.println(i);
    }
}
public static void methode(Set<Number> set) {
    set.add(3f);
}

这里几乎是一样的,如果多态性与泛型兼容,你可以将一个整数集合传递给一个接受数字集合的方法,然后向整数集合中添加一个浮点数!

Sun设计了泛型,以便如果您不使用旧的非类型代码,则您的集合仍然保持类型安全...

就像Bozho所说,在我的例子中,您可以将其用作参数。

Set<? extends Number>

因此,将一组整数传递给该方法是可能的。我不记得确切的情况,但在这种情况下,Java会警告您向列表中插入新项的危险...


请注意,对于数组,数组类型在运行时已知,因此如果您尝试向数组添加错误的值,则会收到ArrayStoreException异常。而对于集合,您可以添加Float类型,但在遍历列表时可能会出现ClassCastException异常。 - Sebastien Lorber

1
你的问题在于Creep实现了接口Movable<Position2D>,而Movable<Position2D>期望有方法setMovementStrategy(MovementStrategy<Movable<Position2D>> movementStrategy)MovementStrategy<Movable<T>> getMovementStrategy()
看这个简单的非泛型例子:
public interface iTest() {
  doSomething(Object o);
}

public class Test implements iTest {
  @Overide
  doSomething(Test t) {
    //error
  }
}

正如您所看到的,doSomething(Test t) 只实现了 iTest 接口的一小部分。如果有人编写以下代码,则在 Test 中没有方法可以执行它:

public class TestImpl {
  public static void main(String[] args) {
    iTest t = new Test();
    t.doSomething("Hi"); // valid, because iTest.doSomething() expects a Object, and String is a Object.
    Test t2 = new Test();
    t2.doSomething("Hi"); // invalid, Test does not have a doSomething(String).
  }
}

是的,我通过去除不必要的泛型来解决了它。但还是谢谢! - netbrain

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接