Eclipse给我以下形式的警告:
类型安全性:未经检查的从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警告,因为从理论上讲,它们至少表明潜在的代码问题。 不过,我还没有找到消除这种情况的好方法。 我可以将涉及到的单行本身提取到一个方法中,并在该方法中添加@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.
#1楼
解决方案:在Eclipse中禁用此警告。 不要@SuppressWarnings它,只需完全禁用它即可。
上面介绍的几个“解决方案”与众不同,为了抑制愚蠢的警告,使代码不可读。
#2楼
这是我覆盖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
编译)
#3楼
警告抑制不是解决方案。 您不应该在一项声明中进行两级转换。
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;
}
#4楼
如果您确定session.getAttribute()返回的类型是HashMap,则不能将其类型转换为该确切类型,而只能依靠检查常规HashMap
HashMap<?,?> getItems(javax.servlet.http.HttpSession session) {
HashMap<?,?> theHash = (HashMap<?,?>)session.getAttribute("attributeKey");
return theHash;
}
Eclipse将使警告出乎意料,但这当然会导致运行时错误,这些错误很难调试。 我仅在非关键操作环境中使用此方法。
#5楼
这是一个简化的示例,它通过采用其他答案中提到的两种策略来避免“未经检查的演员表”警告 。
在运行时将感兴趣类型的Class传递为参数(
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);
#6楼
这使警告消失了...
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;
}
#7楼
拿这个,它比创建一个新的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);
}
#8楼
在Android Studio中,如果您要禁用检查,可以使用:
//noinspection unchecked
Map<String, String> myMap = (Map<String, String>) deserializeMap();
#9楼
如果您发布代码,可以快速猜测一下,虽然可以肯定地说,但是您可能已经做了一些事情,例如
HashMap<String, Object> test = new HashMap();
当您需要这样做时会产生警告
HashMap<String, Object> test = new HashMap<String, Object>();
也许值得一看
如果您不熟悉需要做什么。
#10楼
我可能误解了这个问题(一个示例和一些周围的线条会很好),但是为什么不总是使用适当的接口(和Java5 +)呢? 我认为没有理由为什么您要转换为HashMap
而不是Map<KeyType,ValueType>
。 实际上,我无法想象有任何理由将变量的类型设置为HashMap
而不是Map
。
为什么源是Object
? 它是旧式集合的参数类型吗? 如果是这样,请使用泛型并指定所需的类型。
#11楼
当然,显而易见的答案是不要进行未经检查的转换。
如果绝对必要,则至少尝试限制@SuppressWarnings
批注的范围。 根据其Javadocs ,它可以使用局部变量。 这样,它甚至不会影响整个方法。
例:
@SuppressWarnings("unchecked")
Map<String, String> myMap = (Map<String, String>) deserializeMap();
无法确定Map
是否确实应具有通用参数<String, String>
。 您必须事先知道参数应该是什么(否则您将在获取ClassCastException
时查明)。 这就是代码生成警告的原因,因为编译器可能无法知道是否安全。
#12楼
只需在投放前进行类型检查即可。
Object someObject = session.getAttribute("attributeKey");
if(someObject instanceof HashMap)
HashMap<String, String> theHash = (HashMap<String, String>)someObject;
对于任何询问的人,接收不确定类型的对象都是很常见的。 许多传统的“ SOA”实现绕过了您不应该始终信任的各种对象。 (恐怖!)
编辑一次更改了示例代码,以匹配发布者的更新,并且在进行了一些注释之后,我发现instanceof与泛型不能很好地配合使用。 但是,更改检查以验证外部对象似乎可以在命令行编译器中很好地发挥作用。 现在发布修改后的示例。
#13楼
哇; 我想我已经找到了我自己问题的答案。 我只是不确定是否值得! :)
问题是没有检查演员表。 因此,您必须自己检查它。 您不能只使用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之外,我还能做些更好的事情吗? 有什么更好的我可以抛出的吗? 我应该将其设为检查异常吗?
#14楼
不幸的是,这里没有很好的选择。 请记住,所有这些的目的是保持类型安全。 “ Java泛型 ”提供了一种用于处理非泛型遗留库的解决方案,第8.2节中特别提到了一种“空循环技术”。 基本上,进行不安全的投射,并抑制警告。 然后像这样遍历地图:
@SuppressWarnings("unchecked")
Map<String, Number> map = getMap();
for (String s : map.keySet());
for (Number n : map.values());
如果遇到意外类型,则将获得运行时ClassCastException
,但至少它会在问题根源附近发生。
#15楼
如果我必须使用不支持泛型的API。我尝试以尽可能少的行将这些调用隔离在包装程序中。 然后,我使用SuppressWarnings批注,并同时添加类型安全强制类型转换。
这只是使事情保持整洁的个人喜好。
#16楼
您可以创建如下所示的实用程序类,并使用它来抑制未选中的警告。
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
#17楼
在这种情况下,我不会将Maps直接存储到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);
}
}
#18楼
计算机科学中的几乎所有问题都可以通过添加一定程度的间接*来解决。
因此,引入一个比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();
}
*除了太多级别的间接。
#19楼
在HTTP会话世界中,您真的不能避免强制类型转换,因为API是以这种方式编写的(仅接收和返回Object
)。
通过一点点的工作,您就可以轻松避免未经检查的演员。 这意味着它将转换为传统的ClassCastException
,以便在发生错误时在此处提供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
。
#20楼
这些东西很难,但是这是我目前的想法:
如果您的API返回对象,那么您将无能为力-无论如何,您将盲目地投射对象。 您可以让Java抛出ClassCastExceptions,或者您可以自己检查每个元素并抛出Assertions或IllegalArgumentExceptions或类似的东西,但是这些运行时检查都是等效的。 无论您在运行时做什么,都必须取消取消选中的编译时间 。
我只希望盲目转换,让JVM为我执行运行时检查,因为我们“知道” API应该返回什么,并且通常愿意假定API可以工作。 如果需要,请在演员表上方的各处使用泛型。 由于您仍然只进行盲目转换,因此您并没有真正在此购买任何东西,但是至少您可以从那里开始使用泛型,因此JVM可以帮助您避免在代码的其他部分中盲目转换。
在这种特殊情况下,大概可以看到对SetAttribute的调用并看到类型即将传入,因此在输出时将类型盲目转换为相同类型并不是不道德的。 添加引用SetAttribute的注释并完成它。
#21楼
有两种方法,一种完全避免使用标签,另一种则使用顽皮但实用的实用程序方法。
问题是预先生成的集合...
我相信经验法则是:“一次将对象投射一件事”-在通用环境中尝试使用原始类时,这意味着因为您不知道Map <?,?>中的内容(实际上,JVM甚至可能甚至发现它甚至都不是Map!),当您考虑它时就很明显无法进行转换。 act of faith. 如果您有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;
}
}
#22楼
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
编译器告诉您的是,在运行时不会检查此强制转换,因此在尝试访问通用容器中的数据之前,不会引发运行时错误。
#23楼
在Eclipse首选项中,转到Java->编译器->错误/警告->通用类型,然后选中Ignore unavoidable generic type problems
复选框。
这满足了问题的意图,即
我想避免Eclipse警告...
如果没有精神。