web-dev-qa-db-fra.com

Carte Java avec les valeurs limitées par le paramètre de type de clé

Existe-t-il un moyen en Java d’avoir une carte où le paramètre type d’une valeur est lié au paramètre type d’une clé? Ce que je veux écrire est quelque chose comme ce qui suit:

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

Autrement dit, je peux stocker toute valeur par défaut sur un objet de classe, à condition que le type de la valeur corresponde à celui de l'objet de classe. Je ne vois pas pourquoi cela ne devrait pas être autorisé puisque je peux m'assurer lors de la définition/obtention des valeurs que les types sont corrects.

EDIT: Merci à Cletus pour sa réponse. Je n'ai pas réellement besoin des paramètres de type sur la carte elle-même, car je peux assurer la cohérence des méthodes qui obtiennent/définissent des valeurs, même si cela implique d'utiliser des conversions légèrement laides.

33
Ashley Mercer

Vous n'essayez pas d'implémenter le modèle de conteneur hétérogène typesafe de Joshua Bloch, n'est-ce pas? Fondamentalement:

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

From Effective Java (2e édition) et cette présentation .

52
cletus

La question et les réponses m’ont amené à proposer cette solution: Carte d’objet type-safe . Voici le code. Cas de test:

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

Classe de clé:

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

Non, vous ne pouvez pas le faire directement. Vous devrez écrire une classe wrapper autour de Map<Class, Object> pour appliquer cet objet à la classe instanceof.

2
Paul Tomblin

Il est possible de créer une classe qui stocke une carte de type safe key avec une valeur et une conversion si nécessaire. La méthode de conversion dans get est sûre, car après l'utilisation de new Key<CharSequence>(), il n'est pas possible de le convertir en sécurité en Key<String> ou Key<Object>, de sorte que le système de types impose l'utilisation correcte d'une classe.

La classe Key doit être finale, sinon un utilisateur pourrait remplacer equals et causer une insécurité du type si deux éléments de types différents devaient être égaux. Vous pouvez également remplacer la variable equals par la valeur finale si vous souhaitez utiliser l'héritage en dépit des problèmes qui y sont associés.

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