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

Lesezeit: 7 Minuten

Java Zuordnung mit Werten die durch den Typparameter des Schlussels begrenzt
Ashley Mercer

Gibt es in Java eine Möglichkeit, eine Karte zu haben, bei der der Typparameter eines Werts an den Typparameter eines Schlüssels gebunden 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 erlaubt sein sollte, da ich beim Setzen/Abrufen von Werten sicherstellen kann, dass die Typen korrekt sind.

EDIT: Danke an Cletus für seine Antwort. Ich brauche die Typparameter nicht wirklich auf der Karte selbst, da ich die Konsistenz der Methoden zum Abrufen/Setzen von Werten sicherstellen kann, auch wenn dies bedeutet, dass einige etwas hässliche Umwandlungen verwendet werden.

Java Zuordnung mit Werten die durch den Typparameter des Schlussels begrenzt
Cletus

Sie versuchen nicht, das typsichere heterogene Containermuster von Joshua Bloch zu implementieren, oder? Grundsätzlich:

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

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

  • Was wäre, wenn der Wert selbst Generic wäre? Zum Beispiel statt zu speichern Strings und ints müssen Sie speichern PrettyPrinter<T> wo T wird der Typ-Token als Schlüssel in der Karte verwendet?

    – Lukas

    14. Juli 12 um 22:33 Uhr

  • @Lucas Guava bietet TypeToInstanceMap für diesen Zweck; zusammen mit ClassToInstanceMap die Blochs repliziert Favorites API.

    – dimo414

    26. Juli 17 um 23:22 Uhr

1643912227 129 Java Zuordnung mit Werten die durch den Typparameter des Schlussels begrenzt
Aaron Digulla

Die Frage und die Antworten haben mich zu dieser Lösung gebracht: Typsichere Objektzuordnung. 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 );
    }
}

Dies ist die Key-Klasse. Beachten Sie, dass der Typ T wird in dieser Klasse nie verwendet! Es dient lediglich der Typumwandlung beim Auslesen des Werts aus der Karte. Das Feld key gibt dem Schlüssel nur einen Namen.

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

}

Nein, das geht nicht direkt. Sie müssen eine Wrapper-Klasse schreiben Map<Class, Object> um dieses Objekt durchzusetzen wird instanceof Klasse.

Java Zuordnung mit Werten die durch den Typparameter des Schlussels begrenzt
Konrad Borowski

Es ist möglich, eine Klasse zu erstellen, die eine Zuordnung vom Typ sicherer Schlüssel zu einem Wert speichert und bei Bedarf umwandelt. Die Besetzung get Methode ist sicher, wie nach der Verwendung new Key<CharSequence>(), ist es nicht möglich, es sicher zu übertragen Key<String> oder Key<Object>, sodass das Typsystem die korrekte Verwendung einer Klasse erzwingt.

Der Key Klasse muss final sein, da sonst ein Benutzer sie überschreiben könnte equals und Typunsicherheit verursachen, wenn zwei Elemente mit unterschiedlichen Typen gleich wären. Alternativ ist ein Überschreiben möglich equals 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> {
    }
}

Sie können die folgenden 2 Klassen verwenden, die Kartenklasse: Generische Karte, Map-Key-Klasse: GenericKey

Beispielsweise:

// Create a key includine Type definition
public static final GenericKey<HttpServletRequest> REQUEST = new GenericKey<>(HttpServletRequest.class, "HttpRequestKey");

public void example(HttpServletRequest requestToSave)
{
    GenericMap map = new GenericMap();

    // Saving value
    map.put(REQUEST, requestToSave);

    // Getting value
    HttpServletRequest request = map.get(REQUEST);
}

Vorteile

  • Es zwingt den Benutzer, korrekte Typen durch Kompilierungsfehler zu setzen und zu erhalten
  • Es macht innen Gehäuse für Sie
  • Generic Key hilft zu vermeiden, den Klassentyp bei jedem Aufruf zu schreiben setzen(..) oder werden
  • Keine Tippfehler, z. B. wenn der Schlüssel vom Typ „String“ ist

Generische Karte

public class GenericMap
{  
    private Map<String, Object> storageMap;

    protected GenericMap()
    {
        storageMap = new HashMap<String, Object>();
    }

    public <T> T get(GenericKey<T> key)
    {
        Object value = storageMap.get(key.getKey());
        if (value == null)
        {
            return null;
        }

        return key.getClassType().cast(value);
    }

    /**
     * @param key    GenericKey object with generic type - T (it can be any type)
     * @param object value to put in the map, the type of 'object' mast be - T
     */
    public <T> void put(GenericKey<T> key, T object)
    {
        T castedObject = key.getClassType().cast(object);
        storageMap.put(key.getKey(), castedObject);
    }

    @Override
    public String toString()
    {
        return storageMap.toString();
    }
}

GenericKey

public class GenericKey<T>
{
    private Class<T> classType;
    private String key;

    @SuppressWarnings("unused")
    private GenericKey()
    {
    }

    public GenericKey(Class<T> iClassType, String iKey)
    {
        this.classType = iClassType;
        this.key = iKey;
    }

    public Class<T> getClassType()
    {
        return classType;
    }

    public String getKey()
    {
        return key;
    }

    @Override
    public String toString()
    {
        return "[classType=" + classType + ", key=" + key + "]";
    }
}

1643912227 690 Java Zuordnung mit Werten die durch den Typparameter des Schlussels begrenzt
Yuval Adam

T als Typ muss generisch in der Klasseninstanz definiert werden. Das folgende Beispiel funktioniert:

public class Test<T> {

    private Map<Class<T>, T> defaultValues;

    public void setDefaultValue(Class<T> clazz, T value) {
        defaultValues.put(clazz, value);
    }

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

}

Alternativ können Sie die Antwort von Paul Tomblin verwenden und die umbrechen Map mit Ihrem eigenen Objekt, das diese Art von Generika erzwingt.

.

758540cookie-checkJava-Zuordnung mit Werten, die durch den Typparameter des Schlüssels begrenzt sind

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy