如何处理未经检查的强制转换警告?

Eclipse给我一个如下形式的警告:

类型安全:未检查从对象转换到HashMap

这是从一个API调用,我无法控制返回对象:

HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
HashMap<String, String> theHash = (HashMap<String, String>)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 HashMap<K,V> should be parameterized.
515095 次浏览

快速猜测一下你是否发布了你的代码,可以肯定地说,但是你可能做了一些类似的事情

HashMap<String, Object> test = new HashMap();

当你需要做的时候,哪个会产生警告

HashMap<String, Object> test = new HashMap<String, Object>();

也许值得一看

Java程序设计语言中的泛型 .

如果你不熟悉需要做什么。

我可能误解了这个问题(一个示例和几行代码就好了),但是为什么您不总是使用合适的接口(和Java5+)呢?我看不出为什么你想要强制转换为HashMap而不是Map<KeyType,ValueType>。事实上,我无法想象任何有理由将变量的类型设置为HashMap而不是Map

为什么源是Object?是遗留集合的参数类型吗?如果是,请使用泛型并指定所需的类型。

当然,最明显的答案是不要进行未经检查的强制转换。

如果绝对有必要,那么至少尝试限制@SuppressWarnings注释的范围。根据它的Javadocs,它可以在局部变量;这样,它甚至不会影响整个方法。

例子:

@SuppressWarnings("unchecked")
Map<String, String> myMap = (Map<String, String>) deserializeMap();

没有办法确定Map是否真的应该有通用参数<String, String>。您必须事先知道参数应该是什么(否则当您得到ClassCastException时就会知道)。这就是代码生成警告的原因,因为编译器不可能知道是否安全。

在强制转换之前检查一下。

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"));
}
这里有更多关于这个问题的讨论: # EYZ0 < / p >

在这种特殊情况下,我不会将Maps直接存储到HttpSession中,而是将我自己的类的实例存储到HttpSession中,而该类的实例又包含一个Map(类的实现细节)。然后可以确定映射中的元素是正确的类型。

但是如果你想检查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 Session世界中,您无法真正避免强制转换,因为API就是这样编写的(只接受并返回Object)。

不过,只要稍加努力,你就可以很容易地避免这种未经检查的转换。”这意味着它将变成一个传统的强制转换,在发生错误时给出一个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要快得多,如果它已经是一个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);
}

这很难,但以下是我目前的想法:

如果你的API返回Object,那么你什么也做不了——无论如何,你都将盲目地强制转换对象。您可以让Java抛出classcastexception,或者您可以自己检查每个元素并抛出断言或illegalargumentexception或诸如此类的东西,但这些运行时检查都是等效的。无论您在运行时做什么,都必须抑制编译时未检查的强制转换。

我只是更喜欢盲强制转换,让JVM为我执行它的运行时检查,因为我们“知道”API应该返回什么,并且通常愿意假设API工作。如果需要,可以在类型转换上方的任何地方使用泛型。您实际上并没有购买任何东西,因为您仍然拥有单一的盲强制转换,但至少您可以从这里开始使用泛型,因此JVM可以帮助您避免在代码的其他部分使用盲强制转换。

在这个特殊的例子中,假设您可以看到对SetAttribute的调用,并看到进入的类型,因此在退出时将类型盲强制转换为same并不是不道德的。添加一个引用SetAttribute的注释并完成它。

有两种方法,一种是完全避免标签,另一种是使用一个顽皮但漂亮的实用方法 问题是预泛型集合…
我相信经验法则是:“一次投射一个对象”——这意味着当你试图在泛型世界中使用原始类时,因为你不知道这个Map<?,比;(事实上,JVM甚至可能发现它甚至不是一个Map!),当您考虑它时,很明显您不能强制转换它。如果你有一个Map<String, ?>map2然后HashSet<keys = (HashSet<String>)map2.keySet()不会给你一个警告,尽管这是编译器的“信念行为”(因为它可能会变成一个TreeSet)…但这只是信念的行动
PS反对我的第一种方法迭代“很无聊”和“需要时间”,答案是“没有痛苦就没有收获”:一个泛型集合保证包含Map.Entry<String, String>s,没有其他的。你必须为这项担保付款。当系统地使用泛型时,这种支付非常漂亮,采用编码遵从的形式,而不是机器时间!< BR > 一种观点可能会说,您应该将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上面回答的未检查的实用函数是避免程序混乱的好方法。

如果您不希望在整个方法上使用SuppressWarnings, Java会强制您将其放在本地方法上。如果你需要对一个成员进行强制转换,可能会导致这样的代码:

@SuppressWarnings("unchecked")
Vector<String> watchedSymbolsClone = (Vector<String>) watchedSymbols.clone();
this.watchedSymbols = watchedSymbolsClone;

使用这个实用程序要干净得多,而且你所做的事情仍然很明显:

this.watchedSymbols = Objects.uncheckedCast(watchedSymbols.clone());

<强> # EYZ0 我觉得有必要补充一下,有时候这个警告真的意味着你做错了什么,比如:

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->编译器->错误/警告->泛型类型,并选中Ignore unavoidable generic type problems复选框。

这满足了问题的意图,即。

我希望避免Eclipse警告……

如果不是精神。

解决方案:在Eclipse中禁用此警告。不要@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;
}

以下是通过使用其他答案中提到的两种策略来缩短的避免“unchecked cast”警告的示例

  1. 在运行时将感兴趣的类型的Class作为参数传递(Class<T> inputElementClazz)。然后你可以使用:inputElementClazz.cast(anyObject);

  2. 对于集合的类型转换,使用通配符?而不是泛型类型T,以承认您确实不知道从遗留代码中期望得到什么样的对象(Collection<?> unknownTypeCollection)。毕竟,这就是“unchecked cast”警告想要告诉我们的:我们不能确定我们得到的是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 Collection),上面的代码就会给出运行时错误。

调用方法的例子:

dragFinishedStrategy.dragFinished(viewer, Product.class);

在Android Studio中,如果你想禁用检查,你可以使用:

//noinspection unchecked
Map<String, String> myMap = (Map<String, String>) deserializeMap();