Java泛型类型

12

当我有一个接口

public interface Foo<T> {
    T someMethod();
}

有没有办法确保当某个类实现了该接口时,泛型类型与实现类相同。

例如:

public class Bar implements Foo<Bar>  {
    Bar someMethod() {
        return new Bar();
    }
}

据我理解,有没有办法确保当某个类实现这个接口时,泛型类型与实现类相同 的意思是:我们是否可以拥有一个不与类型参数关联的类名,例如 FancyStringList 而非 ArrayList<FancyString>Color 而非 Enum<Color> - Ray Jasson
3个回答

27
可以做到(在某种程度上;见下文)。在C++世界中,这被称为“奇异递归模板模式”,但它也适用于Java:
public interface Recur<T extends Recur<T>> {
    // ...
}

注意第二次提到的T。这是CRTP的一个重要部分。

此外,这就是java.util.Enum的定义方式,因此名为Foo的枚举类型必须派生自Enum<Foo>,因此在Java中也不是不常见的模式。


我希望说以上就是故事的结尾,但 tangens 在他们的修订中指出它并不是完全健壮的,而且在性质上与他们的答案并没有太大的区别。
有一点区别(我能想到的)是我提出的 Recur<T> 解决方案和 tangens 提出的 Recur<?> 解决方案之间的区别。
public interface Recur<T extends Recur<T>> {
    T foo();
}

class A implements Recur<B> {
    @Override
    public B foo() {
        return new B();
    }
}

class B implements Recur<A> {
    @Override
    public A foo() {
        return new A();
    }
}

使用<T>,上述代码将无法编译;使用<?>,则可以。但这只是纠缠于细节,它并不改变 tangens 的中心观点,即在已有有效的Recur实现的情况下,您可以使后续的实现使用已经有效的类型,而不是它本身。我仍然认为这值得一些价值,但这并不比 tangens 的答案更有价值。
最后,如果可以,请支持 tangens 的回答。 (tangens,您应该更新您的帖子,以便我也可以给您投票。)他们有一个非常好的观点,很抱歉我第一次错过了这一点。谢谢,tangens!

对于答案的加分:+1;对其他答案的负分:-1;承认错误:+1 = +1 - DaveFar

16
不可以引用实现类。最好的方法是:
public interface Foo<T extends Foo<?> > {
    T someMethod();
}

通过这样做,你可以确保someMethod()返回一个实现了Foo接口的对象。


编辑:

即使有这样一个接口定义:

public interface Foo<T extends Foo<T > > {
    T someMethod();
}

您可以定义一个类Bar2
public class Bar2 implements Foo<Bar2 > {
    @Override
    public Bar2 someMethod() {
        return new Bar2();
    }
}

然后您可以定义一个类Bar来实现Foo,但不返回Bar而是返回Bar2

public class Bar implements Foo<Bar2 > {
    @Override
    public Bar2 someMethod() {
        return new Bar2();
    }
}

原问题是是否可以像Bar一样准备对抗接口的使用。
答案是:不行。
编辑:
Angelika Langer很好地解释了如何解密"Enum<E extends Enum<E>>"的问题:How do I decrypt "Enum<E extends Enum<E>>"?

因为你没有回答原帖的问题。原帖的问题是:如何确保实现了 Foo 接口的 Bar 类型只能实现 Foo<Bar>,而不能实现 Foo<Object>(例如)。这是可能的,并且可以使用 java.util.Enum 来实现。 - C. K. Young
因为你错了吗?下面的答案显示了一个模式链接和工作重复模板的示例,这些模板确保实现类使用基类,而不仅仅是任何旧类。 - gnarf
1
为什么这个被接受了,我真的不知道。楼主应该更明确他的实际问题。 - BalusC
2
我添加了一个例子,说明为什么@chris-jester-young的答案不比我的好。 - tangens
你说得对,感谢提供反例。我会更新我的答案。在使用 <?><T> 之间仍然存在差异,但这只是纠结细节,并没有实质性地改变答案的性质。 - C. K. Young
这个答案是正确的,而被接受的答案是错误的。 - John Glassmyer

0

这是不能完成的,因为涉及到类型擦除

编辑:

我被踩了,这里再做一些解释:

public interface Foo<T>;

T是在运行时可用的,因此它无法被检查。但是,可以自定义注释来强制执行此操作。


2
是的,我给你点了踩。我不是在谈论运行时类型检查——当然这在Java中是不可能的——但OP的问题并不是在问那个。 - C. K. Young

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