[每日短篇] 25 - 如何解决 Java 泛型类型转换时的警告

问题

平常在写 Java 代码时对警告 Type safety: Unchecked cast from XXX to YYY 必定不会陌生,例如 Type safety: Unchecked cast from Object to Map<String,String>。若是仔细观察的话,能够注意到,YYY 历来不会是一个非泛型的类型。java

缘由

产生这个警告的缘由是在强制类型转换时目标类型是一个非无边界通配符的泛型类型,而 Java 的半残泛型又没法在运行时判断一个泛型类型是否匹配目标类型。举个例子来讲就是:工具

Object source = Collections.singletonMap("a", 2);  // #1
var target = (Map<String, String>) source;         // #2
var value = target.get("a");                       // #3

对于上面的代码,没法在 #2 行代码执行时就确认 target 是一个彻底经过类型检查的值,当 #3 行代码执行时,仍是会产生 java.lang.ClassCastException 异常;而从直觉上,若是 #2 行没有编译期错误或者运行时 java.lang.ClassCastException 异常,那 target 就是 Map<String, String> 类型的。在这个矛盾之下,在 #2 处产生 Type safety: Unchecked cast 警告提醒该行可能有问题,也是一种没有办法的办法。编码

前面提到了非无边界通配符的泛型类型,若是目标类型是无边界通配符的泛型类型,例如:code

Object source = Collections.singletonMap("a", 2);  // #1
var target = (Map<?, ?>) source;                   // #2

上面代码中的 #2 行不会产生警告,由于这将指定具体的泛型参数类型这件事推迟到了后面的代码中,在这里只须要肯定 sourceMap 类型便可。ip

解决

1. 解决提出问题的人

最容易想到的,也是最广泛采用的方法天然是——当解决问题有困难时,解决提出问题的人。固然,我这是开个玩笑,虽然你们都知道编码实践中这才是主流。get

例如在 Eclipse 中,这个警告受 Preferences > Java > Compiler > Errors/Warnings > Generic types > Unchecked generic type operation 选项控制,将其关闭便可。io

对这个警告来讲,因为它很是特定于泛型类型的转换,而且在这个问题上是总体没有好的解决方案,所以关掉也不会有什么实质性的影响。可是由于没有通用的关掉警告的方法,这种不可移植的方案难以让人接受。编译

另外,@SuppressWarnings("unchecked") 也可让提出问题的人闭嘴。我但愿它是被用在变量上面而不是方法上甚至模块上。使用这个注解的问题是,当对代码质量要求很高时,这个注解一般是被配置为忽略的,而试图消除警告的场景又每每与之高度重合。ast

2. 严谨的处理办法

前面提到了,无边界通配符类型由于延迟了类型指定而完全符合了要求,咱们能够把类型转换拆成多步,保证每一步都只转换肯定的类型。固然,在这个场景下这叫没有困难创造困难也要上。class

Object source = Collections.singletonMap("a", 2);  // #1
var target = (Map<?, ?>) source;                   // #2
Object t = target.get("a");                        // #3
                                                   // #4
if (t instanceof String) {                         // #5
    String value = (String) t;                     // #6
}                                                  // #7

其中的繁琐和局限性一看便知。须要特别注意的是 #3= 的右边失去了强类型的好处,彷佛有些得不偿失。

3. 一个还算不错的处理方式

从前面给出的缘由能够看到,在眼下以及可预见的将来,都没有简单而完全的解决方法。好在这个警告主要是给有代码洁癖人带来麻烦,能够用一种代码级的方法几乎完整解决问题。只须要写一个工具类

public final class CastUtils {

    @SuppressWarnings("unchecked")
    public static <T> T cast(Object obj) {
        return (T) obj;
    }

    private CastUtils() {
        throw new UnsupportedOperationException();
    }

}

原来有警告的代码改成

Object source = Collections.singletonMap("a", 2);           // #1
Map<String, String> target1= CastUtils.cast(source);        // #2
var target2 = CastUtils.<Map<String, String>>cast(source);  // #3
var value1 = target1.get("a");                              // #4
var value2 = target2.get("a");                              // #5

注意 #2 行、#3 行的不一样写法,它们都能消除警告,可是 #2 写法更短,在绝大多数场景下更优一点。

CastUtils 类中依然使用到了 @SuppressWarnings("unchecked") 注解,若是该注解没有被配置为忽略,就能彻底消除该警告,若是被配置为忽略,那也能够保持整个代码中仅有 1 处该警告,也就是几乎完整解决了问题。固然,还有另一种选择,将 CastUtils 置于某个工具类库 jar 文件中,再把 jar 文件引用到项目中,不管 @SuppressWarnings("unchecked") 是否被配置为忽略,均可以完全消除该警告。

总结

Type safety: Unchecked cast 是个说严重不严重,可是对整洁代码来讲却不能无视的警告。若是本身有工具类库,在工具类库里增长 CastUtils 工具类是最好的选择。次优的选择则是将 CastUtils 工具类直接置于项目代码中。

相关文章
相关标签/搜索