【问题标题】:Why java cant infer right type with Self bound generic type in subclasses为什么 java 不能在子类中使用自绑定泛型类型推断正确的类型
【发布时间】:2019-07-06 17:13:01
【问题描述】:

假设我有这个代码:

 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

我尝试了不同的泛型类型,但没有任何帮助。

我必须做什么才能编译这段代码?

【问题讨论】:

  • 您正在为self 使用原始类型。见:stackoverflow.com/questions/2770321/…
  • 这种方法是有缺陷的。极端情况:class D extends A&lt;B&gt; {} 编译,但 new D().self().getClass() 将抛出 ClassCastException

标签: java generics inheritance self-reference


【解决方案1】:

这种方法是有缺陷的。

它不会阻止诸如

之类的声明
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
});

【讨论】:

    【解决方案2】:

    经过几个小时的磨难,我找到了正确的方法。

    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 点那样安全。

    【讨论】:

      猜你喜欢
      • 2021-12-21
      • 1970-01-01
      • 2021-11-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-15
      • 1970-01-01
      相关资源
      最近更新 更多