2016-07-10 4 views
4

私は100%型の安全なコレクションを作成しようとしています。私が苦労している唯一の問題はこれだけです。マップから実行時エラー多型コレクションのJavaコンパイル時の型チェック

special value: 100 running value: false 

なし

AttributeMap map = new AttributeMap(); 

    map.put("special", 100); 

    map.put("running", false); 

    int special = map.get("special"); 

    boolean running = map.get("running"); 

    System.out.println("special value: " + special + " running value: " + running); 

    // not caught at compilation time, caught at run-time 
    boolean test = map.get("special"); 

    // caught at compilation time 
    boolean test2 = map.get("special", Integer.class); 

出力値は、私が選択した値になります。例specialは型整数でなければなりません。なぜなら、これはコンパイル時にこのエラーをチェックして実行時エラーにならないようにするためです。

このコードを投稿する前に、これは過度に複雑に見えますが、なぜこれを行うだけではないのかと聞くかもしれませんか?

private Map<Object, Object> attributes = new HashMap<>(); 

はい、それは私がやっているものと同じことをするだろうが、それはコンパイル時にすべてのキャストをキャッチするために失敗しました。私は値として入れて同じ型としてそれを取得する私のタイプを追跡しようとしているので、コンパイル時にキャッチすることができます。

ここまでは私のクラスです。私はこれを行うことにより、この問題をキャッチすることができます

のattributeMap

package com.vltr.collection.attr; 

import java.util.Collection; 
import java.util.HashMap; 
import java.util.HashSet; 
import java.util.Map; 
import java.util.Map.Entry; 
import java.util.Set; 

/** 
* A specialized {@link Map} that ensures type safely upon a generic map. 
* 
* @author Vult-R 
*/ 
public final class AttributeMap { 

/** 
* The map contains attributes. 
*/ 
private Map<AttributeKey<?, ?>, Object> attributes; 

private Set<AttributeKey<?, ?>> set = new HashSet<>(); 

/** 
* Creates a new {@link AttributeMap}. 
* 
* @param attributes 
*   The map of attributes. 
*/ 
public AttributeMap(Map<AttributeKey<?, ?>, Object> attributes) { 
    this.attributes = attributes; 
} 

/** 
* Creates an empty {@link AttributeMap}. 
*/ 
public AttributeMap() { 
    this.attributes = new HashMap<AttributeKey<?, ?>, Object>(); 
} 

/** 
* Places a new {@link AttributeKey} into the map. 
* 
* @param key 
*   The key to be used. 
* 
* @param value 
*   The value to map. 
* 
* @param clazz 
*   The class type associated between the key and value. 
*/ 
@SuppressWarnings("unchecked") 
public <K, V> void put(K key, V value) { 
    put(new AttributeKey<K, V>(key, (Class<V>) value.getClass()), value); 
} 

/** 
* A wrapper function for placing a new {@link AttributeKey} into the map. 
* 
* @param key 
*   The key to be used. 
* 
* @param value 
*   The value to map. 
* 
* @param clazz 
*   The class type associated between the key and value. 
*/ 
private <K, V> void put(AttributeKey<K, V> key, V value) { 
    attributes.put(key, value); 
    set.add(key); 
} 

/** 
* A wrapper function for retrieving a value. 
* 
* @param key 
*   The key mapped to a value. 
* 
* @throws AttributeException 
*    If an error occurs while trying to retrieve a value. 
* 
* @return The associated value. 
*/ 
private <K, V> V get(AttributeKey<K, V> key) throws AttributeException { 

    V type = null; 

    AttributeKey<K, V> k = getFromSet(key); 

    try { 
     type = (V) key.getClazz().cast(attributes.get(key)); 
    } catch (ClassCastException ex) { 
     throw new AttributeException(key, attributes.get(key).getClass()); 
    } 

    if (key.getClazz() != k.getClazz()) { 
     System.out.println("not the same");   
    } 

    return type; 

} 

/** 
* Gets a value for retrieving a value 
* 
* @param key 
*   The key mapped to a value. 
* 
* @param clazz 
*   The class type associated between the key and value. 
* 
* @throws AttributeException 
*    If an error occurs while trying to retrieve a value. 
* 
* @return The associated value. 
*/ 
public <K, V> V get(K key, Class<V> clazz) { 
    return get(new AttributeKey<K, V>(key, clazz)); 
} 

/** 
* Gets a value for retrieving a value 
* 
* @param key 
*   The key mapped to a value. 
* 
* @param clazz 
*   The class type associated between the key and value. 
* 
* @throws AttributeException 
*    If an error occurs while trying to retrieve a value. 
* 
* @return The associated value. 
*/ 
public <K, V> V get(K key) { 

    final AttributeKey<K, V> k = new AttributeKey<K, V>(key, getFromSet(new AttributeKey<K, V>(key, null)).getClazz()); 

    return get(k); 
} 

/** 
* Removes a {@code key} and associated value from the map. 
* 
* @param key 
*   The key and its associated value to remove. 
*/ 
public <K, V> void remove(AttributeKey<K, V> key) { 
    attributes.remove(key); 
    set.remove(key); 
} 

AttributeKey

/** 
* Removes a {@code key} and associated value from the map. 
* 
* @param key 
*   The key and its associated value to remove. 
*/ 
public <K, V> void remove(K key) { 

    final AttributeKey<K, V> ak = new AttributeKey<K, V>(key, getFromSet(new AttributeKey<K, V>(key, null)).getClazz()); 

    remove(ak); 
} 

/** 
* Sets a {@code key} and its associated {@code value}. 
* 
* @param key 
*   The key to set. 
* 
* @param value 
*   The value to set. 
*/ 
public <K, V> void set(AttributeKey<K, V> key, V value) { 
    attributes.put(key, value); 
} 

/** 
* Clears all keys and associated values from this map. 
*/ 
public void clear() { 
    attributes.clear(); 
} 

/** 
* Determines if a {@code key} with associated {@code clazz} type exists 
* within this map. 
* 
* @param key 
*   The key to check. 
* 
* @param clazz 
*   The clazz to check. 
* 
* @return {@code true} If this map contains a specified key and its correct 
*   class type. {@code false} Otherwise. 
*/ 
public <K, V> boolean containsKey(K key, Class<V> clazz) { 
    return attributes.containsKey(new AttributeKey<K, V>(key, clazz)); 
} 

/** 
* Determines if a value exists within this map. 
* 
* @param value 
*   The value to check. 
* 
* @return {@code true} If this map contains this specified value. 
*   {@code false} Otherwise. 
*/ 
public boolean containsValue(Object value) { 
    return attributes.containsValue(value); 
} 

/** 
* Retrieves the undlying {@link #entrySet()} from this map. 
* 
* @return The {@link #entrySet()}. 
*/ 
public Set<Entry<AttributeKey<?, ?>, Object>> entrySet() { 
    return attributes.entrySet(); 
} 

@SuppressWarnings("unchecked") 
private <K, V> AttributeKey<K, V> getFromSet(AttributeKey<K, V> key) { 
    for(AttributeKey<?, ?> k : set) { 
     if (k.getKey() == key.getKey()) { 
      return (AttributeKey<K, V>) k; 
     }   
    } 
    return null; 
} 

/** 
* Determines if this attribute map equals another attribute map. 
* 
* @param o 
*   The object to check. 
* 
* @return {@code true} If this map equals another attribute set, 
*   {@code false} Otherwise. * 
*/ 
public boolean equals(Object o) { 
    return attributes.equals(o); 
} 

/** 
* Retrieves the hash code for this attribute map. 
* 
* @return The hash code. 
*/ 
public int hashCode() { 
    return attributes.hashCode(); 
} 

/** 
* Determines if this attribute map is empty. 
* 
* @return {@true} If this map is empty, {@code false} Otherwise. 
*/ 
public boolean isEmpty() { 
    return attributes.isEmpty(); 
} 

/** 
* Retrieves the underlying {@link #keySet()} from this map. 
* 
* @return The {@link #keySet()}. 
*/ 
public Set<AttributeKey<?, ?>> keySet() { 
    return attributes.keySet(); 
} 

/** 
* Gets the size of this map. 
* 
* @return The size. 
*/ 
public int size() { 
    return attributes.size(); 
} 

public int setSize() { 
    return set.size(); 
} 

/** 
* Gets the values of this map. 
* 
* @return The values. 
*/ 
public Collection<Object> values() { 
    return attributes.values(); 
} 

} 


package com.vltr.collection.attr; 

/** 
* Represents a wrapper that wraps a {@link Map}s key value. This class will  help enforce the correct type. 
* 
* @author Vult-R 
*/ 
public final class AttributeKey<K, V> { 

/** 
* The key that will be used. 
*/ 
private final K key; 

/** 
* The class type associated with this key. 
*/ 
private final Class<V> clazz; 

/** 
* Creates a new {@link AttributeKey}. 
* 
* @param key 
*  The key that will be used. 
* 
* @param clazz 
*  The associated class type. 
*/ 
public AttributeKey(K key, Class<V> clazz) { 
    this.key = key; 
    this.clazz = clazz; 
} 

/** 
* Gets the key for this attribute. 
* 
* @return The key. 
*/ 
public K getKey() { 
    return key; 
} 

/** 
* Gets the associated class type for this attribute. 
* 
* @return The class type. 
*/ 
public Class<V> getClazz() { 
    return clazz; 
} 

@Override 
public int hashCode() { 
    final int prime = 31; 
    int result = 1; 
    result = prime * result + ((key == null) ? 0 : key.hashCode()); 
    return result; 
} 

@Override 
public boolean equals(Object obj) { 
    if (this == obj) { 
     return true; 
    } 
    if (obj == null) { 
     return false; 
    } 
    if (getClass() != obj.getClass()) { 
     return false; 
    } 
    AttributeKey<?, ?> other = (AttributeKey<?, ?>) obj; 
    if (key == null) { 
     if (other.key != null) { 
      return false; 
     } 
    } else if (!key.equals(other.key)) { 
     return false; 
    } 
    return true; 
} 

@Override 
public String toString() { 
    return key.getClass().getSimpleName(); 
} 
} 

package com.vltr.collection.attr; 

/** 
* The {@link RuntimeException} implementation specifically for {@link Attribute}s. 
* 
* @author Seven 
*/ 
public final class AttributeException extends RuntimeException { 

private static final long serialVersionUID = 1L; 

/** 
* Creates a new {@link AttributeException}. 
* 
* @param key 
* The key or this attribute. 
* 
* @param value 
* The value for this attribute. 
*/ 
public AttributeException(AttributeKey<?, ?> key, Object value) { 
    super(String.format("Invalid value type: %s for [key=%s], only accepts type of %s", value.getClass().getSimpleName(), key.getKey().toString(), key.getClazz().getClass().getSimpleName())); 
} 

/** 
* Creates a new {@link AttributeException}. 
* 
* @param key 
*  The key which contains an error. 
*/ 
    public AttributeException(AttributeKey<?, ?> key) { 
    super(String.format("Could not retrieve a value for [key= %s]", key.getKey())); 
    } 

    public AttributeException(AttributeKey<?, ?> key, Class<?> clazz) { 
    super(String.format("Could not cast [key= %s] from [type= %s] to [type= %s]. ", key.getKey(), key.getClazz().getSimpleName(), clazz.getSimpleName())); 
    } 

} 

 map.put("special", 100); 

    // correct 
    int special = map.get("special", Integer.class); 

    // incorrect and caught at compile-time  
    boolean special = map.get("special", Integer.class); 

私は第2パラメータを指定したくありませんが、それを隠したいと思います。それは可能ですか?

+0

あなたはAttributeKeyのコードも投稿できますか? –

+0

ええ、私はちょうどそれをやった。 –

+1

@SameerNaik自動ボクシングのためのGoogle –

答えて

1

これはできません。

Javaは、多型ジェネリックコレクションのコンパイル時の型チェックをサポートしていません。あなたはCollection<?>に何かを追加することができますが、取得する際には、必ず戻ってObjectを取得し、常に実行時のチェックが参加する適切な型にキャストする必要があります。

コンパイラはこれを伝えようとしていましたが、警告メッセージを@SuppressWarnings("unchecked")で無効にしました。これは車の温度警告灯の上に黒いテープを置き、エンジンが過熱したときに驚くようなものです。

あなたは言う:

私はこれを行うことによってこの問題をキャッチすることができます。

map.put("special", 100); 
// correct 
int special = map.get("special", Integer.class); 

// incorrect and caught at compile-time  
boolean special = map.get("special", Integer.class); 

私はそれを非表示にする二番目のパラメータを指定する必要はありませんが。それは可能ですか?

はこれを通じ考えます。 put呼び出しは(おそらく、現在のソースファイルで、すなわち昨年コンパイル何かをしない)遠く、遠くに起きている(だろう)ことができます。 コンパイラは、任意の特定のキーのランタイムでMapに含まれているものの種類は考えていません。実際には2つの異なる実行では、与えられたキーは完全に異なる型の値にマップできます。コンパイラがソースをコンパイルするときに、キーに関連付けられた値の型を知っていると仮定すると、将来的に?あるいは、型は常に同じものになるでしょうか? OPによってコメントから

マップを用いて100%タイプセーフなコレクションを作ることは可能ですが。 AttributeMap.javaにここhttps://github.com/atomicint/aj8/tree/master/server/src/main/java/org/apollo/game/attribute

お知らせください:

@SuppressWarnings("unchecked") 
public <T> T get(AttributeKey<T> key) { 
    ... 

すべてそのコードはAttributeMap<>#get()にランタイムチェックをプッシュされず、それはまた、@SuppressWarnings("unchecked")に頼っ。ランタイムチェックを隠すだけで、コードでコードを隠す必要はありません。ランタイムチェックと潜在的なClassCastExceptionはまだそこにあります。これは、もはやタイプセーフではありません。

+0

プログラミングで何か可能ではありませんか?私は、拡張可能な列挙型/定数を定義することでこの問題を解決しました。 –

+0

明らかに、このような振る舞いをプログラムすることは可能です。あなたは、特定の構文がjavaで可能かどうかについての質問です。すべてが有効な構文ではありません。 –