【问题标题】:How to create an interface with abstract methods that reference self-type in java如何在java中使用引用self类型的抽象方法创建接口
【发布时间】:2016-01-31 18:43:56
【问题描述】:

我确定这已被回答 100 次,但我不确定要搜索什么。 我想创建一个带有抽象方法的接口,该方法强制实现它的类的自类型参数。

嗬嗬嗬嗬。

例如,我有一个名为 Collider 的接口,带有一个名为 isColliding 的方法。我有一个名为 Player 的类,它实现了 Collider。在我的用例中,只有 Collider 相同子类型的对象需要检查它们是否相互碰撞。我希望 Player 实现方法 isColliding,但我希望函数原型被强制执行为 isColliding(Player p),而不是 isColliding(Collider c)

通过将 Collider 声明为

,我设法实现了我认为的解决方法
public interface Collider<T extends Collider<T>> {
    public abstract boolean isColliding(T other);
}

但是当我声明实现碰撞器的类时,类原型看起来像这样

public class Player implements Collider<Player>

这看起来很难看,而且不一定像我想要的那样类型安全。似乎应该有一种方法可以使 隐含。我的目标是让子类的 Overridden 函数原型看起来像这样

public boolean isColliding(Player other)

提前致谢!

编辑:

提供更多背景知识:

我有一个名为 Collision 的单例类,它注册我的可能相互碰撞的对象。在 Collision 内部,我有一个声明为

的 Hashmap
HashMap<Class, ArrayList<Collider>> colliders

这是我用于存储可能相互碰撞的对象的数据结构。它们是按类映射的,因为我的设计只要求每个类检查它是否与自身发生碰撞。 Collision 有自己的 isColliding 函数,可以从实现 Collider 的对象中调用。看起来是这样的

public <T extends Collider> boolean isColliding(T c) throws ColliderNotPopulatedException {
    ArrayList<T> cList = (ArrayList<T>)colliders.get(c.getClass());

    if (cList == null) {
        throw new ColliderNotPopulatedException();
    }

    for (T otherC : cList) {
        if (c != otherC && c.isColliding(otherC)) {
            return true;
        }
    }
    return false;
}

当我尝试在此方法中调用 isColliding 时遇到 NoSuchMethod 错误,我怀疑这是因为未实现自类型。我应该重新考虑我的设计吗?有没有模式可以让这个更干净?

编辑 2:

我设法克服了在调用 isColliding 时将“otherC”转换为类型 (T) 的错误。看来这个实现对我有用。感谢大家的帮助!

【问题讨论】:

  • 这样的经典例子是 java.lang.Comparable。你考虑过复制它的结构吗?
  • 我认为这就是我已经拥有的。我希望我忽略了 Java 的另一个特性来为我找出自我类型,而不是我自己在每个继承的类中明确提供它
  • 这是不精确和多余的。这就是为什么我认为这可能不是最好的解决方案。这是我在 isColliding 方法中强制执行 self 类型的解决方法。这不是我想要的,但它比允许任何泛型类型更具体。
  • 泛型类型中的自键入可能会成为困难的来源。如果它是您真正需要的,那么就是这样,但有时有一些优雅的解决方案可以避免自键入。
  • 这意味着什么?它的难度细节是什么?

标签: java generics methods abstract self-reference


【解决方案1】:

您似乎正在尝试创建某种游戏或游戏引擎,对吧?在这种情况下,我还猜想在某些时候,您不仅想检测人之间的碰撞,还想检测其他事物(例如人、怪物、车辆等)之间的碰撞。

您可能需要重新考虑一下您的设计,而不是进入自我类型。

首先,我建议您制作所有可能与常见类型的子类发生碰撞的对象,该类型公开可用于检测碰撞的属性(可能是位置、多边形等)。

 public interface GameObejct {

        int getProperty1();
        int getProperty2();
    }

 public class Person implements GameObejct {
    @Override
    public int getProperty1() {
        return 0;
    }

    @Override
    public int getProperty2() {
        return 0;
    }
}

public class Monster implements GameObejct{
    @Override
    public int getProperty1() {
        return 0;
    }

    @Override
    public int getProperty2() {
        return 0;
    }
}

碰撞检测逻辑不属于对象(人、怪物等),而是在程序的另一部分。对象本身不需要知道它可以与某物发生碰撞。

由于您表达了使用不同碰撞检测方法的愿望,您可以使用碰撞检测策略并创建可以使用的各种实例。

public interface CollidingDetectionPolicy <T extends GameObejct> {

    boolean objectsColliding(T object1,T object2);

}

public class SimpleCollisionDetector implements CollidingDetectionPolicy<GameObejct> {
    @Override
    public boolean objectsColliding(GameObejct object1, GameObejct object2) {
       // detect collision using a simple method...
        return false;
    }
}

public class ComplexCollisionDetector implements  CollidingDetectionPolicy<GameObejct> {
    @Override
    public boolean objectsColliding(GameObejct object1, GameObejct object2) {
        // detect collision using some other more complex method
        return false;
    }
}

游戏的主引擎应该在适当的时候检查碰撞。

public class GameEngine {

    public void detectCollisions() {

        /// ...
        /// ...
        // need to know if there is some collision

        // get all the objects on screen and the current collision detection policy (see note)  
        List<GameObejct> currentlyVisibleObjects = getObjectsOnScreen();
        CollidingDetectionPolicy collisionDetector = getCollisionDetectionLogic();

        // naive implementation . don't traverse your list this way!Think about complexity!
        for (int i = 0; i < currentlyVisibleObjects.size() - 1; i++) {
            GameObejct object1 = currentlyVisibleObjects.get(i);

            for (int j = i + 1; j < currentlyVisibleObjects.size(); j++) {
                GameObejct object2 = currentlyVisibleObjects.get(j);
                if (collisionDetector.objectsColliding(object1, object2)) {
                    // object colliding ...do something
                }
            }
        }

    }

    public List<GameObejct> getObjectsOnScreen () {
        ... // return the list of game objects
    }

    public CollidingDetectionPolicy getCollisionDetectionLogic() {
        ... /// return the detection implementation 
    }
}

注意:我认为对象本身提供检测策略并不明智,因为在 2 个对象提供不同策略的情况下,比较语义会有点奇怪,如下例所示:

让我们假设对象本身可以使用方法为我们提供其首选的检测策略

public CollidingDetectionPolicy getDetectionPolicy();

我们必须检查这两个对象

Person p = ...;
Monster m = ..;

然后

p.getDetectionPolicy().objectsColliding(p, m)

可能有不同的结果

m.getDetectionPolicy().objectsColliding(p, m)

这至少可以说很奇怪。

【讨论】:

  • 感谢您对编写碰撞检测引擎的全面演示。我实际上是在创建一个模拟,我的用例比典型的视频游戏要简单得多。这里有很多很棒的信息,如果我确实需要更强大的碰撞引擎,我肯定会参考你提到的模式。
【解决方案2】:

这个设计看起来很奇怪:用一个 T 类型声明 Collider 就足够了:

interface Collider<T> {
    boolean isColliding(T other);
}

在这种情况下,实现类检查与相同类型对象的冲突:

class Person implements Collider<Person> {
    @Override
    public boolean isColliding(Person otherPerson) {
        ...
    }
}

例如,这与Comparable 接口的工作方式非常相似。

声明&lt;T extends Collider&lt;T&gt;&gt;更强大,并确保实现类只能将 Collider 子类型指定为泛型类型:

class Person implements Collider<Person> - this will work
class Person implements Collider<String> - this will not work

它仍然不要求使用相同的类型 - class Person1 implements Collider&lt;Person2&gt; - 合法

【讨论】:

    【解决方案3】:

    我认为最好的方法是你展示的那个

    但是另一种方法可能是在 Collider 接口中定义它

    public boolean isColliding(Object object);
    

    然后这样实现方法

    class Player implements Collider{
    
        public boolean isColliding(Object object){
            if( object instanceof Player ){
                // some stuff
                return true;
            }
            return false;
        }
    
    }
    

    我更喜欢使用你所做的模板,因此该方法具有固定类型。

    【讨论】:

    • 这也许是一个解决方案,但不是一个好的解决方案。它缺乏泛型类型可以帮助程序员实现的任何类型安全保证。一般来说,在您自己创作(或可以修改)的类型中使用instanceof 表示可能存在问题。
    • 正如我所写的,我不喜欢这个解决方案,因为我喜欢使用模板...我提供了一个可能的解决方案.. 使用泛型类型不是问题,而且由于这个原因,使用 Java 中实现的关键字“instanceof”没有下划线......对不起,我不同意你的看法
    • 您可以不同意,但最佳实践是有原因的。您建议的代码不是类型安全的。如果采用最佳实践,它也不会像它那样可维护或健壮。例如,您已将错误发现从编译时(就像使用泛型类型一样)移到运行时,这会使错误更难识别。对于 Java 学习者,我只想说,上面可能是一个完全合理的内部实现,不是一种可以效仿的风格。
    • 实际上,这里描述的“其他方式”提供了运行时类型安全性,这是纯泛型方法无法实现的。并且泛型在编译时没有帮助在这种特殊情况下,因为Collision 在编译时不知道它在调用谁。
    【解决方案4】:

    也许我误解了,但我假设每种类型的对象的比较方法都是相同的,因为您不想覆盖每个类的方法,而真正重要的是只有相同类型的对象是比较的。然后以 Andrea 的回答为基础,您或许可以使用:

    class Collider{
    
        public boolean isColliding(Object o){
            if (this.getClass() == o.getClass()){
                //Do some stuff
                return true;
            }
            return false;
        }
    
    }
    

    但是我认为您的通用答案是可以的,毕竟它是 java 用于 Comparable 的模式。

    【讨论】:

    • 我确实想覆盖每个类的方法。我打算让我的每个子类实现自己的碰撞检测算法。有些是球形的,有些是多边形的。许多人都有有趣的形状,并且必须有自己的解决方案。
    • 好的,那我肯定认为你的原创设计是最好的!
    【解决方案5】:

    接口Comparable可以作为向导。正如 AdamSkywalker 在他的回答中提到的,

    public interface Collider<T>
    

    足够好。绑定在这里没有任何好处。

    在任何你概括碰撞器类型的方法或类中,你都可以使用绑定

    <T extends Collider<? super T>>
    

    确保该类型可以检查与自身的冲突。

    对于您的 Map,Java 中通常无法声明 Map 变量,该变量以类型检查的方式将 Class 映射到该类的元素(无论您是否对class 是否允许),因为Map 的方法的参数类型只与整个Map 的类型参数相关,所以它们对于Map 中的所有元素必须相同。

    但是,您可以编写一个包装类,其 API 会强制执行这种关系,例如,

    class ClassToColliderList {
        public <T extends Collider<? extends T>> void put(Class<T> clazz, List<T> list);
        public <T extends Collider<? extends T>> List<T> get(Class<T> clazz);
    }
    

    这样的类仍然必须在内部存储某种类型的变量,例如Map&lt;Class&lt;?&gt;, List&lt;?&gt;&gt;,并在内部进行未经检查的强制转换,但这都包含在该类的私有实现细节中,可以对其进行检查以确保其安全。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-09-14
      • 1970-01-01
      • 2016-07-22
      • 1970-01-01
      • 2017-07-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多