重新设计未经检查的演员警告

时间:2015-02-10 03:26:38

标签: java generics

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

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);
}

是否有其他方法可以实现类似的逻辑而不会导致未经检查的强制转换警告?

4 个答案:

答案 0 :(得分:2)

考虑使用Google Guava中的TypeToInstanceMap<Parser<?>>。这将允许您执行此类操作,无需编译器警告或错误:

TypeToInstanceMap<Parser<?>> parsers;

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

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

这本质上是一个库,它与@ruakh's answer非常相似。

在Java 5发布后不久,将<T>添加到Class<T>,Neil Gafter,discussed the fundamental issue in his blog的开发人员。他称Class<T>为&#34;类型代币&#34;,并说:

  

[Y]你根本无法为通用类型

制作类型令牌

...换句话说,您无法制作Class<Parser<T>>

答案 1 :(得分:2)

无法创建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);
}

答案 2 :(得分:1)

由于您的地图parsers值类型为Parser<?>且您的方法的返回类型为Parser<T>,因此显示parsers.get(type)的结果显然是错误1}}到T

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

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

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

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

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

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);
  }
}

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

答案 3 :(得分:0)

我以不同的方式工作。我自己正在尝试使用仿制药,很乐意接受批评:)

我所做的是为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);
    }

}