Java泛型 - 不允许子类

5
我有一个关于泛型的问题。假设我有3个类“Foo”、“Bar”(它是“Foo”的子类)、一个类“Factory”和一个接口“TestInterface”。我想编写一个泛型方法,只允许“直接”实现该接口的对象,而不是其子类。这对我来说相当困难,因为Java8似乎比Java7“更聪明”。以下是示例:
public interface TestInterface<T extends TestInterface<T>> {

}

public class Foo implements TestInterface<Foo>{

}

public class Bar extends Foo {

}

public class Factory {

   public static <T extends TestInterface<T>> T doSth(T arg) {
       return arg;
   }
}

好的,以下是我的课程。现在在doSth(T arg)方法中,我希望允许Foo对象,在这种情况下,因为它是唯一“直接”实现该接口的类。
显然,现在这样做是不会编译的:
Bar b = Factory.doSth(new Bar());

因为 Bar 没有实现 TestInterface<Bar> 接口。

我的问题是,这行代码编译时没有任何问题:

Foo f = Factory.doSth(new Bar());

尽管通用参数为T且T应实现具有自己类的接口(抱歉,不知道该如何表达,希望您能理解),编译器仍然接受"Bar"作为参数。因此,编译器必须表现得像“好的,Bar不适合,但是也许一个超类会适合”。然后它“看到”它可以与超类“Foo”一起使用。然后Bar将被视为Foo,因为每个Bar都是某种Foo或类似于这样的东西??至少我是这么想的。
嗯,如果我尝试使用Java7编译该行,它会失败。因此,似乎Java8对类型擦除更加智能?!
在Java8中是否有任何方法不允许子类,以便不会编译此内容?
Foo f = Factory.doSth(new Bar());

希望你能理解我的问题,我很少用英语写作。
问候。

1
如果你想让 Foo f = Factory.doSth(new Bar()); 无法编译,那么 Bar 就不能实现 TestInterface<Foo>,也就是说它不能扩展 Foo。 - user253751
3
如果BarFoo的子类,那么Bar的实例也是Foo的实例,并且在需要接受Foo的时候也必须接受Bar。为什么您不希望在此处接受Bar - rgettman
1
@FelRPI 两个版本的不同之处在于类型推断的工作方式,这在Java 8中已经发生了变化。事实上,Java 8中的类型推断已经有所改进。 - Rohit Jain
2
在我的电脑上,使用Java 1.8.0_40 版本,你的代码块都无法编译通过。你确定你的例子没问题吗?请问你使用的是哪个版本的Java? - Martin Andersson
2
@FelRPI 在Java 7中,使用显式指定类型参数的方式Foo f = Factory.<Foo>doSth(new Bar());应该可以正常工作。 - user253751
显示剩余5条评论
2个回答

3
Foo f = Factory.doSth(new Bar());在Java 8中能够工作而在Java 7中不能工作的原因是因为Java 8具有更好的类型推断能力。在Java 7中,类型被推断为:
Foo f = Factory.<Bar>doSth(new Bar());

Bar没有实现TestInterface<Bar>,但是通过继承它实现了TestInterface<Foo>。Java 8的类型推断得到了提升,类型将被推断为:

Foo f = Factory.<Foo>doSth(new Bar());

请注意,Factory.<Foo>doSth() 期望一个 Foo 作为参数,因此 new Bar() 会被隐式转换为 Foo,这是可以正常工作的。
很遗憾,无论如何都无法避免这种情况。您始终可以将对象向上转换为其超类。您可以将 Foo 声明为 final,但这完全禁止了对 Foo 的子类化。

1
我认为这种情况确实存在;Bar在概念上是Foo的子类,但我们不希望它们共享同一个doSth()方法。虽然不完全相同,但同样地,在许多情况下,应该避免在超类和子类之间共享equals()方法,以保持等价关系的对称性和传递性(详见J.B.的《Effective Java 2nd ed.》第8条)。
到目前为止,我没有找到一个带有泛型参数描述的好解决方案(Java的类型系统,像其他典型的面向对象语言一样,似乎基本上默认每对超类和子类都应该共享超类的方法)。相反,我发现在这种情况下放弃继承将是一种可能的解决方案,如下所示,
public class Bar  { // using composition, instead of inheritance
  Foo Bar(Foo f) {
     this.f = f;
  }
  // here, definitions of delegated methods for each of f's methods
  // see details in Item 16 of Effective Java 2nd ed. by J.B.

  Foo asFoo(){ 
     return f;
  }
}

通过修改 Bar,我们可以在这里获得编译时错误,

Foo b = Factory.doSth(new Bar(new Foo())); // a compile time error occurs

即使在使用JDK7中,我们仍然使用<Foo>
Foo b = Factory.<Foo>doSth(new Bar(new Foo())); // a compile time error occurs

另一方面,必要时我们可以使用asFoo()来避免这个错误。
Foo b = Factory.<Foo>doSth(new Bar(new Foo()).asFoo()); // no compile time error

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