Eclipse正在向我发出以下形式的警告:
类型安全:从Object到HashMap的未选中转换
这是来自对我无法控制的API的调用返回Object:
HashMapgetItems(javax.servlet.http.HttpSession session) { HashMap theHash = (HashMap )session.getAttribute("attributeKey"); return theHash; }
如果可能的话,我想避免Eclipse警告,因为理论上它们至少表明存在潜在的代码问题.但是,我还没有找到一种消除这种方法的好方法.我可以将涉及的单行提取到方法中并添加@SuppressWarnings("unchecked")
到该方法,从而限制了我忽略警告的代码块的影响.有更好的选择吗?我不想在Eclipse中关闭这些警告.
在我来到代码之前,它更简单,但仍然引发了警告:
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 HashMapshould be parameterized.
Michael Myer.. 534
当然,显而易见的答案是不做无节制的演员.
如果这是绝对必要的,那么至少要尝试限制@SuppressWarnings
注释的范围.根据它的Javadocs,它可以继续局部变量; 这样,它甚至不会影响整个方法.
例:
@SuppressWarnings("unchecked") MapmyMap = (Map ) deserializeMap();
无法确定是否Map
真的应该具有通用参数
.你必须事先知道参数应该是什么(或者你得到的时候会发现ClassCastException
).这就是代码生成警告的原因,因为编译器不可能知道是否安全.
当然,显而易见的答案是不做无节制的演员.
如果这是绝对必要的,那么至少要尝试限制@SuppressWarnings
注释的范围.根据它的Javadocs,它可以继续局部变量; 这样,它甚至不会影响整个方法.
例:
@SuppressWarnings("unchecked") MapmyMap = (Map ) deserializeMap();
无法确定是否Map
真的应该具有通用参数
.你必须事先知道参数应该是什么(或者你得到的时候会发现ClassCastException
).这就是代码生成警告的原因,因为编译器不可能知道是否安全.
不幸的是,这里没有很好的选择.请记住,所有这一切的目标是保持类型安全." Java Generics "提供了处理非泛化遗留库的解决方案,在8.2节中有一个特别称为"空循环技术".基本上,进行不安全的转换,并禁止警告.然后循环遍历地图,如下所示:
@SuppressWarnings("unchecked") Mapmap = getMap(); for (String s : map.keySet()); for (Number n : map.values());
如果遇到意外类型,您将获得运行时ClassCastException,但至少它会发生在问题的根源附近.
哇; 我想我找到了自己问题的答案.我只是不确定它是否值得!:)
问题是没有检查演员表.所以,你必须自己检查一下.您不能只使用instanceof检查参数化类型,因为参数化类型信息在运行时不可用,已在编译时擦除.
但是,您可以使用instanceof对哈希中的每个项目执行检查,这样,您就可以构造一个类型安全的新哈希.而且你不会引发任何警告.
感谢mmyers和Esko Luontola,我已经参数化了我最初在这里编写的代码,因此它可以包含在某个实用程序类中并用于任何参数化的HashMap.如果你想更好地理解它并且不熟悉泛型,我鼓励查看这个答案的编辑历史.
public staticHashMap castHash(HashMap input, Class keyClass, Class valueClass) { HashMap output = new HashMap (); 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之外,我还能做些什么吗?我能抛出更好的东西吗?我应该将其设为已检查的例外吗?
在Eclipse Preferences中,转到Java-> Compiler-> Errors/Warnings-> Generic types并选中Ignore unavoidable generic type problems
复选框.
这满足了问题的意图,即
我想避免Eclipse警告......
如果不是精神.
您可以创建如下所示的实用程序类,并使用它来禁止未经检查的警告.
public class Objects { /** * Helps to avoid using {@code @SuppressWarnings({"unchecked"})} when casting to a generic type. */ @SuppressWarnings({"unchecked"}) public staticT uncheckedCast(Object obj) { return (T) obj; } }
您可以按如下方式使用它:
import static Objects.uncheckedCast; ... HashMapgetItems(javax.servlet.http.HttpSession session) { return uncheckedCast(session.getAttribute("attributeKey")); }
关于这方面的更多讨论在这里:http: //cleveralias.blogs.com/thought_spearmints/2006/01/suppresswarning.html
这个东西很难,但这是我目前的想法:
如果您的API返回Object,那么您无能为力 - 无论如何,您将盲目地投射该对象.您让Java抛出ClassCastExceptions,或者您可以自己检查每个元素并抛出Assertions或IllegalArgumentExceptions等等,但这些运行时检查都是等效的.无论你在运行时做什么,都必须禁止编译时未经检查的转换.
我只是喜欢盲目转换,让JVM为我执行运行时检查,因为我们"知道"API应该返回什么,并且通常愿意假设API有效.如果需要,可以在演员阵容上方的任何地方使用泛型.你并没有真正购买任何东西,因为你仍然有单盲,但至少你可以从那里使用泛型,所以JVM可以帮助你避免代码中其他部分的盲目转换.
在这种特殊情况下,大概你可以看到对SetAttribute的调用并看到类型正在进入,所以在出路时盲目地将类型转换为相同并不是不道德的.添加引用SetAttribute的注释并完成它.
在HTTP Session世界中,您无法真正避免强制转换,因为API是以这种方式编写的(只接受并返回Object
).
但是,通过一些工作,你可以轻松地避免无节制的演员阵容.这意味着它将变成一个传统的演员ClassCastException
,在发生错误的情况下在那里给予权利).未经检查的异常可能会CCE
在以后的任何时候转变为a 而不是转换点(这就是为什么它是一个单独的警告).
用专用类替换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{ final Map content = new HashMap (); @Override public Set > entrySet() { return content.entrySet(); } @Override public Set keySet() { return content.keySet(); } @Override public Collection values() { return content.values(); } @Override public String put(final String key, final String value) { return content.put(key, value); } }
然后转换到该类而不是,Map
并在您编写代码的确切位置检查所有内容.ClassCastExceptions
以后没有意外.
这是一个缩短的示例,通过采用其他答案中提到的两种策略来避免"未经检查的强制转换"警告.
在运行时(Class
)传递感兴趣类型的Class作为参数.然后你可以使用:inputElementClazz.cast(anyObject);
对于Collection的类型转换,请使用通配符?而不是通用类型T,以确认您确实不知道遗留代码(Collection> unknownTypeCollection
)期望什么类型的对象.毕竟,这就是"未经检查的演员"警告要告诉我们的事情:我们不能确定我们得到了一个Collection
,所以诚实的做法是使用一个Collection>
.如果绝对需要,仍然可以构建已知类型的集合(Collection
).
以下示例中的遗留代码接口在StructuredViewer中具有属性"input"(StructuredViewer是树或表小部件,"input"是其背后的数据模型).这个"输入"可以是任何类型的Java Collection.
public void dragFinished(StructuredViewer structuredViewer, ClassinputElementClazz) { 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 knownTypeCollection = new ArrayList (); for (Object object : unknownTypeCollection) { T aT = inputElementClazz.cast(object); knownTypeCollection.add(aT); System.out.println(aT.getClass()); } structuredViewer.refresh(); }
当然,如果我们使用具有错误数据类型的遗留代码(例如,如果我们将数组设置为StructuredViewer的"输入"而不是Java Collection),则上面的代码可能会给出运行时错误.
调用方法的示例:
dragFinishedStrategy.dragFinished(viewer, Product.class);
在这种特殊情况下,我不会直接将Maps存储到HttpSession中,而是将我自己的类的实例存储起来,而后者又包含一个Map(类的实现细节).然后,您可以确保地图中的元素属于正确的类型.
但是如果你想要检查Map的内容是否是正确的类型,你可以使用这样的代码:
public static void main(String[] args) { Mapmap = new HashMap (); map.put("a", 1); map.put("b", 2); Object obj = map; Map ok = safeCastMap(obj, String.class, Integer.class); Map error = safeCastMap(obj, String.class, String.class); } @SuppressWarnings({"unchecked"}) public static Map safeCastMap(Object map, Class keyType, Class valueType) { checkMap(map); checkMapContents(keyType, valueType, (Map, ?>) map); return (Map ) map; } private static void checkMap(Object map) { checkType(Map.class, map); } private static void checkMapContents(Class keyType, Class valueType, Map, ?> map) { for (Map.Entry, ?> entry : map.entrySet()) { checkType(keyType, entry.getKey()); checkType(valueType, entry.getValue()); } } private static void checkType(Class expectedType, Object obj) { if (!expectedType.isInstance(obj)) { throw new IllegalArgumentException("Expected " + expectedType + " but was " + obj.getClass() + ": " + obj); } }
在Android Studio中,如果要禁用检查,可以使用:
//noinspection unchecked MapmyMap = (Map ) deserializeMap();
Esko Luontola上面的答案中的Objects.Unchecked实用函数是避免程序混乱的好方法.
如果您不想在整个方法上使用SuppressWarnings,Java会强制您将其放在本地方法上.如果您需要对成员进行强制转换,则可以使用以下代码:
@SuppressWarnings("unchecked") VectorwatchedSymbolsClone = (Vector ) watchedSymbols.clone(); this.watchedSymbols = watchedSymbolsClone;
使用该实用程序要清晰得多,而且您正在做的事情仍然很明显:
this.watchedSymbols = Objects.uncheckedCast(watchedSymbols.clone());
注意: 我觉得重要的是,有时警告确实意味着你做错了,比如:
ArrayListintList = new ArrayList (); intList.add(1); Object intListObject = intList; // this line gives an unchecked warning - but no runtime error ArrayList stringList = (ArrayList ) intListObject; System.out.println(stringList.get(0)); // cast exception will be given here
编译器告诉您的是,在运行时不会检查此强制转换,因此在您尝试访问通用容器中的数据之前不会引发运行时错误.
警告抑制不是解决方案。您不应该在一项声明中进行两级转换。
HashMapgetItems(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 HashMap returingHash = new HashMap<>(); for (Entry, ?> entry : theHash.entrySet()) { returingHash.put((String) entry.getKey(), (String) entry.getValue()); } return returingHash; }