Eclipseは私に次の形式の警告を出しています。
型安全:ObjectからHashMapへの未確認キャスト
これは、Objectを返すAPIを制御できないという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.
ワオ;私は自分の質問に対する答えを考え出したと思います。私はそれが価値があることを確信していません! :)
問題はキャストがチェックされていないことです。だから、あなたは自分でそれをチェックしなければなりません。パラメータ化された型情報は実行時には利用できず、コンパイル時に消去されているため、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を投げる以外に私ができるより良い何かがありますか?私が投げることができるよりよい何かがありますか?チェック例外にするべきですか?
明らかな答えは、もちろん、未チェックのキャストをしないことです。
どうしても必要な場合は、少なくとも@SuppressWarnings
アノテーションの範囲を制限するようにしてください。 Javadocs によると、ローカル変数を使うことができます。このようにして、メソッド全体には影響しません。
例:
@SuppressWarnings("unchecked")
Map<String, String> myMap = (Map<String, String>) deserializeMap();
Map
が本当に総称パラメータ<String, String>
を持つべきかどうかを決定する方法はありません。あなたは事前にパラメータが何であるべきかを知っていなければなりません(あるいはあなたがClassCastException
を手に入れた時にあなたは見つけるでしょう)。これが、コードが警告を生成する理由です。なぜなら、コンパイラはおそらく安全かどうかを知ることができないからです。
残念ながら、ここには素晴らしいオプションはありません。覚えておいて、このすべての目標は型の安全性を維持することです。 " Java Generics "は一般化されていないレガシーライブラリを扱うための解決策を提供します、そして特にセクション8.2で "空ループ技術"と呼ばれるものがあります。基本的に、危険なキャストを行い、警告を抑制します。次に、このように地図をループします。
@SuppressWarnings("unchecked")
Map<String, Number> map = getMap();
for (String s : map.keySet());
for (Number n : map.values());
予期しない型が見つかった場合は、実行時にClassCastExceptionが発生しますが、少なくとも問題の原因に近いところで発生します。
Eclipseの「設定」で、「Java」 - >「コンパイラー」 - >「エラー/警告」 - >「汎用タイプ」に進み、「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 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を返した場合、あなたができることは何もありません - 何に関係なく、あなたは盲目的にオブジェクトをキャストするでしょう。 JavaにClassCastExceptionをスローさせるか、各要素を自分でチェックしてAssertionsやIllegalArgumentExceptionなどをスローすることができますが、これらの runtime チェックはすべて同等です。実行時に何をしても、 コンパイル時間 未チェックキャストを抑制する必要があります。
APIが返すべきものを「知っている」ので、ブラインドキャストしてJVMにランタイムチェックを実行させることをお勧めします。通常、APIが機能すると想定しても構いません。あなたがそれらを必要とするならば、キャストの上のいたるところに総称を使用してください。まだブラインドキャストは1つしかないので、実際には何も購入していませんが、少なくともそこからジェネリックを使用することで、コードの他の部分でJVMがブラインドキャストを回避できるようにすることができます。
この特定のケースでは、おそらくSetAttributeへの呼び出しを見て、型が入ってくるのを見ることができるので、途中で型をブラインドキャストするだけでは不道徳ではありません。 SetAttributeを参照するコメントを追加してください。
HTTPセッションの世界では、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
はありません。
これは短縮された他の答えで言及された2つの戦略を使用することによって "未チェックのキャスト"警告を避ける例です。
実行時に対象の型のクラスをパラメータとして渡します(Class<T> inputElementClazz
)。それからあなたは使うことができます:inputElementClazz.cast(anyObject);
コレクションの型キャストには、ワイルドカードを使用しますか?ジェネリック型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();
}
当然、間違ったデータ型のレガシーコードを使用すると(たとえば、JavaコレクションではなくStructuredViewerの「入力」として配列を設定した場合など)、上記のコードでランタイムエラーが発生することがあります。
メソッド呼び出しの例
dragFinishedStrategy.dragFinished(viewer, Product.class);
この特定のケースでは、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);
}
}
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
コンパイラがあなたに言っているのは、このキャストは実行時にチェックされないので、ジェネリックコンテナのデータにアクセスしようとするまで実行時エラーは発生しません。
Android Studioでは、検査を無効にしたい場合は、次のものを使用できます。
//noinspection unchecked
Map<String, String> myMap = (Map<String, String>) deserializeMap();
警告抑制は解決策ではありません。 1つのステートメントで2レベルのキャストを行うべきではありません。
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;
}
GenericsをサポートしていないAPIを使用する必要がある場合は、可能な限り少ない行数でラッパールーチン内でそれらの呼び出しを分離してみます。次にSuppressWarningsアノテーションを使用し、同時に型安全キャストも追加します。
これは、物事をできるだけきれいに保つための個人的な好みです。
これを考えてみましょう。新しいHashMapを作成するよりもはるかに高速です。既に1つですが、各要素はその型に対してチェックされるため、まだ安全です。
@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);
}
あなたがあなたのコードを投稿するならば、ちょっと推測は確かに言うことができます、しかし、あなたはの線に沿って何かをしたかもしれない
HashMap<String, Object> test = new HashMap();
あなたがする必要があるときこれは警告を生成します
HashMap<String, Object> test = new HashMap<String, Object>();
見る価値があるかもしれません
あなたがしなければならないことになじみがないならば。
私はその質問を誤解したかもしれませんが(例と周囲の数行がいいでしょう)、なぜあなたはいつも適切なインターフェース(そしてJava 5+)を使わないのですか? Map<KeyType,ValueType>
の代わりにHashMap
にキャストしたいと思う理由はありません。実際、 any が変数の型をHashMap
ではなくMap
に設定する理由は想像できません。
そして、なぜソースはObject
なのですか?それはレガシーコレクションのパラメータ型ですか?そうであれば、総称を使用して、必要なタイプを指定してください。
equals()
操作をオーバーライドするときにこれを処理する1つの方法があります。
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
でコンパイルされていても)
コンピュータサイエンスのほとんどすべての問題は、間接的なレベル*を追加することで解決できます。
そのため、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();
}
*多すぎる間接参照レベルを除く。
2つの方法があります。1つはタグを完全に回避する方法で、もう1つはいたずらですがNiceユーティリティ方法を使用する方法です。
問題は事前一般化されたコレクションです...
経験則は、「一度に1つのオブジェクトをキャストする」ということです。一般化された世界で生のクラスを使用しようとするときに意味するのは、この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;
}
}
Session.getAttribute()によって返される型がHashMapであることが確実な場合は、その型に型キャストすることはできませんが、一般的なHashMapの確認のみに頼ってください。
HashMap<?,?> getItems(javax.servlet.http.HttpSession session) {
HashMap<?,?> theHash = (HashMap<?,?>)session.getAttribute("attributeKey");
return theHash;
}
Eclipseはそれから警告を驚かせるでしょう、しかしもちろんこれは実行時エラーにつながることができて、それはデバッグするのが難しい場合があります。このアプローチは、運用上重要ではない状況でのみ使用します。
キャストする前にタイプチェックするだけです。
Object someObject = session.getAttribute("attributeKey");
if(someObject instanceof HashMap)
HashMap<String, String> theHash = (HashMap<String, String>)someObject;
そして、誰かに尋ねる人にとっては、その種類がわからないところでオブジェクトを受け取るのはごく普通のことです。多くのレガシーな「SOA」実装は、常に信頼すべきではないさまざまなオブジェクトを回避します。 (恐怖!)
_ edit _ ポスターの更新に合わせてサンプルコードを1回変更しました。いくつかのコメントに従うと、instanceofは総称でうまく機能しないことがわかります。しかし、外側のオブジェクトを検証するためにチェックを変更することは、コマンドラインコンパイラでうまくいくようです。改訂例を掲載しました。