2015-02-10 49 views
3

我有一个类将包含几个不同对象的不同解析器实现。尽管我能够在没有任何警告的情况下存储解析器实现,但从映射中获取解析器会警告未经检查的转换异常。下面是一个简化的摘录:重新设计未经检查的强制转换警告

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

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

+0

没有没有真正的方法来做到这一点,没有未经检查的演员。只要你不做任何有趣的事情就没关系。 – Radiodef 2015-02-10 04:27:39

回答

1

没有办法创建一个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); 
} 
+0

出于好奇,如何抑制原始类型警告比抑制未经检查的警告更好/更安全? – fayerth 2015-02-10 05:42:46

+1

@fayerth:不是的。两者同样优良 - 重要的部分是您只是尽可能地本地化非类型检查的代码,以便您对其正确性有信心。我的例子最后是'@SuppressWarnings(“raw”)',因为你不能直接从'Parser '转换成'Parser ' - 这不是编译警告*,而是编译错误*所以我需要一个中间演员,而'(Parser)'是最简单的演员。 (否则,我需要写*两个*演员,例如像'(Parser )(Parser )this'。) – ruakh 2015-02-10 05:46:05

+0

@fayerth:其实,对不起,更正:我刚刚在Eclipse中测试,并从'解析器'到'解析器'实际上工作正常。只是一个未经检查的警告。所以我会改变我的答案。 – ruakh 2015-02-10 05:51:44

1

由于地图parsers值类型是Parser<?>和你的方法的返回类型为Parser<T>,这显然给parsers.get(type)结果转换为T错误。

之一删除编译错误的方式是投类型Parser<T>

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

另外,还可以从你指定的解析器地图为Map<Class<?>, Parser<?>>返回类型更改为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); 
    } 
} 

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

+1

在编译错误上,这是我的一个错字;我已经编辑了相应的问题。 对于你关于返回'Parser '的建议,我曾尝试过,最初认为它会起作用。不过,我后来意识到,当我使用'parserFor()'时,我得到了一个未经检查的强制转换警告,这基本上意味着警告现在只是在不同的地方。 理想情况下,类本身并不需要一个类型参数来完成它的工作,因为它只是想得到一个特定类型的解析器并使用它。仍然会有某种方法可以避免这种警告吗? – fayerth 2015-02-10 05:28:19

+0

返回一个通配符类型,这也是一个无界的是一个坏主意IMO。 'Parser '应该没问题。 – 2015-02-10 19:32:16

1

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

TypeToInstanceMap<Parser<?>> parsers; 

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

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

这基本上是做非常相似@ruakh's answer引擎盖下的东西的库。

在Java 5发布后不久,开发人员将<T>添加到Class<T>,Neil Gafter,discussed the fundamental issue in his blog。他呼吁Class<T>“类型标记”,并说:

[Y] OU根本无法做出型令牌泛型类型

...换句话说,你可以” t做一个Class<Parser<T>>

+0

你分享的链接绝对是一个很好的阅读,并有助于澄清这个话题。由于我正在寻找不需要第三方库的解决方案,因此我决定接受@ ruakh的答案。话虽如此,我认为这绝对是一个很好的选择。 – fayerth 2015-02-11 01:53:12

0

我得到这个以不同的方式工作。我正在尝试用仿制药自己,并会很乐意收到批评:)

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

public interface IParseable {} 

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

而现在解析器工厂不必使用通配符也不使用强制转换。

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

} 
+0

这并没有帮助,因为你强迫客户现在处理'IP可清除'而不是默认为'对象'的通配符。然而,解析器应该处理的所有内容都不可能实现该接口。所以这基本上是一个额外的负担。 – SpaceTrucker 2015-02-10 09:52:12

+0

@SpaceTrucker - 谢谢你,你是对的。它的确如此。我认为这是一个内部库,在这种情况下,让类实现标记接口并不困难。此外,我认为解析任何对象是一个高难度的命令,而Parseable的对象应该具有某些特征。不捍卫答案,只是解释我的思路。再次感谢您的反馈。 – ramp 2015-02-10 10:25:42

相关问题