在Java中,我该如何引用一个接口实现的类类型?

20

我在制作一个程序时遇到了与接口相关的问题。我想创建一个接口,其中一个方法接收/返回对该对象本身类型的引用。大致如下:

public interface I {
    ? getSelf();
}

public class A implements I {
    A getSelf() {
        return this;
    }
}

public class B implements I {
    B getSelf() {
        return this;
    }
}

我不能在需要一个类时使用 "I",因为我不想返回该接口的引用,而是返回该类。我搜索了一下,发现在Java中无法进行“自我引用”,因此我不能只是用一个“self”关键字或类似的内容替换示例中的“?”。 实际上,我想到了一个解决方案,大概是这样的

public interface I<SELF> {
    SELF getSelf();
}

public class A implements I<A> {
    A getSelf() {
        return this;
    }
}

public class B implements I<B> {
    B getSelf() {
        return this;
    }
}

但这似乎只是一个变通方法,还有其他的方法可以实现吗?


我不觉得这是一个变通方法 - 你的解决方案似乎正是我认为你想要的,而且比其他提供的答案更好。你能解释一下它为什么感觉“不对”吗? - Ed Staub
1
是的,我提出的解决方案确实解决了问题,但前提是每个实现它的人都要使用自己的类作为I的泛型类型。我觉得这不太合适,因为如果有人不遵循这个规则,代码就会出错。例如,“class C implements I<B>”将无法工作;想要创建I的实现的每个人都应该意识到这一点,我认为这不是正确的做法。这是我的思路。 - Leonardo Raele
1
这对我来说没有意义。接口的整个目的是为一组实现共同方法的类分配一个类型,以便它们可以互换使用。我看到你提出了两个问题。首先,如果每个实现该方法的类返回不同的类型,则这些方法实际上没有共同的方法。如果您能够摆脱它,那么您将无法互换使用这些类:I x; A = x.getSelf(); // 仅适用于A对象 B = x.getSelf(); // 仅适用于B对象 - Paul Jackson
1
@PaulJackson,如果你有一个向量接口,可能有两个实现,一个是颜色,一个是(3D)点,那么一个通用方法可以使用它们的add和multiply方法,分别返回颜色和点,而不必被加法的细节所困扰,这将非常有用。 - jgon
4个回答

13

有一种方法可以强制使用自己的类作为参数来扩展接口:

interface I<SELF extends I<SELF>> {
    SELF getSelf();
}

class A implements I<A> {
    A getSelf() {
        return this;
    }
}

class B implements I<A> { // illegal: Bound mismatch
    A getSelf() {
        return this;
    }
}

即使编写通用类也可以使用此方法。唯一的缺点是必须将this转换为SELF

正如安德烈·马卡罗夫(Andrey Makarov)在下面的评论中所指出的,当编写通用类时,这种方法并可靠。

class A<SELF extends A<SELF>> {
    SELF getSelf() {
        return (SELF)this;
    }
}
class C extends A<B> {} // Does not fail.

// C myC = new C();
// B myB = myC.getSelf(); // <-- ClassCastException

3
SELF转换为B是不安全的。请考虑以下情况:class B extends A<B>{},然后class C extends A<B> {}。所有类型限制都被满足,但在C中,getSelf()方法将具有B作为返回类型,而C并没有扩展B,因此进行转换将产生ClassCastException异常。 - Andrey Makarov

7

Java支持协变返回类型,因此这是一种选择。利用AB都派生自Object的事实:

public interface I {
    Object getSelf();  // or I, see below
}
public class A implements I {
    A getSelf() { return this; }
}
public class B implements I {
    B getSelf() { return this; }
}

重点在于,A.getSelf()B.getSelf()都是I.getSelf()的合法覆盖,尽管它们的返回类型不同。这是因为每个都可以像Object一样处理,因此返回类型与基函数的类型兼容。(这称为“协变性”)。
事实上,由于
也知道源自,所以您可以出于同样的原因将替换为
协变性通常是件好事:拥有类型 的接口对象的人可以调用getSelf()并获得另一个接口,这就是她需要了解的全部。另一方面,已经知道他有一个
对象的人可以调用getSelf()并实际上会得到另一个对象。其他信息可用于获取更具体的派生类型,但缺乏该信息的人仍将获得接口基类所规定的所有内容:
I x = new A();
A y = new A();

I a = x.foo();    // generic
A b = y.foo();    // we have more information, but b also "is-an" I
A c = (A)x.foo(); // "cheating" (we know the actual type)

我认为这就是我一直在寻找的解决方案。虽然这对我来说并不比使用模板更好或更清晰,但这是一个替代方案。谢谢! - Leonardo Raele
2
@LeonardoRaele - 这正是你在说“我不能用'I'”时所放弃的解决方案 :-) - Stephen C
@StephenC:确实 - 这不是一个“解决方案”,而是对你最初想法正确性的解释:这种行为是在设计语言时考虑到的! - Kerrek SB
@StephenC 当然,这是可以的;但对于我所寻找的最佳方式。我的问题是是否有更好的方法来做到这一点。我得到的答案是“没有”。 - Leonardo Raele

3
我在想是否有其他方法可以这样做? 你可以按照以下方式编写:
public interface I {
   I getSelf();
}

然后将结果转换为您想要的类型。您现有的 AB 类将按原样工作。
(这是返回类型协变的示例。在 Java 5 中添加了对返回类型协变的支持。在旧版 JDK 中,此方法会引起编译错误。)
使用泛型的替代版本允许您避免显式类型转换。但是,在生成的代码中存在隐式类型转换,而且在运行时...除非 JIT 编译器可以将其优化掉。
据我所知,没有更好的替代方案。

1

正如其他人所说,您可以在实现类中覆盖返回类型:

public interface I {
    public I getSelf();
}

public class A implements I {
    @Override
    public A getSelf() {
        return this;
    }
}

然而,我有两个“为什么”的问题要问你:

1:为什么你想要一个接口返回实现对象?这似乎与接口和继承的一般思想相悖。你能展示一下这可能如何使用的例子吗?

2:无论如何,你为什么要这个函数?如果a.getSelf() == a,为什么不直接使用a呢?


  1. 我正在为一个学术项目开发一款游戏;我在Board类中,该类应该管理在棋盘上的对象。由于我希望Board(像我的老师一样)尽可能通用(例如允许将Board用于其他游戏),因此我正在使用自定义接口Piece进行工作。我想创建一个方法“Piece onColision(Piece p)”,该方法将自己与另一个比较,并返回剩余的一个(p或this)。
  2. 这个方法纯粹是假设性的,没有真正的实现。
- Leonardo Raele
在这种情况下,我仍然会返回接口类型。当两个碎片碰撞时,你只关心一个碎片被返回。如果必要,您可以通过o.getClass()访问类型。在上面的回答中,您谈到了A a = x.getSelf()不能总是使用的事实。这正是为什么您正在尝试实现的东西并不真正受支持的原因:没有真正的方法可以在没有大量类型转换的情况下使用它,这似乎使您的程序不太通用而不是更通用。 - Trasvi
这适用于返回类型,但我对接收参数很好奇。实现Piece的类应该接收一个对其自身类的对象的引用,这样它们可以使用该类的特定字段进行比较。例如:ChessPiece应该与另一个不同于TableRPGPiece的对象进行比较。 - Leonardo Raele

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