【问题标题】:Java generics - Don't allow subclassesJava 泛型 - 不允许子类
【发布时间】:2015-03-16 18:45:53
【问题描述】:

我有一个关于泛型的问题。假设我有 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&lt;Bar&gt;

好吧,我现在的问题是,这一行将毫无问题地编译:

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

即使泛型参数是 T 并且 T 应该用它自己的类来实现接口(对不起,真的不知道怎么说,希望你明白我的意思),编译器接受“Bar”作为参数。

所以编译器必须表现得像“好吧 Bar 不适合,但也许超类适合”。然后它“看到”它与 superlcas“Foo”一起工作。 然后 Bar 将被视为 Foo,因为每个 Bar 都是某种 Foo 或类似的东西?至少我是这么想的。

好吧,如果我尝试用 Java7 编译该行,它会失败。所以看起来Java8在类型擦除方面更聪明一点?!

在 Java8 中有什么方法不允许子类,这样就不会编译?:

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

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

问候

【问题讨论】:

  • 如果你想让Foo f = Factory.doSth(new Bar());不编译,那么Bar一定不能实现TestInterface,这意味着它不能扩展Foo..
  • 如果BarFoo的子类,那么Bar的实例Foo的实例,并且Bar的实例必须在任何时候被接受Foo 被接受。为什么不希望 Bar 在这里被接受?
  • @FelRPI 不同之处在于类型推断在两个版本中的工作方式,在 Java 8 中发生了变化。事实上,Java 8 中的类型推断有所改进。
  • 在我的机器上,使用 Java 1.8.0_40,你的行没有编译。你确定你的例子吗?您使用的是哪个 Java 版本?
  • @FelRPI Foo f = Factory.&lt;Foo&gt;doSth(new Bar()); 在 Java 7 中应该可以工作(明确指定类型参数)。

标签: java generics subclass type-inference


【解决方案1】:

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

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

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

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

请注意,Factory.&lt;Foo&gt;doSth() 需要 Foo 作为参数,因此 new Bar() 被隐式转换为 Foo,这样可以正常工作。

恐怕没有办法解决这个问题。您将始终能够将对象向上转换到其超类。你可以创建Foofinal,但这完全不允许Foo的子类化。

【讨论】:

    【解决方案2】:

    我同意这种情况确实存在; Bar“概念上”是Foo的子类,但我们不想让它们共享相同的方法doSth()。 不完全相同,但相似的是,在许多情况下应避免在超子类之间共享equals() 方法,以保持等价关系的对称性和传递性。 (详细信息在 Effective Java 2nd ed. by J.B. 的第 8 项中)

    到目前为止,我还没有找到一个带有泛型参数描述的好的解决方案。 (Java 的类型系统和其他典型的 OO 语言一样,似乎基本上假设每对父类和子类都应该默认共享父类的方法。)相反,我发现在这种情况下丢弃继承将是一种可能的解决方案,如下,

    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 中使用 &lt;Foo&gt;

    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
    

    【讨论】:

      猜你喜欢
      • 2010-10-04
      • 1970-01-01
      • 1970-01-01
      • 2013-12-26
      • 2016-07-12
      • 1970-01-01
      • 1970-01-01
      • 2010-09-16
      • 2018-10-13
      相关资源
      最近更新 更多