it-swarm.com.de

Java-Map mit Werten, die durch den Typparameter des Schlüssels begrenzt sind

Gibt es in Java eine Möglichkeit, eine Map zu haben, bei der der Typparameter eines Werts mit dem Typparameter eines Schlüssels verknüpft ist? Was ich schreiben möchte, ist etwa Folgendes:

public class Foo {
    // This declaration won't compile - what should it be?
    private static Map<Class<T>, T> defaultValues;

    // These two methods are just fine
    public static <T> void setDefaultValue(Class<T> clazz, T value) {
        defaultValues.put(clazz, value);
    }

    public static <T> T getDefaultValue(Class<T> clazz) {
        return defaultValues.get(clazz);
    }
}

Das heißt, ich kann jeden Standardwert für ein Klassenobjekt speichern, vorausgesetzt, der Typ des Werts stimmt mit dem des Klassenobjekts überein. Ich verstehe nicht, warum dies nicht zulässig sein sollte, da ich beim Festlegen/Abrufen von Werten sicherstellen kann, dass die Typen korrekt sind.

EDIT: Danke an Cletus für die Antwort. Ich brauche die Typparameter nicht wirklich auf der Map selbst, da ich die Konsistenz der Methoden sicherstellen kann, mit denen Werte abgerufen/gesetzt werden, auch wenn dies die Verwendung einiger leicht hässlicher Casts bedeutet.

33
Ashley Mercer

Sie versuchen nicht, Joshua Blochs typsicheres, heterogenes Containermuster zu implementieren, oder? Grundsätzlich gilt:

public class Favorites {
  private Map<Class<?>, Object> favorites =
    new HashMap<Class<?>, Object>();

  public <T> void setFavorite(Class<T> klass, T thing) {
    favorites.put(klass, thing);
  }

  public <T> T getFavorite(Class<T> klass) {
    return klass.cast(favorites.get(klass));
  }

  public static void main(String[] args) {
    Favorites f = new Favorites();
    f.setFavorite(String.class, "Java");
    f.setFavorite(Integer.class, 0xcafebabe);
    String s = f.getFavorite(String.class);
    int i = f.getFavorite(Integer.class);
  }
}

Ab Effective Java (2. Auflage) und diese Präsentation .

52
cletus

Die Frage und die Antworten ließen mich diese Lösung finden: Typensichere Objektkarte . Hier ist der Code. Testfall:

import static org.junit.Assert.*;

import Java.util.ArrayList;
import Java.util.List;

import org.junit.Test;


public class TypedMapTest {
    private final static TypedMapKey<String> KEY1 = new TypedMapKey<String>( "key1" );
    private final static TypedMapKey<List<String>> KEY2 = new TypedMapKey<List<String>>( "key2" );

    @Test
    public void testGet() throws Exception {

        TypedMap map = new TypedMap();
        map.set( KEY1, null );
        assertNull( map.get( KEY1 ) );

        String expected = "Hallo";
        map.set( KEY1, expected );
        String value = map.get( KEY1 );
        assertEquals( expected, value );

        map.set( KEY2, null );
        assertNull( map.get( KEY2 ) );

        List<String> list = new ArrayList<String> ();
        map.set( KEY2, list );
        List<String> valueList = map.get( KEY2 );
        assertEquals( list, valueList );
    }
}

Schlüsselklasse:

public class TypedMapKey<T> {
    private String key;

    public TypedMapKey( String key ) {
        this.key = key;
    }

    @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;
        }
        TypedMapKey<?> other = (TypedMapKey<?>) 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;
    }
}

TypedMap.Java:

import Java.util.Collection;
import Java.util.HashMap;
import Java.util.Map;
import Java.util.Set;

public class TypedMap implements Map<Object, Object> {
    private Map<Object, Object> delegate;

    public TypedMap( Map<Object, Object> delegate ) {
        this.delegate = delegate;
    }

    public TypedMap() {
        this.delegate = new HashMap<Object, Object>();
    }

    @SuppressWarnings( "unchecked" )
    public <T> T get( TypedMapKey<T> key ) {
        return (T) delegate.get( key );
    }

    @SuppressWarnings( "unchecked" )
    public <T> T remove( TypedMapKey<T> key ) {
        return (T) delegate.remove( key );
    }

    public <T> void set( TypedMapKey<T> key, T value ) {
        delegate.put( key, value );
    }

    // --- Only calls to delegates below

    public void clear() {
        delegate.clear();
    }

    public boolean containsKey( Object key ) {
        return delegate.containsKey( key );
    }

    public boolean containsValue( Object value ) {
        return delegate.containsValue( value );
    }

    public Set<Java.util.Map.Entry<Object, Object>> entrySet() {
        return delegate.entrySet();
    }

    public boolean equals( Object o ) {
        return delegate.equals( o );
    }

    public Object get( Object key ) {
        return delegate.get( key );
    }

    public int hashCode() {
        return delegate.hashCode();
    }

    public boolean isEmpty() {
        return delegate.isEmpty();
    }

    public Set<Object> keySet() {
        return delegate.keySet();
    }

    public Object put( Object key, Object value ) {
        return delegate.put( key, value );
    }

    public void putAll( Map<? extends Object, ? extends Object> m ) {
        delegate.putAll( m );
    }

    public Object remove( Object key ) {
        return delegate.remove( key );
    }

    public int size() {
        return delegate.size();
    }

    public Collection<Object> values() {
        return delegate.values();
    }

}
3
Aaron Digulla

Nein, Sie können es nicht direkt tun. Sie müssen eine Wrapper-Klasse um Map<Class, Object> schreiben, um zu erzwingen, dass Object eine instanceof-Klasse ist.

2
Paul Tomblin

Es ist möglich, eine Klasse zu erstellen, in der eine Zuordnung vom Typ sicherer Schlüssel zu einem Wert gespeichert und bei Bedarf umgewandelt wird. Die in get umgesetzte Methode ist sicher, da es nach der Verwendung von new Key<CharSequence>() nicht möglich ist, sie sicher in Key<String> oder Key<Object> umzusetzen, sodass das Typsystem die korrekte Verwendung einer Klasse erzwingt.

Die Klasse Key muss final sein, da andernfalls ein Benutzer equals überschreiben und eine Unsicherheit des Typs verursachen könnte, wenn zwei Elemente mit unterschiedlichen Typen gleich wären. Alternativ können Sie equals überschreiben, um endgültig zu sein, wenn Sie die Vererbung trotz der damit verbundenen Probleme verwenden möchten.

public final class TypeMap {
    private final Map<Key<?>, Object> m = new HashMap<>();

    public <T> T get(Key<? extends T> key) {
        // Safe, as it's not possible to safely change the Key generic type,
        // hash map cannot be accessed by an user, and this class being final
        // to prevent serialization attacks.
        @SuppressWarnings("unchecked")
        T value = (T) m.get(key);
        return value;
    }

    public <T> void put(Key<? super T> key, T value) {
        m.put(key, value);
    }

    public static final class Key<T> {
    }
}
0
Konrad Borowski