如何使用在 java 中引用 self-type 的抽象方法创建接口

How to create an interface with abstract methods that reference self-type in java

我确定此问题已被回答 100 次,但我不确定要搜索什么。 我想创建一个带有抽象方法的接口,该接口强制执行实现它的 class 的 self-type 参数。

哎呀,真是一口。

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

通过将 Collider 声明为

,我已经设法实现了我认为的 work-around
public interface Collider<T extends Collider<T>> {
    public abstract boolean isColliding(T other);
}

但是当我声明实现对撞机的 class 时,class 原型看起来像这样

public class Player implements Collider<Player>

这看起来很难看,而且不一定像我想要的那样 type-safe。似乎应该有一种方法可以暗示 。我的目标是让 child class 的覆盖函数原型看起来像这样

public boolean isColliding(Player other)

提前致谢!

编辑:

提供更多背景信息:

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

HashMap<Class, ArrayList<Collider>> colliders

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

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 错误,我怀疑这是因为未实现 self-typeness。我应该 re-think 我的设计吗?是否有模式可以使它更清洁?

编辑 2:

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

我认为最好的方法是您展示的方法

然而,另一种方法可能是在 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;
    }

}

我更喜欢像你一样使用模板,所以方法有固定的类型。

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

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

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

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

例如,这与 Comparable 界面非常相似。

声明 <T extends Collider<T>> 更强大并确保实现 class 只能将 Collider 子类型指定为通用类型:

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

还是不要求使用同类型 - class Person1 implements Collider<Person2> - 合法

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

class Collider{

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

}

不过我认为您的通用答案还可以,毕竟这是 java 用于 Comparable 的模式。

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

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

首先,我建议您将所有可能发生碰撞的对象设为一个公共类型的子类,该类型公开可用于检测碰撞的属性(可能是位置、多边形等)。

 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;
    }
}

碰撞检测逻辑不属于物体(人、怪物等),但在 program.The 物体的另一部分,它自己不需要知道它可以与某物发生碰撞。

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

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)

至少可以说这很奇怪。

界面Comparable可以作为指南。正如 AdamSkywalker 在他的回答中提到的那样,

public interface Collider<T>

够用了。绑定在这里对您没有任何好处。

在任何方法或 class 中概括对撞机类型的地方,您可以使用边界

<T extends Collider<? super T>>

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

对于您的 Map,通常无法在 Java 中声明将 Class 映射到 [=37= 的元素的 Map 变量] 以类型检查的方式(不管你是否限制了 classes 允许与否),因为 Map 的方法具有仅与整个类型参数相关的参数类型Map,因此 Map.

中的所有元素必须相同

但是,您可以编写一个包装器 class,其 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);
}

这样的 class 仍然必须在内部存储某种类型的变量,例如 Map<Class<?>, List<?>> 并在内部进行未经检查的转换,但这都包含在 [=37 的私有实现细节中=],可以检查以保证它是安全的。