2016-07-10 20 views
4

Ich versuche eine Sammlung zu schreiben, die zu 100% typsicher ist. Ich habe alles richtig funktioniert, das einzige Problem, mit dem ich Probleme habe, ist dies.Java-Kompilierzeittyp, der nach polymorphen Sammlungen sucht

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); 

Ausgabe ohne die Laufzeitfehler

special value: 100 running value: false 

Der Wert der Karte wird der Wert I wählen. Beispiel special sollte eine Integer-Zahl sein, denn das ist, was ich in die Map geschrieben habe, gibt es eine Möglichkeit, diesen Fehler zur Kompilierungszeit zu überprüfen, damit es nicht zu einem Laufzeitfehler wird?

Bevor ich diesen Code poste, wird das zu kompliziert aussehen, du fragst dich vielleicht, warum tust du das nicht einfach?

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

Ja, das würde das gleiche tun wie das, was ich tue, aber das scheitert jede Besetzung bei der Kompilierung zu fangen. Ich versuche, meinen Typ, den ich als Wert eingegeben habe, im Auge zu behalten und ihn als denselben Typ abzurufen, damit er zur Kompilierungszeit abgefangen werden kann.

Hier ist meine Klasse so weit.

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())); 
    } 

} 

ich dieses Problem, indem Sie diese fangen.

Obwohl ich den zweiten Parameter nicht angeben möchte, möchte ich das ausblenden. Ist das möglich?

+0

Können Sie den Code für AttributeKey auch gutschreiben? –

+0

Ja, ich habe das gerade getan. –

+1

@SameerNaik Google für Auto-Boxen –

Antwort

1

Sie können dies nicht tun.

Java unterstützt keine Überprüfung der Kompilierungszeit für polymorphe generische Sammlungen. Sie können alles zu einem Collection<?> hinzufügen, aber beim Abrufen erhalten Sie immer Object zurück und müssen auf den entsprechenden Typ umwandeln, was immer eine Laufzeitprüfung mit sich bringt.

Der Compiler hat versucht, Ihnen dies zu sagen, aber Sie haben die Warnungen mit @SuppressWarnings("unchecked") deaktiviert. Das ist so, als würde man die Temperaturwarnlampe in Ihrem Auto mit schwarzem Klebeband überziehen und sich dann wundern, wenn der Motor überhitzt.

Sie sagen:

ich dieses Problem, indem Sie diese fangen.

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); 

Obwohl ich den zweiten Parameter nicht angeben möchte, möchte ich das ausblenden. Ist das möglich?

Denken Sie dies durch. Die put Aufrufe konnten (weit) weit (weit entfernt) geschehen sein (d. H. Nicht in der aktuellen Quelldatei, möglicherweise etwas, das letztes Jahr kompiliert wurde). Die Compiler hat keine Ahnung, welche Arten in den Mapzur Laufzeit für einen bestimmten Schlüssel enthalten sind.Tatsächlich könnte ein gegebener Schlüssel bei zwei verschiedenen Ausführungen auf Werte ganz unterschiedlicher Typen abgebildet werden. Wie soll der Compiler, wenn er die Quelle kompiliert, den Werttyp wissen, der einem Schlüssel in der Zukunft zugeordnet ist? Oder dass der Typ immer derselbe sein wird?

Aus einem Kommentar des OP:

Obwohl machen 100% typsichere Sammlungen mit einer Karte möglich ist. Sehen Sie hier https://github.com/atomicint/aj8/tree/master/server/src/main/java/org/apollo/game/attribute

Hinweis in AttributeMap.java:

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

dass der gesamte Code ist die Laufzeitüberprüfung in AttributeMap<>#get() nicht schieben, und es greift auch auf @SuppressWarnings("unchecked"). Es verbirgt nur die Laufzeitprüfung, so dass Ihr Code sie nicht verstecken muss. Der Laufzeitcheck und das Potential ClassCastException sind noch da, und das ist definitiv NICHT mehr typsicher.

+0

Ist in der Programmierung nichts möglich? Ich weiß, Netty hat dieses Problem gelöst, indem er erweiterbare Enums/Konstanten definiert hat. –

+0

Offensichtlich ist es möglich, etwas zu programmieren, das sich so verhält, du hast es schon getan. Sie fragen sich, ob eine bestimmte Syntax in Java möglich ist. Nicht alles ist gültige Syntax. –