繁体   English   中英

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

[英]Redesigning around unchecked cast warnings

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

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

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

考虑使用Google Guava中的TypeToInstanceMap<Parser<?>> 这将让你做这样的事情,没有任何编译器警告或错误:

TypeToInstanceMap<Parser<?>> parsers;

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

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

这本质上是一个库,它与@ ruakh的答案非常类似。

在Java 5发布后不久, <T>添加到Class<T>的开发人员Neil Gafter 在他的博客中讨论了基本问题 他将Class<T>称为“类型令牌”,并说:

[Y]你根本无法为泛型类型创建类型令牌

...换句话说,你不能创建一个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);
}

由于您的map parsers值类型是Parser<?>而您的方法的返回类型是Parser<T> ,因此将parsers.get(type)的结果parsers.get(type)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);
  }
}

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

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

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

}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM