Java泛型 - 将可分配的捕获类型转换为子类

时间:2018-03-28 12:35:26

标签: java generics

我在Java泛型中有以下场景:

public abstract class A<T> {
    protected final Class<T> typeOfX;

    public A(final Class<T> typeOfX) {
        this.typeOfX = typeOfX;
    }

    public abstract void load(final T x);
}

public class AnyA<S> extends A<S> {
    private final Map<String, A<? extends S>> map;

    public AnyA(final Class<S> superTypeOfX,
                final Map<String, A<? extends S>> map) {
        super(superTypeOfX);
        this.map = map;
    }

    @Override
    public void load(final S superx) {
        for (final A<? extends S> a: map.values())
            if (a.typeOfX.isAssignableFrom(superx.getClass())) //Here I want to say: "if superx can be casted to a.typeOfX".
                a.load(a.typeOfX.cast(superx)); //Here I want to cast superx to a.typeOfX (so as to call the load method). Here's the compile error.
    }
}

我收到错误:

  

不兼容的类型:S无法转换为CAP#1
  其中S是一个类型变量:
      S扩展在AnyA类中声明的Object   其中CAP#1是一个新的类型变量:
      CAP#1将S从捕获?延伸S

AnyA是一个复合A,即A,可以维护其他几个A个实例。

AnyA方法中的

load(...)将决定应该使用哪个维护的A个实例来将加载过程传递给&#34;论证 换句话说,AnyA负责找到加载参数的正确AAnyA也是A,因为它处理加载参数。

我的问题是:
当我知道ST的子类并且A中的所有AnyA个实例都可以加载S的子类时,为什么不能进行此转换? 1}}?
如何在不改变类图的情况下克服这个问题呢?

我已阅读有关"helper methods"的内容,但无法将此处显示的示例与我的问题相符。

我正在使用带有Java SDK 8的NetBeans IDE。

1 个答案:

答案 0 :(得分:2)

请注意,无论您做什么,代码在任何情况下都不是“语法类型安全”。 未经检查的演员表,唯一能防止出错的安全带是isAssignableFrom检查。

(通常情况下,我只是提到完整性)

当您拉开线条时,错误的原因可能更明显(此处,S代表SuperType,根据Type Parameter Naming Conventions - 请关注它们!)

A<? extends S> a = ...;
S s = a.typeOfX.cast(s);
a.load(s);

A<? extends S>直观地表示A可以在load方法中接受未知类型。您知道它扩展了S类型,但您知道这是哪种类型。

当您为Object插入S时,它可能会变得非常明显:

A<String> specificA = ...;

// So the "specificA" can load "String" objects. Then this is fine:
A<? extends Object> a = specificA;
Object s = a.typeOfX.cast(s);

// But here's the error: "s" is only an Object, and not a String!
a.load(s); 

我认为混淆的主要原因(以及问题的主要原因)如下:致电

Object s = a.typeOfX.cast(s);

typeOfXString.class,则广告投放的返回类型String,但类型此时编译器可以推断出来。在上面的示例中,这是Object

但是,您已经提到了Helper Methods,实际上,通过一些技巧,您可以进行编译,

但......(见下面的注释)

import java.util.Map;

abstract class A<T>
{
    protected final Class<T> typeOfX;

    public A(Class<T> typeOfX)
    {
        this.typeOfX = typeOfX;
    }

    public abstract void load(T x);
}

class AnyA<S> extends A<S>
{
    private final Map<String, A<? extends S>> map;

    public AnyA(Class<S> superTypeOfX,
        Map<String, A<? extends S>> map)
    {
        super(superTypeOfX);
        this.map = map;
    }

    @Override
    public void load(S s)
    {
        for (A<? extends S> a : map.values())
        {
            if (a.typeOfX.isAssignableFrom(s.getClass()))
            {
                callLoad(a, s);
            }
        }
    }

    private static <S, T extends S> T cast(A<T> a, S s)
    {
        T t = a.typeOfX.cast(s);
        return t;
    }

    private static <T, S extends T> void callLoad(A<S> a, T s)
    {
        a.load(cast(a, s));        
    }
}

我不会在实践中推荐这个。

个人和主观:我认为当你进行isAssignableFrom检查时,(未经检查)的演员应该尽可能接近到这个检查。否则,代码将很难理解。

因此,虽然未经检查的强制转换在实践中是代码气味,但我尽可能避免使用SuppressWarning,我认为这更具可读性:

for (A<? extends S> a : map.values())
{
    if (a.typeOfX.isAssignableFrom(superx.getClass()))
    {
        // This call is safe as of the check done above:
        @SuppressWarnings("unchecked")
        A<Object> castA = (A<Object>) a;
        castA.load(superx);
    }
}