Вопрос-ответ

Java map with values limited by key's type parameter

Java-карта со значениями, ограниченными параметром типа ключа

Есть ли в Java способ создать карту, в которой параметр типа значения привязан к параметру типа ключа? Я хочу написать что-то вроде следующего:

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

То есть я могу сохранить любое значение по умолчанию для объекта класса при условии, что тип значения совпадает с типом объекта класса. Я не понимаю, почему это не должно быть разрешено, поскольку я могу гарантировать при установке / получении значений, что типы верны.

РЕДАКТИРОВАТЬ: Спасибо клетусу за его ответ. На самом деле мне не нужны параметры типа на самой карте, поскольку я могу обеспечить согласованность методов, которые получают / устанавливают значения, даже если это означает использование некоторых немного уродливых приведений.

Переведено автоматически
Ответ 1

Вы же не пытаетесь реализовать типобезопасный гетерогенный шаблон контейнера Джошуа Блоха, не так ли? В основном:


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

От Эффективная Java (2-я редакция) и эта презентация.

Ответ 2

Вопрос и ответы на него побудили меня предложить это решение: Типобезопасная объектная карта. Вот код. Тестовый пример.:

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

Это класс Key. Обратите внимание, что тип T никогда не используется в этом классе! Это исключительно для приведения типов при считывании значения из map. Поле key дает ключу только имя.

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

Нет, вы не можете сделать это напрямую. Вам нужно будет написать класс-оболочку вокруг Map<Class, Object> чтобы обеспечить, чтобы объект был instanceof классом.

Ответ 4

Можно создать класс, который хранит сопоставление типа safe key со значением и приводит его при необходимости. Метод cast in get безопасен, поскольку после использования new Key<CharSequence>() его невозможно безопасно преобразовать в Key<String> или Key<Object>, поэтому система типов обеспечивает правильное использование класса.

Key Класс должен быть окончательным, так как в противном случае пользователь может переопределить equals и вызвать небезопасность типа, если два элемента с разными типами будут равны. В качестве альтернативы можно переопределить equals, чтобы быть окончательным, если вы хотите использовать наследование, несмотря на проблемы с ним.

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> {
}
}
java generics