围绕未经检查的投射警告重新设计

Redesigning around unchecked cast warnings

我有一个 class 将包含一些针对不同对象的不同解析器实现。虽然我能够在没有任何警告的情况下存储解析器实现,但从映射中获取解析器会警告未检查的转换异常。以下是简化的摘录:

private Map<Class<?>, Parser<?>> parsers = new HashMap<>();

public <T> void addParser(Class<T> type, Parser<T> parser) {
    parsers.put(type, parser);
}

private <T> Parser<T> parserFor(Class<T> type) {
    // Compiler complains about unchecked cast below
    return (Parser<T>) parsers.get(type);
}

是否有另一种方法可以实现类似的逻辑而不会导致未检查的转换警告?

因为你的映射 parsers 值类型是 Parser<?> 并且你的方法的 return 类型是 Parser<T>,所以转换 [=16= 的结果显然是错误的] 到 T.

消除编译错误的一种方法是将类型转换为 Parser<T>:

private <T> Parser<T> parserFor(Class<T> type) {
  return (Parser<T>)parsers.get(type);
}

此外,您可以将 return 类型更改为 Parser<?>,因为您将解析器映射指定为 Map<Class<?>, Parser<?>>。这也将清除编译错误。

private <T> Parser<?> parserFor(Class<T> type) {
  return parsers.get(type);
}

或者您可以将类型参数添加到包装中 class。

public class YourClass<T> {
  private Map<Class<T>, Parser<T>> parsers = new HashMap<>();

  public void addParser(Class<T> type, Parser<T> parser) {
    parsers.put(type, parser);
  }

  private Parser<T> parserFor(Class<T> type) {
    return parsers.get(type);
  }
}

我不确定哪一个可以正确应用,但是,尽量不要使用类型转换。考虑一下我们为什么使用泛型。

考虑使用来自 Google Guava 的 TypeToInstanceMap<Parser<?>>。这将使您可以在没有任何编译器警告或错误的情况下执行这样的操作:

TypeToInstanceMap<Parser<?>> parsers;

parsers.putInstance(new TypeToken<Parser<String>>(){},
                    makeStringParser());

Parser<Integer> intParser = parsers.getInstance(new TypeToken<Parser<Integer>>(){});

这本质上是一个库,其底层功能与 非常相似。

<T> 添加到 Class<T> 的开发人员 Neil Gafter discussed the fundamental issue in his blog 在 Java 5 发布后不久。他称 Class<T> 为 "type token",并说:

[Y]ou simply can't make a type token for a generic type

...换句话说,你不能Class<Parser<T>>.

没有办法创建一个 Map<Class<...>, Parser<...>>,其中 ...-s 可以是任何东西,但必须在键和它的值之间进行匹配;因此,您无法让编译器为您进行检查,其中检索 Class<T> 保证会为您提供 Parser<T>。但是,您的代码本身是正确的; 知道你的转换是正确的,即使编译器不正确。

所以,当你知道你的演员表是正确的,但 Java 不知道,你能做什么?

最好和最安全的方法是编写一段尽可能小的特定代码,负责处理已检查和未检查逻辑之间的转换,并确保未检查逻辑不会导致任何错误。然后,您只需使用适当的 @SuppressWarnings 注释标记该代码。例如,你可以有这样的东西:

public abstract class Parser<T> {
    private final Class<T> mType;

    protected Parser(final Class<T> type) {
        this.mType = type;
    }

    public final Class<T> getType() {
        return mType;
    }

    @SuppressWarnings("unchecked")
    public final <U> Parser<U> castToParserOf(final Class<U> type) {
        if (type == mType) {
            return (Parser<U>) this;
        } else {
            throw new ClassCastException("... useful message ...");
        }
    }
}

这将允许您安全地编写,在您的示例中:

public <T> void addParser(final Parser<T> parser) {
    parsers.put(parser.getType(), parser);
}

private <T> Parser<T> parserFor(final Class<T> type) {
    return parsers.get(type).castToParserOf(type);
}

我让这个以不同的方式工作。我自己正在试验泛型,很乐意接受批评:)

我所做的是为 Parseable 对象添加一个标记接口,然后将其用作 Parser 的上限。

public interface IParseable {}

public class Parser<T extends IParseable> {
    T paresableObj;
    // do something with parseableObject here
}

现在 Parser Factory 不必使用通配符或强制转换。

public class ParserFactory {

    private Map<Class<?>, Parser<? extends IParseable>> parsers = new HashMap<Class<?>, Parser<? extends IParseable>>();

    public <T> void addParser(Class<T> type, Parser<? extends IParseable> parser) {
        if(parserFor(type) == null){
            parsers.put(type, parser);
        }else{
            //throw some excep
        }
    }

    private <T> Parser<? extends IParseable> parserFor(Class<T> type) {
        return parsers.get(type);
    }

}