反射泛型类型边界和方法链接

时间:2014-11-25 14:39:29

标签: java generics

我需要提供一个在单独的类层次结构中使用的通用接口,并希望该接口支持调用链。

我试图使用反射边界来做到这一点,但我似乎无法将其投入使用而无需投射"这个"到所需的类型。这是我目前的做法:

public interface Operable<T extends Operable<T>> {
  T prepare();
  T operate();
}

public abstract class BaseOperable<T extends Operable<T>> implements Operable<T> {
  @Override
  public T prepare() {
    System.out.println("Preparing...");
    // why is this needed? "this" is a BaseOperable that implements Operable<T>
    return (T) this;
  }
}

public class SpecialOperable<T extends SpecialOperable<T>> extends
    BaseOperable<SpecialOperable<T>> {
  @Override
  public T operate() {
    System.out.println("Operation "
            + (Math.random() > 0.5 ? "succeeded" : "failed"));
    // this seems to be required
    return (T) this;
  }

  @Override
  public T prepare() {
    // if I don't override this, the supertype T is used which is
    // BaseOperable and hides "specialOp" from chaining
    return (T) super.prepare();
  }

  public T specialOp() {
    System.out.println("Doing something special...");
    return (T) this;
  }
}

以上以下代码行编译: mySpecialOperable().prepare().operate().specialOp().operate();

我的问题是:有没有办法避免每个return语句的类型转换?是否也可以不必覆盖最专业级别的所有内容(如prepare()方法所做的那样)?

2 个答案:

答案 0 :(得分:0)

您的问题可以简化为:

public abstract class AbstractOperable<T extends AbstractOperable<T>> {

    public T prepare() {
        // Type mismatch: cannot convert from AbstractOperable<T> to T
        return this;
    }

}

public class OperableImpl extends AbstractOperable<OperableImpl> {

}

现在,请考虑以下类:

public class OperableImplHack extends AbstractOperable<OperableImpl> {

}

虽然它符合T extends AbstractOperable<T>合同,但this不是T。这就是编译器无法知道thisT是否AbstractOperable的原因。


解决方案#1:

public abstract class AbstractOperable<T extends AbstractOperable<T>> {

    public abstract T getThis();

    public T prepare() {
        return getThis();
    }

}

public class OperableImpl extends AbstractOperable<OperableImpl> {

    @Override
    public OperableImpl getThis() {
        return this;
    }

}

解决方案#2:

public abstract class AbstractOperable<T extends AbstractOperable<T>> {

    protected T that;

    public T prepare() {
        return that;
    }

}

public class OperableImpl extends AbstractOperable<OperableImpl> {

    public OperableImpl() {
        that = this;
    }

}

答案 1 :(得分:0)

问题在于,如果转换是合法的,那么您在编译器无法在编译时确定的地方假设类型安全。这需要你遇到的警告:为了使这个更清楚,假设:

class Foo implements Operable<Foo> { ... } // legal
class Bar implements Operable<Foo> { ... } // not intended, but also legal

Bar类不是您的抽象模型合法的。您希望实现自我类型,但在Java中您可以实际要求的是要求实现给定接口的类型T的扩展。因此,延伸

class Bar extends BaseOperable<Foo> { ... } 

基本上会在运行时执行,就好像你实现了:

class Bar implements Operable<Foo> {
  @Override
  public Foo prepare() {
    System.out.println("Preparing...");
    // Why is this needed, you ask? Because you are now expressing this:
    return (Foo) this; // this is of type Bar, not Foo
  }
  ...
}

其中thisBar的实例,而不是Foo的实例。当然,这会导致ClassCastException,并且对于这种可能性,编译器会警告您静态类型安全性已经失败,并且此异常可能发生在您通常不期望的情况下。

您通常可以通过添加以下方法来避免这种情况:

protected abstract T self();

然后由任何非抽象类实现。由于此方法是使用非泛型返回类型实现的,因此静态编译检查可以完成其工作,并禁止任何非法返回类型,如我们之前观察到的那样。然后,上面的prepare方法将实现如下:

public T prepare() {
  System.out.println("Preparing...");
  return self();
}

但是,如果您正在使用未由任何用户实现的封闭式API,并且您希望采用该快捷方式(希望您具有针对任何可能的滥用行为进行验证的单元测试),则可以使用{{注释该方法1}}告诉编译器你知道你正在处理什么。