为什么Java无法在子类中使用自绑定泛型类型推断正确的类型

时间:2019-07-06 17:13:01

标签: java generics inheritance self-reference

假设我有以下代码:

 class A<THIS extends A> {
        public THIS self() {
            return (THIS) this;
        }
    }

 class B extends A<B> { }

 A a = new A<>().self().self().self();  // OK
 B b = new B().self().self().self().self().self().self(); // OK

它编译良好。 但是,当我再添加一个继承级别时,它将不起作用。

    class A<THIS extends A> {
        public THIS self() {
            return (THIS) this;
        }
    }
    class B<THIS extends B> extends A<B> { }
    class C extends B<C> { }

  A<A> self2 = new A<A>().self().self().self();  // OK
  B<B> self = new B<B>().self().self().self().self(); // error -> A
  C self1 = new C().self().self();  // error -> A

我尝试了其他通用类型,但没有帮助。

我该怎么做才能编译此代码?

2 个答案:

答案 0 :(得分:2)

这种方法是有缺陷的。

它不会阻止诸如这样的声明

class D extends A<B> {
}

将进行编译,但会在运行时引发异常,更具体:

new D().self().getClass() // => ClassCastException

要让类提供超出其已知超类的功能,您可以尝试使用 adapter 模式。

它的基础通常是一个接口,例如

interface Adaptable {
    T getAdapter(Class<? extends T> key);

    // for those who don't like to type long method names
    default T as(Class<? extends T> key) {
        return getAdapter(key);
    }
}

一个实现可能看起来像

class A implements Adaptable {
    @Override
    public T getAdapter(Class<? extends T> key) {
        /*
         * To be less strict, one might also check for 'key.isInstance(this)',
         * but it's an implementation decision.
         */
        if(getClass() == key) {
            return key.cast(this);
        }
        return null;
    }
}

但是, adapter 模式允许提供其他对象,通常是目标上的专用视图,请参见下面的FileSource示例。

此方法的主要缺点是客户端将始终必须检查适配器是否可用。但是,如果客户知道对象 是它正在寻找的子类,则可以简单地将其强制转换,这样我们就不会损失任何东西。也可以使用java.util.Optional扩展该接口,但是基本思想保持不变。

interface Adaptable {
    Optional<T> getAdapter(Class<? extends T> key);
}

对于一个示例用例,假设有一个Source类,它为任何过程建模可用的源。我们知道源代码处理通常很棘手,因此很难归一化为单个类或接口,因此我们让Source类实现了Adaptable

class Source implements Adaptable {
    @Override
    public Optional<T> getAdapter(Class<? extends T> key) {
        if(getClass() == key) {
            return Optional.of(key.cast(this));
        }
        return Optional.empty();
    }
}

现在有一个基本的实现FileSource,通常以java.io.File的形式提供。

class FileSource extends Source {
    private File pointer;

    public File asFile() {
        return pointer;
    }
}

客户端现在可以检查源文件是否可用,并使用基础的java.io.File进行某些操作。

Source source;
...
source.getAdapter(FileSource.class).ifPresent(fileSource -> {
    File file = fileSource.asFile();
    // do your magic with 'file'
});

更好的是,FileSource可以简单地为File提供一个适配器。此时,客户甚至不需要关心实现子类,而只需关心他/她实际想要的是什么。

class FileSource extends Source {
    private File pointer;

    @Override
    public Optional<T> getAdapter(Class<? extends T> key) {
        if(File.class == key) {
            return Optional.of(key.cast(asFile()));
        }
        return super.getAdapter(key);
    }

    public File asFile() {
        return pointer;
    }
}

Source source;
...
source.getAdapter(File.class).ifPresent(file -> {
    // do your magic with file
});

答案 1 :(得分:-1)

几小时的苦难后,我找到了正确的路。

class A<THIS extends A<THIS>> {
    public THIS self() {
        return (THIS) this;
    }
}
class B<T extends B<T>> extends A<T> { }
class C<T extends C> extends B<C<T>> { }
A a = new A<>().self().self().self().self().self().self();     // OK
B b = new B<>().self().self().self().self().self().self();     // OK
C c = new C<>().self().self().self().self().self().self();    // OK

是的,它像@Izruo点一样不安全。