【问题标题】:Returning an objects subclass with generics返回具有泛型的对象子类
【发布时间】:2010-07-19 20:08:03
【问题描述】:

对于一个抽象类,我想定义一个为子类返回“this”的方法:

public abstract class Foo {
    ...
    public <T extends Foo> T eat(String eatCake) {
        ...
        return this;
    }
}  

public class CakeEater extends Foo {}

我希望能够做以下事情:

CakeEater phil = new CakeEater();
phil.eat("wacky cake").eat("chocolate cake").eat("banana bread");

可以说香蕉面包会抛出 IllegalArgumentException 并显示消息“不是蛋糕!”

【问题讨论】:

    标签: java generics subclass parameterized return-type


    【解决方案1】:
    public abstract class Foo<T extends Foo<T>>  // see ColinD's comment
    {
        public T eat(String eatCake) 
        {
            return (T)this;
        }
    }
    
    public class CakeEater extends Foo<CakeEater> 
    {
        public void f(){}
    }
    

    编辑

    要求子类以某种超出静态类型可以检查的方式运行是没有问题的。我们一直这样做——一页又一页的简单英语来指定你如何编写子类。

    另一个建议的解决方案,具有协变返回类型,必须做同样的事情 - 用简单的英语要求子类实现者返回 this 的类型。静态类型无法指定该要求。

    【讨论】:

    • 应该是Foo&lt;T extends Foo&lt;T&gt;&gt;。这是可行的,尽管您可以轻松地创建一个类,当您调用 eat 时,该类会因 ClassCastException 而爆炸,例如 class Bar extends Foo&lt;CakeEater&gt;
    • 这是我最终使用的解决方案,但您会注意到返回有一个未经检查的强制转换警告。虽然从逻辑上讲,我们可以强制转换为 T,因为这是 T 的子类。
    • 是的。在一个更好的世界里,Java 可以有一个 This 类型,这在很多情况下都非常有用。
    • class FakeEater extends Foo&lt;CakeEater&gt; { } 现在注意警告。
    • 汤姆,我没有注意到任何警告。也许这就是您回应的重点,即没有编译时警告。调用 FakeEater 的 eat 时,如果将其分配给 FakeEater 字段,则返回类型不是 FakeEater 类型的编译时异常。
    【解决方案2】:

    正如 Michael Barker 指出的那样,从客户的角度(通常是您想要采用的方法)来看,有品位的方法是使用协变返回类型,它是为了支持泛型而添加的。

    稍微不那么有品位,但更有品位的演员是添加getThis方法:

    protected abstract T getThis();
    
    public <T extends Foo> T eat(String eatCake) {
        ...
        return getThis();
    }
    

    【讨论】:

      【解决方案3】:

      我认为您不需要泛型 Java 5(及更高版本)具有协变返回类型,例如:

      public abstract class Foo {
          ...
          public Foo eat(String eatCake) {
              ...
              return this;
          }
      }  
      
      public class CakeEater extends Foo {
      
          public CakeEater eat(String eatCake) {
              return this;
          }
      }
      

      【讨论】:

      • 如果它在超类中完全定义,那么它会创建冗余代码来在子类中定义它;它可以调用超类,但这似乎没有必要。如果您将其转换为 T 类型并禁止显示警告,它会起作用。
      • 如果您将其转换为类型 T 并禁止显示警告,它会起作用。它应该返回子类的类型的原因是:methodTakesCakeEater((new CakeEater).eat("cake").eat("pie"); methodTakesCakeEater 需要一个 CakeEater 参数。
      • 如果解决方案包含@SuppressWarnings,则不应将其视为解决方案。只是我的两分钱。
      • 我不认为这是解决草图问题的好方法,因为它需要重写子类中的所有方法,几乎​​不值得付出努力。这个想法是有一个流畅的 API,子类可以利用,而不是重写。
      • 我也不认为这是需要忽略或抑制警告的“前卫”东西
      【解决方案4】:

      我以前用来实现类似行为的一种方法是让子类将其类型传递给(生成的)父类型的构造函数。通过免责声明,我正在动态生成子类,继承有点欺骗,以保持我的代码生成简单,因为我的第一直觉总是尝试完全删除扩展关系。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多