提问人:skiphoppy 提问时间:2/4/2009 最后编辑:Chris Stillwellskiphoppy 更新时间:9/2/2022 访问量:532934
如何解决未经检查的强制转换警告?
How do I address unchecked cast warnings?
问:
Eclipse 给了我以下形式的警告:
类型安全:从 Object 到 HashMap 的未检查强制转换
这是来自对 API 的调用,我无法控制该 API 返回 Object:
HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
HashMap<String, String> theHash = (HashMap<String, String>)session.getAttribute("attributeKey");
return theHash;
}
如果可能的话,我想避免 Eclipse 警告,因为从理论上讲,它们至少表明存在潜在的代码问题。不过,我还没有找到消除这个问题的好方法。我可以将涉及的单行提取到方法本身并添加到该方法中,从而限制了忽略警告的代码块的影响。有更好的选择吗?我不想在Eclipse中关闭这些警告。@SuppressWarnings("unchecked")
在我进入代码之前,它更简单,但仍然引发了警告:
HashMap getItems(javax.servlet.http.HttpSession session) {
HashMap theHash = (HashMap)session.getAttribute("attributeKey");
return theHash;
}
问题出在其他地方,当您尝试使用哈希时,您会收到警告:
HashMap items = getItems(session);
items.put("this", "that");
Type safety: The method put(Object, Object) belongs to the raw type HashMap. References to generic type HashMap<K,V> should be parameterized.
答:
如果你发布你的代码,一个快速的猜测可以肯定地说,但你可能已经做了一些类似的事情
HashMap<String, Object> test = new HashMap();
这将在您需要时产生警告
HashMap<String, Object> test = new HashMap<String, Object>();
可能值得一看
如果您不熟悉需要做什么。
评论
我可能误解了这个问题(一个例子和几行周围的行会很好),但你为什么不总是使用合适的接口(和 Java5+)?我看不出你为什么会想投给 a 而不是 .事实上,我无法想象有任何理由将变量的类型设置为而不是。HashMap
Map<KeyType,ValueType>
HashMap
Map
为什么来源是?它是旧集合的参数类型吗?如果是这样,请使用泛型并指定所需的类型。Object
评论
当然,显而易见的答案是不要进行未经检查的演员表。
如果绝对必要,那么至少尝试限制注释的范围。根据它的 Javadocs,它可以使用局部变量;这样,它甚至不会影响整个方法。@SuppressWarnings
例:
@SuppressWarnings("unchecked")
Map<String, String> myMap = (Map<String, String>) deserializeMap();
没有办法确定是否真的应该有泛型参数。您必须事先知道参数应该是什么(否则当您得到 ).这就是代码生成警告的原因,因为编译器不可能知道是否安全。Map
<String, String>
ClassCastException
评论
String s = (String) new Object() ;
Hashmap
HashMap<String,String>
只需在投射之前对其进行类型检查即可。
Object someObject = session.getAttribute("attributeKey");
if(someObject instanceof HashMap)
HashMap<String, String> theHash = (HashMap<String, String>)someObject;
对于任何询问的人来说,收到您不确定类型的对象是很常见的。许多遗留的“SOA”实现传递了各种对象,而这些对象不应该总是信任。(恐怖!
编辑更改了一次示例代码以匹配海报的更新,并且在一些评论之后,我看到 instanceof 不能很好地与泛型配合。但是,更改检查以验证外部对象似乎与命令行编译器配合得很好。修改后的示例现已发布。
评论
哇;我想我找到了自己问题的答案。我只是不确定这是否值得!:)
问题是没有检查演员表。所以,你必须自己检查。不能只使用 instanceof 检查参数化类型,因为参数化类型信息在运行时不可用,在编译时已被擦除。
但是,您可以使用 instanceof 对哈希中的每个项目执行检查,这样,您可以构造一个类型安全的新哈希。而且你不会引起任何警告。
感谢 mmyers 和 Esko Luontola,我已经对我最初在这里编写的代码进行了参数化,因此它可以包装在某个地方的实用程序类中,并用于任何参数化的 HashMap。如果您想更好地理解它并且对泛型不是很熟悉,我建议您查看此答案的编辑历史记录。
public static <K, V> HashMap<K, V> castHash(HashMap input,
Class<K> keyClass,
Class<V> valueClass) {
HashMap<K, V> output = new HashMap<K, V>();
if (input == null)
return output;
for (Object key: input.keySet().toArray()) {
if ((key == null) || (keyClass.isAssignableFrom(key.getClass()))) {
Object value = input.get(key);
if ((value == null) || (valueClass.isAssignableFrom(value.getClass()))) {
K k = keyClass.cast(key);
V v = valueClass.cast(value);
output.put(k, v);
} else {
throw new AssertionError(
"Cannot cast to HashMap<"+ keyClass.getSimpleName()
+", "+ valueClass.getSimpleName() +">"
+", value "+ value +" is not a "+ valueClass.getSimpleName()
);
}
} else {
throw new AssertionError(
"Cannot cast to HashMap<"+ keyClass.getSimpleName()
+", "+ valueClass.getSimpleName() +">"
+", key "+ key +" is not a " + keyClass.getSimpleName()
);
}
}
return output;
}
这是很多工作,可能回报很少......我不确定我是否会使用它。我将不胜感激任何关于人们是否认为值得的评论。另外,我很感激改进建议:除了抛出 AssertionErrors 之外,我还能做些什么更好的事情吗?有没有更好的东西可以扔?我应该将其设置为选中的例外吗?
评论
不幸的是,这里没有很好的选择。请记住,所有这些工作的目标都是为了保持类型安全。“Java 泛型”为处理非泛型遗留库提供了一种解决方案,在第 8.2 节中有一个特别称为“空循环技术”的解决方案。基本上,进行不安全的强制转换,并禁止显示警告。然后像这样遍历地图:
@SuppressWarnings("unchecked")
Map<String, Number> map = getMap();
for (String s : map.keySet());
for (Number n : map.values());
如果遇到意外类型,您将获得一个 运行时 ,但至少它会在问题根源附近发生。ClassCastException
评论
如果我必须使用不支持泛型的 API..我尝试用尽可能少的行将这些调用隔离在包装器例程中。然后,我使用 SuppressWarnings 注释,并同时添加类型安全强制转换。
这只是个人喜好,以保持事物尽可能整洁。
您可以创建如下所示的实用程序类,并使用它来禁止显示未选中的警告。
public class Objects {
/**
* Helps to avoid using {@code @SuppressWarnings({"unchecked"})} when casting to a generic type.
*/
@SuppressWarnings({"unchecked"})
public static <T> T uncheckedCast(Object obj) {
return (T) obj;
}
}
您可以按如下方式使用它:
import static Objects.uncheckedCast;
...
HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
return uncheckedCast(session.getAttribute("attributeKey"));
}
关于这一点的更多讨论在这里:http://cleveralias.blogs.com/thought_spearmints/2006/01/suppresswarning.html
评论
在这种特殊情况下,我不会将 Maps 直接存储到 HttpSession 中,而是将我自己类的实例存储到其中,而该类又包含 Map(类的实现细节)。然后,您可以确定地图中的元素类型正确。
但是,如果您仍然想检查地图的内容是否正确,则可以使用如下代码:
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1);
map.put("b", 2);
Object obj = map;
Map<String, Integer> ok = safeCastMap(obj, String.class, Integer.class);
Map<String, String> error = safeCastMap(obj, String.class, String.class);
}
@SuppressWarnings({"unchecked"})
public static <K, V> Map<K, V> safeCastMap(Object map, Class<K> keyType, Class<V> valueType) {
checkMap(map);
checkMapContents(keyType, valueType, (Map<?, ?>) map);
return (Map<K, V>) map;
}
private static void checkMap(Object map) {
checkType(Map.class, map);
}
private static <K, V> void checkMapContents(Class<K> keyType, Class<V> valueType, Map<?, ?> map) {
for (Map.Entry<?, ?> entry : map.entrySet()) {
checkType(keyType, entry.getKey());
checkType(valueType, entry.getValue());
}
}
private static <K> void checkType(Class<K> expectedType, Object obj) {
if (!expectedType.isInstance(obj)) {
throw new IllegalArgumentException("Expected " + expectedType + " but was " + obj.getClass() + ": " + obj);
}
}
评论
计算机科学中几乎每个问题都可以通过添加间接*或其他东西来解决。
因此,引入一个非泛型对象,该对象的级别高于 .在没有上下文的情况下,它看起来不会很有说服力,但无论如何:Map
public final class Items implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private Map<String,String> map;
public Items(Map<String,String> map) {
this.map = New.immutableMap(map);
}
public Map<String,String> getMap() {
return map;
}
@Override public String toString() {
return map.toString();
}
}
public final class New {
public static <K,V> Map<K,V> immutableMap(
Map<? extends K, ? extends V> original
) {
// ... optimise as you wish...
return Collections.unmodifiableMap(
new HashMap<String,String>(original)
);
}
}
static Map<String, String> getItems(HttpSession session) {
Items items = (Items)
session.getAttribute("attributeKey");
return items.getMap();
}
*除了太多的间接级别。
评论
在HTTP会话世界中,您无法真正避免强制转换,因为API是这样编写的(仅接受和返回)。Object
不过,只要稍加努力,你就可以轻松避免不受检查的演员阵容。这意味着它将变成一个传统的演员阵容,在发生错误时给予权利)。未经检查的异常可能会在以后的任何时候变成 a,而不是强制转换的点(这就是为什么它是一个单独的警告的原因)。ClassCastException
CCE
将 HashMap 替换为专用类:
import java.util.AbstractMap;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Attributes extends AbstractMap<String, String> {
final Map<String, String> content = new HashMap<String, String>();
@Override
public Set<Map.Entry<String, String>> entrySet() {
return content.entrySet();
}
@Override
public Set<String> keySet() {
return content.keySet();
}
@Override
public Collection<String> values() {
return content.values();
}
@Override
public String put(final String key, final String value) {
return content.put(key, value);
}
}
然后强制转换为该类而不是,所有内容都将在编写代码的确切位置进行检查。后来没有意外。Map<String,String>
ClassCastExceptions
评论
如果您确定 session.getAttribute() 返回的类型是 HashMap,那么您不能将类型转换为该确切类型,而只能依靠检查通用 HashMap
HashMap<?,?> getItems(javax.servlet.http.HttpSession session) {
HashMap<?,?> theHash = (HashMap<?,?>)session.getAttribute("attributeKey");
return theHash;
}
然后,Eclipse 会发出令人惊讶的警告,但当然这可能会导致难以调试的运行时错误。我只在操作关键型上下文中使用这种方法。
以这个为例,它比创建一个新的 HashMap 要快得多,如果它已经是一个,但仍然是安全的,因为每个元素都根据它的类型进行检查......
@SuppressWarnings("unchecked")
public static <K, V> HashMap<K, V> toHashMap(Object input, Class<K> key, Class<V> value) {
assert input instanceof Map : input;
for (Map.Entry<?, ?> e : ((HashMap<?, ?>) input).entrySet()) {
assert key.isAssignableFrom(e.getKey().getClass()) : "Map contains invalid keys";
assert value.isAssignableFrom(e.getValue().getClass()) : "Map contains invalid values";
}
if (input instanceof HashMap)
return (HashMap<K, V>) input;
return new HashMap<K, V>((Map<K, V>) input);
}
评论
key.isAssignableFrom(e.getKey().getClass())
可以写成key.isInstance(e.getKey())
这些东西很难,但这是我目前的想法:
如果你的 API 返回 Object,那么你就无能为力了——无论如何,你都会盲目地转换对象。你让 Java 抛出 ClassCastExceptions,或者你可以自己检查每个元素并抛出 Assertions 或 IllegalArgumentExceptions 之类的,但这些运行时检查都是等效的。无论您在运行时执行什么操作,都必须禁止编译时未检查的强制转换。
我宁愿盲投并让 JVM 为我执行运行时检查,因为我们“知道”API 应该返回什么,并且通常愿意假设 API 有效。如果需要,请在演员表上方的任何地方使用泛型。你并没有真正在那里购买任何东西,因为你仍然有单一的盲转换,但至少你可以从那里开始使用泛型,这样 JVM 可以帮助你避免其他代码片段中的盲转换。
在这种特殊情况下,大概您可以看到对 SetAttribute 的调用并看到类型正在进入,因此在退出时将类型盲投为 same 并不是不道德的。添加引用 SetAttribute 的注释并完成它。
有两种方法,一种是完全避免标签,另一种使用顽皮但不错的实用方法。
问题是预通用的集合......
我相信经验法则是:“一次投射一个对象”——当试图在泛型世界中使用原始类时,这意味着因为你不知道这个 Map 中有什么<?, ?>(事实上,JVM 甚至可能会发现它甚至不是一个 Map!),当你想到它时,很明显你不能投射它。如果你有一个 Map<String, ?> map2,那么 HashSet<String> keys = (HashSet<String>)map2.keySet() 不会给你警告,尽管这对编译器来说是一种“信仰行为”(因为它可能会变成一个 TreeSet)......但这只是一个单一的信仰行为。
PS 对于像我的第一种方式一样迭代“很无聊”并且“需要时间”的反对意见,答案是“没有痛苦就没有收获”:通用集合保证包含 Map.Entry<String、String>s,而不是其他任何东西。您必须为此保证付费。当系统地使用泛型时,这种支付,很漂亮,采取编码合规性的形式,而不是机器时间!
有一种观点可能会说,你应该将 Eclipse 的设置设置为使这种未经检查的强制转换错误,而不是警告。在这种情况下,您将不得不使用我的第一种方式。
package scratchpad;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;
public class YellowMouse {
// First way
Map<String, String> getHashMapStudiouslyAvoidingSuppressTag(HttpSession session) {
Map<?, ?> theHash = (Map<?, ?>)session.getAttribute("attributeKey");
Map<String, String> yellowMouse = new HashMap<String, String>();
for( Map.Entry<?, ?> entry : theHash.entrySet() ){
yellowMouse.put( (String)entry.getKey(), (String)entry.getValue() );
}
return yellowMouse;
}
// Second way
Map<String, String> getHashMapUsingNaughtyButNiceUtilityMethod(HttpSession session) {
return uncheckedCast( session.getAttribute("attributeKey") );
}
// NB this is a utility method which should be kept in your utility library. If you do that it will
// be the *only* time in your entire life that you will have to use this particular tag!!
@SuppressWarnings({ "unchecked" })
public static synchronized <T> T uncheckedCast(Object obj) {
return (T) obj;
}
}
评论
Esko Luontola 在上面的答案中的 Objects.Unchecked 实用程序函数是避免程序混乱的好方法。
如果你不希望整个方法上的 SuppressWarnings,Java 会强制你把它放在本地方法上。如果需要对成员进行强制转换,则可能导致如下代码:
@SuppressWarnings("unchecked")
Vector<String> watchedSymbolsClone = (Vector<String>) watchedSymbols.clone();
this.watchedSymbols = watchedSymbolsClone;
使用该实用程序要干净得多,并且您在做什么仍然很明显:
this.watchedSymbols = Objects.uncheckedCast(watchedSymbols.clone());
注意:我觉得重要的是要补充一点,有时警告真的意味着你做错了什么,比如:
ArrayList<Integer> intList = new ArrayList<Integer>();
intList.add(1);
Object intListObject = intList;
// this line gives an unchecked warning - but no runtime error
ArrayList<String> stringList = (ArrayList<String>) intListObject;
System.out.println(stringList.get(0)); // cast exception will be given here
编译器告诉您的是,在运行时不会检查此强制转换,因此在您尝试访问泛型容器中的数据之前,不会引发运行时错误。
在 Eclipse 首选项中,转到 Java->Compiler->Errors/Warnings->Generic types 并选中该复选框。Ignore unavoidable generic type problems
这满足了问题的意图,即
我想避免 Eclipse 警告......
如果不是精神。
评论
uses unchecked or unsafe operations.
javac
@SuppressWarnings("unchecked")
javac
解决方案:在 Eclipse 中禁用此警告。不要@SuppressWarnings它,只需完全禁用它。
上面介绍的几个“解决方案”是不合时宜的,为了抑制愚蠢的警告,使代码不可读。
评论
@SuppressWarnings
这是我在重写操作时处理此问题的一种方法。equals()
public abstract class Section<T extends Section> extends Element<Section<T>> {
Object attr1;
/**
* Compare one section object to another.
*
* @param obj the object being compared with this section object
* @return true if this section and the other section are of the same
* sub-class of section and their component fields are the same, false
* otherwise
*/
@Override
public boolean equals(Object obj) {
if (obj == null) {
// this exists, but obj doesn't, so they can't be equal!
return false;
}
// prepare to cast...
Section<?> other;
if (getClass() != obj.getClass()) {
// looks like we're comparing apples to oranges
return false;
} else {
// it must be safe to make that cast!
other = (Section<?>) obj;
}
// and then I compare attributes between this and other
return this.attr1.equals(other.attr1);
}
}
这似乎在 Java 8 中有效(即使编译为-Xlint:unchecked
)
警告抑制不是解决方案。你不应该在一个语句中做两级强制转换。
HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
// first, cast the returned Object to generic HashMap<?,?>
HashMap<?, ?> theHash = (HashMap<?, ?>)session.getAttribute("attributeKey");
// next, cast every entry of the HashMap to the required type <String, String>
HashMap<String, String> returingHash = new HashMap<>();
for (Entry<?, ?> entry : theHash.entrySet()) {
returingHash.put((String) entry.getKey(), (String) entry.getValue());
}
return returingHash;
}
评论
下面是一个简短的示例,它通过采用其他答案中提到的两种策略来避免“未检查的强制转换”警告。
在运行时将感兴趣类型的类作为参数传递 ()。然后,您可以使用:
Class<T> inputElementClazz
inputElementClazz.cast(anyObject);
对于 Collection 的类型转换,请使用通配符 ?而不是泛型类型 T,以确认您确实不知道从遗留代码中可以期望获得什么样的对象 ()。毕竟,这就是“未检查的投射”警告想要告诉我们的:我们不能确定我们得到了一个 ,所以诚实的做法是使用 .如果绝对需要,仍然可以构建已知类型的集合 ()。
Collection<?> unknownTypeCollection
Collection<T>
Collection<?>
Collection<T> knownTypeCollection
以下示例中接口的旧代码在 StructuredViewer 中具有属性“input”(StructuredViewer 是树或表小部件,“input”是其背后的数据模型)。这个“输入”可以是任何类型的 Java 集合。
public void dragFinished(StructuredViewer structuredViewer, Class<T> inputElementClazz) {
IStructuredSelection selection = (IStructuredSelection) structuredViewer.getSelection();
// legacy code returns an Object from getFirstElement,
// the developer knows/hopes it is of type inputElementClazz, but the compiler cannot know
T firstElement = inputElementClazz.cast(selection.getFirstElement());
// legacy code returns an object from getInput, so we deal with it as a Collection<?>
Collection<?> unknownTypeCollection = (Collection<?>) structuredViewer.getInput();
// for some operations we do not even need a collection with known types
unknownTypeCollection.remove(firstElement);
// nothing prevents us from building a Collection of a known type, should we really need one
Collection<T> knownTypeCollection = new ArrayList<T>();
for (Object object : unknownTypeCollection) {
T aT = inputElementClazz.cast(object);
knownTypeCollection.add(aT);
System.out.println(aT.getClass());
}
structuredViewer.refresh();
}
当然,如果我们使用具有错误数据类型的旧代码(例如,如果我们将数组设置为 StructuredViewer 的“输入”而不是 Java 集合),则上述代码可能会产生运行时错误。
调用该方法的示例:
dragFinishedStrategy.dragFinished(viewer, Product.class);
在 Android Studio 中,如果您想停用检查功能,可以使用:
//noinspection unchecked
Map<String, String> myMap = (Map<String, String>) deserializeMap();
上一个:构造函数中的虚拟成员调用
评论
enum
Class<T>