我如何解决未经检查的投射警告?
Eclipse给了我一个以下forms的警告:
types安全性:取消勾选从Object到HashMap
这是从一个API调用,我无法控制哪个返回对象:
HashMap<String, String> getItems(javax.servlet.http.HttpSession session) { HashMap<String, String> theHash = (HashMap<String, String>)session.getAttribute("attributeKey"); return theHash; }
我想避免Eclipse的警告,如果可能的话,理论上他们至less指出一个潜在的代码问题。 但是,我还没有find消除这个问题的好方法。 我可以将涉及的单行解压缩到一个方法本身,并将@SuppressWarnings("unchecked")
到该方法,从而限制了在忽略警告的情况下创build代码块的影响。 有更好的select吗? 我不想在Eclipse中closures这些警告。
在我来到代码之前,这是更简单的,但仍然引发警告:
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.
当然,显而易见的答案不是不加限制地施展。
如果这是绝对必要的,那么至less试着限制@SuppressWarnings
注释的范围。 根据其Javadocs ,它可以继续使用局部variables; 这样,它甚至不影响整个方法。
例:
@SuppressWarnings("unchecked") Map<String, String> myMap = (Map<String, String>) deserializeMap();
无法确定Map
是否应该具有通用参数<String, String>
。 你必须事先知道参数应该是什么(或者你会发现什么时候得到一个ClassCastException
)。 这就是代码生成警告的原因,因为编译器不可能知道是否安全。
哇; 我想我想出了自己的问题的答案。 我只是不确定这是值得的! 🙂
问题是铸件没有被检查。 所以,你必须自己检查一下。 您不能仅仅使用instanceof检查参数化types,因为参数化types信息在运行时不可用,在编译时已经被擦除。
但是,您可以使用instanceof对散列中的每个项目执行一次检查,这样可以构造一个types安全的新散列。 而且你不会引起任何警告。
感谢mmyers和Esko Luontola,我已经参数化了我最初在这里编写的代码,所以它可以被包装在某个实用程序类中,并用于任何参数化的HashMap。 如果你想更好地理解它,并且不太熟悉generics,我鼓励查看这个答案的编辑历史。
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; }
这是很多工作,可能只有很less的奖励…我不知道我是否会使用它。 对于是否有人认为这是值得的,我将不胜感激。 另外,我会感激改善build议:有什么更好的,我可以做除了抛出断言错误? 有什么更好的,我可以扔? 我应该使它成为一个检查exception?
不幸的是,这里没有很好的select。 请记住,所有这些的目标是保持types安全。 “ Javagenerics ”为处理非generics化遗留库提供了一个解决scheme,在8.2节中有一个特别称为“空循环技术”。 基本上,做不安全的演员,并压制警告。 然后像这样循环浏览地图:
@SuppressWarnings("unchecked") Map<String, Number> map = getMap(); for (String s : map.keySet()); for (Number n : map.values());
如果遇到意外的types,您将得到一个运行时ClassCastException,但至less它会发生接近问题的来源。
在Eclipse首选项中,转到Java-> Compiler-> Errors / Warnings-> Generic types,并选中Ignore unavoidable generic type problems
checkbox。
这满足了这个问题的意图,即
我想避免Eclipse的警告…
如果不是精神的话。
您可以像下面这样创build一个实用程序类,并使用它来抑制未经检查的警告。
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
这东西很难,但这是我目前的想法:
如果你的API返回Object,那么你就无能为力了 – 不pipe怎么样,你都会盲目地投射物体。 您让Java抛出ClassCastExceptions,或者您可以自己检查每个元素并抛出Assertions或IllegalArgumentExceptions等,但是这些运行时检查都是等价的。 不pipe你在运行时做什么,你都必须抑制编译时间的未经检查的转换。
我只想盲目地投射,并让JVM为我执行运行时检查,因为我们“知道”什么API应该返回,并且通常愿意假定API工作。 如果你需要的话,可以在演员身上的任何地方使用generics。 你并不是真的在那里购买任何东西,因为你仍然有单盲,但至less你可以从那里使用generics,所以JVM可以帮助你避免在其他代码中盲目的投射。
在这个特殊情况下,大概你可以看到对SetAttribute的调用,并且看到types正在进入,所以只是盲目地将types转换为相同的方式并不是不道德的。 添加一个引用SetAttribute的注释并完成它。
在HTTP Session世界中,你不能真正避免强制转换,因为API是以这种方式编写的(只需要返回Object
)。
但是,通过一点点的工作,你就可以轻松地避免不加控制的演员。 这意味着它会变成一个传统的ClassCastException
,在出现错误的情况下给出ClassCastException
)。 一个未经检查的exception可能在之后的任何时刻变成CCE
,而不是演员的angular色(这就是为什么它是一个单独的警告)。
用专门的类replaceHashMap:
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
稍后。
在这种特殊情况下,我不会直接将地图存储到HttpSession中,而是将我自己的类的实例存储到Map中(该类的实现细节)。 然后你可以确定地图中的元素是正确的types。
但是,如果你想检查地图的内容是正确的types,你可以使用这样的代码:
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); } }
这是一个简短的例子,通过采用其他答案中提到的两种策略来避免“未经检查的强制转换”警告 。
-
在运行时(
Class<T> inputElementClazz
)将感兴趣types的类作为参数Class<T> inputElementClazz
。 然后你可以使用:inputElementClazz.cast(anyObject);
-
对于集合的types转换,使用通配符? 而不是genericstypesT来确认您确实不知道遗留代码(
Collection<?> unknownTypeCollection
)期望什么types的对象。 毕竟,这是“unchecked cast”警告想要告诉我们的:我们不能确定我们得到了一个Collection<T>
,因此诚实的做法是使用一个Collection<?>
。 如果绝对需要,仍然可以构build一个已知types的集合(Collection<T> knownTypeCollection
)。
在下面的例子中接口的遗留代码在StructuredViewer中有一个属性“input”(StructuredViewer是一个树或者表格部件,“input”是它后面的数据模型)。 这个“input”可以是任何types的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(); }
自然,如果我们使用具有错误数据types的遗留代码(例如,如果将数组设置为StructuredViewer的“input”而不是Java集合),那么上面的代码可能会给运行时错误。
调用方法的示例:
dragFinishedStrategy.dragFinished(viewer, Product.class);
警告抑制不是一个解决scheme。 你不应该在一个声明中做两个级别的铸造。
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; }
Esko Luontola上面的答案中的Objects.Unchecked实用程序function是避免程序混乱的好方法。
如果你不想在整个方法上使用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
编译器告诉你的是,这个强制转换不会在运行时被检查,所以在你尝试访问通用容器中的数据之前,不会产生运行时错误。
如果你发布你的代码,可以肯定的说,但你可能已经做了一些事情了
HashMap<String, Object> test = new HashMap();
当你需要的时候会产生警告
HashMap<String, Object> test = new HashMap<String, Object>();
这可能值得一看
Java编程语言中的generics
如果你不熟悉需要做什么。
我可能误解了这个问题(一个例子和一些周围的线会很好),但为什么不总是使用适当的接口(和Java5 +)? 我看不出为什么你会想要转换为HashMap
而不是Map<KeyType,ValueType>
。 事实上,我无法想象任何理由将variables的types设置为HashMap
而不是Map
。
为什么源是一个Object
? 它是传统集合的参数types吗? 如果是这样,请使用generics并指定所需的types。
如果我不得不使用一个不支持generics的API ..我尝试用尽可能less的行将这些调用隔离在包装例程中。 然后我使用SuppressWarnings注释,并同时添加types安全性转换。
这只是个人喜好,要尽可能保持整洁。
拿这个,比创build一个新的HashMap快得多,如果它已经是一个,但仍然是安全的,因为每个元素都是针对它的types进行检查的。
@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); }
只要在施放之前检查一下。
Object someObject = session.getAttribute("attributeKey"); if(someObject instanceof HashMap) HashMap<String, String> theHash = (HashMap<String, String>)someObject;
而对于任何人来说,在你不确定types的地方接收对象是很常见的。 许多传统的“SOA”实现传递给你不应该总是信任的各种对象。 (恐怖!)
编辑更改了示例代码一次,以配合海报的更新,以下的一些评论我看到,instanceof不能很好地与generics玩。 不过,改变检查来validation外部对象似乎与命令行编译器很好地发挥。 现在修改的例子已经发布。
几乎在计算机科学中的每一个问题都可以通过添加一个间接*或某事来解决。
因此,引入一个更高级别的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(); }
*除了太多的间接程度。
这是我重写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
编译-Xlint:unchecked
)
问题出在这里:
... = (HashMap<String, String>)session.getAttribute("attributeKey");
session.getAttribute(...)
的结果是一个可以是任何东西的object
,但是因为你“知道”它是一个HashMap<String, String>
你只是在没有首先检查的情况下投射。 因此,警告。 为了迂回,在这种情况下,Java需要你,你应该检索结果并validation它与instanceof
的兼容性。
如果您确定session.getAttribute()返回的types是HashMap,那么您不能强制转换为该types,而是仅依赖于检查genericsHashMap
HashMap<?,?> getItems(javax.servlet.http.HttpSession session) { HashMap<?,?> theHash = (HashMap<?,?>)session.getAttribute("attributeKey"); return theHash; }
然后,Eclipse会发出警告,但是这当然会导致运行时错误,难以debugging。 我只在非操作关键的环境中使用这种方法。
有两种方法,一种是完全避免标签,另一种是使用顽皮但不错的实用方法。
问题是预先通用的集合…
我相信经验法则是:“一次抛出一个对象” – 当在一个通用化的世界中尝试使用原始类时,这意味着什么,因为你不知道这个Map中有什么<?,?>甚至JVM甚至可能会发现它甚至不是一个Map!),当你考虑到这个时候你就不能施展它了。 如果你有一个Map <String,?> map2,那么HashSet <String> keys =(HashSet <String>)map2.keySet()不会给你一个警告,尽pipe这是编译器的“信仰行为”它可能会变成TreeSet),但它只是一个单一的信念行为。
PS的反对意见,迭代如我的第一种方式是“无聊”和“需要时间”,答案是“不痛不痒”:一个通用集合保证包含Map.Entry <String,String> s,并没有其他。 你必须支付这个保证。 在系统地使用generics这个支付时,精美地采取编码合规的forms,而不是机器时间!
一派思想可能会说,你应该设置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; } }
在Android Studio中,如果要禁用检查,可以使用:
//noinspection unchecked Map<String, String> myMap = (Map<String, String>) deserializeMap();
这使警告消失…
static Map<String, String> getItems(HttpSession session) { HashMap<?, ?> theHash1 = (HashMap<String,String>)session.getAttribute("attributeKey"); HashMap<String,String> theHash = (HashMap<String,String>)theHash1; return theHash; }
Solution: Disable this warning in Eclipse. Don't @SuppressWarnings it, just disable it completely.
Several of the "solutions" presented above are way out of line, making code unreadable for the sake of suppressing a silly warning.