类型安全:未经检查的转换

329
在我的Spring应用程序上下文文件中,我有类似这样的内容:
<util:map id="someMap" map-class="java.util.HashMap" key-type="java.lang.String" value-type="java.lang.String">
    <entry key="some_key" value="some value" />
    <entry key="some_key_2" value="some value" />   
</util:map>

在Java类中,实现看起来像这样:
private Map<String, String> someMap = new HashMap<String, String>();
someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");

在Eclipse中,我看到一个警告,上面写着:
类型安全性:从Object到HashMap的未检查转换
出了什么问题?

1
我想出了一种例程来实际检查参数化HashMap的转换,从而消除了未经检查的转换警告:[链接](https://dev59.com/GnRB5IYBdhLWcg3wyqOo#509230)我认为这是“正确”的解决方案,但它是否值得讨论可能存在争议。 :) - skiphoppy
相关/重复:如何解决未经检查的转换警告? - blahdiblah
可能是如何解决未经检查的转换警告?的重复问题。 - Angelo Fuchs
10个回答

389
问题在于类型转换是在运行时进行检查的,但由于类型擦除,在运行时实际上对于任何其他 Foo 和 Bar,HashMap<String,String> 和 HashMap<Foo,Bar> 之间没有区别。 使用 @SuppressWarnings("unchecked") 并忍受一下。哦,并呼吁 Java 引入具体化的泛型 :)

19
我更喜欢Java的具体化泛型而不是未经类型标记的NSMutableWhatever,后者感觉像是倒退了十年。每天都会选择前者。至少Java正在努力。 - Dan Rosenstark
13
没错。如果您坚持类型检查,只能使用HashMap<?,?>进行检查,但这不会消除警告,因为这与不对泛型类型进行检查是相同的。这并不是世界末日,但很烦人,要么压制警告,要么接受它。 - dubmojo
15
什么是具体化的泛型? - SasQ
1
@SasQ和其他所有想知道的人都应该看一下这个问题 - Flimtix

289
首先,您正在通过新的 HashMap 创建调用浪费内存。您的第二行完全忽略了对此创建的哈希映射的引用,使其随后可供垃圾收集器使用。因此,请不要这样做,请使用:
HashMap<String, String> map = new HashMap<>();
private Map<String, String> someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");

其次,编译器报错是因为您将对象转换为HashMap时没有检查它是否为HashMap。但是,即使您执行以下操作:

if(getApplicationContext().getBean("someMap") instanceof HashMap) {
    private Map<String, String> someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");
}

你可能仍然会收到这个警告。问题在于,getBean 返回 Object,因此不知道它的类型是什么。直接将其转换为 HashMap 不会导致第二种情况的问题(也许第一种情况不会有警告,我不确定 Java 5 的编译器对警告有多苛刻)。但是,你将其转换为了一个 HashMap<String, String>

HashMap 实际上是将一个对象作为键,另一个对象作为值的映射,如果你愿意,可以认为是 HashMap<Object, Object>。因此,当你获取你的 bean 后,不能保证它可以表示为 HashMap<String, String>,因为你可能有一个 HashMap<Date, Calendar>,因为返回的非泛型表示可以包含任何对象。

如果代码编译通过,并且你可以执行 String value = map.get("thisString"); 而没有任何错误,请不要担心这个警告。但是,如果映射不完全由字符串键和字符串值组成,则会在运行时收到 ClassCastException,因为在这种情况下泛型无法阻止出现这种情况。


20
这是一段时间之前发生的事情,我正在寻找针对Set<CustomClass>进行类型检查的答案,但你不能在泛型中使用instanceof。例如,if(event.getTarget instanceof Set<CustomClass>)。你只能使用?来检查泛型,但这不会消除强制转换警告。例如,if(event.getTarget instanceof Set<?>)。 - dubmojo

121
根据上述消息,List无法区分List、List或List。
我已为类似问题解决了此错误消息。
List<String> strList = (List<String>) someFunction();
String s = strList.get(0);

以下是:

List<?> strList = (List<?>) someFunction();
String s = (String) strList.get(0);

解释:第一种类型转换验证对象是否为List,而不关心其中保存的类型(因为我们无法在List级别上验证内部类型)。现在需要进行第二次转换,因为编译器只知道List包含某种类型的对象。这将在访问List中的每个对象时验证其类型。


5
你是正确的,我的朋友。不要对列表进行强制类型转换,而是迭代它并对每个元素进行强制类型转换,警告就不会出现了,太棒了。 - juan Isaza
4
已经去除了警告,但我仍然不太自信:P - mumair
8
感觉就像是给编译器蒙上眼睛,但对运行时没有影响 :D 所以我没看出这与 @SuppressWarnings("unchecked") 有什么区别。 - channae
4
太棒了!使用@SupressWarning的主要区别在于,该注释可以消除IDE和代码分析工具中的警告,但如果您正在使用-Werror标志进行编译,则最终会出现错误。使用这种方法可以修复两个警告。 - Edu Costa
1
在https://www.baeldung.com/java-warning-unchecked-cast中解释了ClassCastException可能会在代码的任何地方抛出,而不涉及此冒犯的未检查转换。解决方法是将转换移动到具有非常简洁的ClassCastException的内容,因此如果需要更容易修复。 - Queeg

39

警告只是一种警示。有时候警告是不相关的,有时候则并非如此。它们被用来引起您对编译器认为可能会出现问题但可能并不是问题的事物的注意。

在类型转换的情况下,它总是会发出警告。如果你绝对确定某个特定的类型转换是安全的,那么你应该考虑在这行代码之前添加一个注释,就像这样(我不确定语法):

@SuppressWarnings (value="unchecked")

22
警告信号永远不应该被忽视,要么排除这些类型的警告,要么修复它。总会有那么一刻,你会收到太多的警告而无法发现相关的警告。 - ezdazuzena
16
当进行参数化泛型类型转换(例如Map)时,无法真正避免类转换警告,因此这是原始问题的最佳答案。 - muttonUp
根据我的经验,通常会省略"value="部分,因为那是默认值:@SuppressWarnings("unchecked")。 - Greg Brown
2
哦,很高兴有人评论了我14年前的答案。我再也不这样做了。有时警告无法实际修复,但我宁愿看到它们而不是隐藏它们。 - David M. Karr

9

您收到此消息是因为getBean返回一个对象引用,而您正在将其强制转换为正确的类型。Java 1.5会发出警告。这就是使用类似代码的Java 1.5或更高版本的特性。Spring有类型安全的版本。

someMap=getApplicationContext().getBean<HashMap<String, String>>("someMap");

在其待办事项清单上。

6

如果您真的想要摆脱这些警告,您可以创建一个继承自通用类的类。

例如,如果您尝试使用:

private Map<String, String> someMap = new HashMap<String, String>();

你可以像这样创建一个新的类
public class StringMap extends HashMap<String, String>()
{
    // Override constructors
}

当你使用

someMap = (StringMap) getApplicationContext().getBean("someMap");

编译器知道(不再是通用的)类型是什么,不会出现警告。这可能并非总是完美的解决方案,有人可能会认为这种做法有悖于通用类的目的,但您仍然可以重复使用通用类中的所有代码,只需在编译时声明要使用的类型即可。

6
避免未经检查的警告的解决方案:
class MyMap extends HashMap<String, String> {};
someMap = (MyMap)getApplicationContext().getBean("someMap");

2
看起来像是一个hack而不是解决方案。 - Malwinder Singh
1
可序列化类 MyMap 没有声明一个类型为 long 的静态 final serialVersionUID 字段 :{ - Ulterior
@Ulterior 然后添加这样一个字段:https://dev59.com/MnE95IYBdhLWcg3wf98F - Line
这只有在“someMap”实际上是本地MyMap类的一个实例时才能起作用,这似乎不太可能。 - Greg Brown

4
以下代码会引起类型安全警告:
``` Map myInput = (Map) myRequest.get(); ```
解决方法:
创建一个新的 Map 对象,不需要指定参数类型,因为列表中所持有的对象类型未经验证。 步骤 1: 创建一个新的临时 Map。
``` Map tempMap = (Map) myRequest.get(); ``` 步骤 2: 实例化主要的 Map。
Map<String, Object> myInput=new HashMap<>(myInputObj.size());

步骤三:遍历临时Map,并将值设置到主Map中。

 for(Map.Entry<?, ?> entry :myInputObj.entrySet()){
        myInput.put((String)entry.getKey(),entry.getValue()); 
    }

2

如果你发现自己经常要转换同一对象,但又不想在代码中到处写上@SupressWarnings("unchecked"),那么另一个解决方案就是创建一个带有注释的方法。这样,你就可以将类型转换集中在一个地方,从而减少错误的可能性。

@SuppressWarnings("unchecked")
public static List<String> getFooStrings(Map<String, List<String>> ctx) {
    return (List<String>) ctx.get("foos");
}

2
我做错了什么?我该如何解决这个问题?
这里:
Map someMap = (Map)getApplicationContext().getBean("someMap");
你使用了一个我们通常不想使用的旧方法,因为它返回Object:
Object getBean(String name) throws BeansException;

从bean工厂中获取(对于单例)/创建(对于原型)一个bean的方法是:
<T> T getBean(String name, Class<T> requiredType) throws BeansException;

使用它就像这样:
Map<String,String> someMap = app.getBean("someMap", Map.class);

将编译,但仍然会出现未经检查的转换警告,因为所有的Map对象不一定是Map<String, String>对象。

但是,在泛型类中,如泛型集合,<T> T getBean(String name, Class<T> requiredType) throws BeansException;是不够的,因为这需要指定多个类作为参数:集合类型和其泛型类型。

在这种情况下和一般情况下,更好的方法是不直接使用BeanFactory方法,而是让框架来注入bean。

bean声明:

@Configuration
public class MyConfiguration{

    @Bean
    public Map<String, String> someMap() {
        Map<String, String> someMap = new HashMap();
        someMap.put("some_key", "some value");
        someMap.put("some_key_2", "some value");
        return someMap;
    }
}

豆子注射:
@Autowired
@Qualifier("someMap")
Map<String, String> someMap;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接