无法在Map中设置通用属性的值

时间:2015-04-29 15:31:48

标签: java javafx

我正在尝试在地图中设置属性的值,但是我收到以下错误:

The method setValue(capture#7-of ?) in the type WritableValue<capture#7-of ?> is not applicable for the arguments (capture#8-of ?)

这是我的代码:

Map<String, Property<?>> map1 = new HashMap<String, Property<?>>();
Map<String, Property<?>> map2 = new HashMap<String, Property<?>>();

map1.put("key1", new SimpleIntegerProperty(5));
map1.put("key2", new SimpleStringProperty("hi")); //I need multiple property types in this Map, all of which implement Property

map2.put("key1", new SimpleIntegerProperty(5));
map2.put("key2", new SimpleStringProperty("hi"));

//I can assume that the value of the properties with the same key are of the same type
map2.get("key1").setValue(map1.get("key1").getValue()); //Error
map2.get("key2").setValue(map1.get("key2").getValue()); //Error

我不能这样做,它们必须只复制值:

map2.put("key1", map1.get("key1"));
map2.put("key2", map1.get("key2"));

如果没有地图,我可以更加简化:

Property<?> p1 = new SimpleIntegerProperty(5);
Property<?> p2 = new SimpleIntegerProperty(10);
p1.setValue(p2.getValue());//Same (basic) error
p1.setValue(new Object());//Same (basic) error

我正在使用Java 1.8 JDK

4 个答案:

答案 0 :(得分:3)

问题是你要做的事情本质上不是类型安全的。您可以从编程逻辑中了解到"key1"中与map1相关联的属性的类型与"key1"中与map2相关联的属性的类型相同,但是编译器不可能保证这个事实。因此,使用当前的结构,您唯一的选择就是放弃编译时的安全性。

这里的根本问题是Map具有错误的API以满足您的要求(即使它的基本功能是您所需要的)。 Map同类容器,意味着给定地图中的所有值都属于同一类型。这由API强制执行:public V get(K key);public void put(K key, V value);始终使用相同的类型V,这对于任何单个Map实例都是固定的。你真正想要的是一个异构容器,其中值取决于密钥。因此,您需要一个API,其中V未针对容器实例进行修复,而是针对方法getput的每次调用进行更改,具体取决于密钥的值。所以你需要getput方法是通用方法的东西:

public interface Container<K> { // K is the type of the key...

    public <V> V get(K key) ;
    public <V> void put(K key, V value);

}

Josh Bloch&#34; Effective Java&#34;中记录了这种实现,并称为&#34; Typesafe异构容器&#34;图案。

首先为您的密钥定义一个类型,以维护相应属性的类型:

    /**
     * @param <T> The type associated with this key
     * @param <K> The actual type of the key itself
     */
    public class TypedKey<T, K> {
        private final Class<T> type ;
        private final K key ;

        public TypedKey(Class<T> type, K key) {
            if (type == null || key == null) {
                throw new NullPointerException("type and key must be non-null");
            }
            this.type = type ;
            this.key = key ;
        }

        @Override
        public boolean equals(Object o) {
            if (o == null) return false ;
            if (! (o instanceof TypedKey)) {
                return false ;
            }
            TypedKey<?, ?> other = (TypedKey<?, ?>) o ;
            return other.type.equals(type) && other.key.equals(key);
        }

        @Override
        public int hashCode() {
            return Objects.hash(type, key);
        }
    }

此处T将是属性的类型,K是密钥的实际类型。因此,您将修改代码

// String key to map to Property<Number>:
TypedKey<Number, String> key1 = new TypedKey<>(Number.class, "key1");

// String key to map to Property<String>:
TypedKey<String, String> key2 = new TypedKey<>(String.class, "key2");

现在定义一个容器类作为地图。这里的基本思想是有两种方法:

public <T> void put(TypedKey<T, K> key, Property<T> property)

public <T> Property<T> get(TypedKey<T, K> key)

实施非常简单:

    /**
     * @param <K> The type of the key in the TypedKey
     */
    public class TypedPropertyMap<K> { 
        private final Map<TypedKey<?, K>, Property<?>> map = new HashMap<>();

        public <T> void put(TypedKey<T, K> key, Property<T> property) {
            map.put(key, property);
        }

        @SuppressWarnings("unchecked")
        public <T> Property<T> get(TypedKey<T, K> key) {

            // by virtue of the API we defined, the property associated with
            // key must be a Property<T> (even though the underlying map does not know it):

            return (Property<T>) map.get(key);
        }
    }

这里有一些细微之处。由于基础地图为private,因此我们确信唯一可以通过我们的putget方法访问该地图。因此,当我们使用get()在基础地图上调用TypedKey<T,K>时,我们可以确保相应的属性必须为(null或)a Property<T>(因为这是唯一的编译器允许先前插入的东西)。因此,即使编译器没有意识到它,我们也能保证转换成功,并且@SuppressWarnings是合理的。

现在,如果我创建TypedPropertyMap<K>K这里只是实际键的类型),我编译时保证map.get(key1)返回Property<Number>(因为key1的编译时类型为TypedKey<Number, String>)而map.get(key2)返回Property<String>(因为key2的编译时类型为TypedKey<String, String> })。

这是一个完整的可运行示例:

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import javafx.beans.property.Property;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;


public class TypedPropertyMapTest {

    public static void main(String[] args) {
        TypedPropertyMap<String> map1 = new TypedPropertyMap<>();
        TypedPropertyMap<String> map2 = new TypedPropertyMap<>();

        TypedKey<Number, String> key1 = new TypedKey<>(Number.class, "key1");
        TypedKey<String, String> key2 = new TypedKey<>(String.class, "key2");

        map1.put(key1, new SimpleIntegerProperty(5));
        map1.put(key2, new SimpleStringProperty("hi"));

        map2.put(key1, new SimpleIntegerProperty());
        map2.put(key2, new SimpleStringProperty());

        map2.get(key1).setValue(map1.get(key1).getValue());
        map2.get(key2).setValue(map1.get(key2).getValue());

        System.out.println(map2.get(key1).getValue());
        System.out.println(map2.get(key2).getValue());

    }


    /**
     * @param <T> The type associated with this key
     * @param <K> The actual type of the key itself
     */
    public static class TypedKey<T, K> {
        private final Class<T> type ;
        private final K key ;

        public TypedKey(Class<T> type, K key) {
            if (type == null || key == null) {
                throw new NullPointerException("type and key must be non-null");
            }
            this.type = type ;
            this.key = key ;
        }

        @Override
        public boolean equals(Object o) {
            if (o == null) return false ;
            if (! (o instanceof TypedKey)) {
                return false ;
            }
            TypedKey<?, ?> other = (TypedKey<?, ?>) o ;
            return other.type.equals(type) && other.key.equals(key);
        }

        @Override
        public int hashCode() {
            return Objects.hash(type, key);
        }
    }

    /**
     * @param <K> The type of the key in the TypedKey
     */
    public static class TypedPropertyMap<K> { 
        private final Map<TypedKey<?, K>, Property<?>> map = new HashMap<>();

        public <T> void put(TypedKey<T, K> key, Property<T> property) {
            map.put(key, property);
        }

        @SuppressWarnings("unchecked")
        public <T> Property<T> get(TypedKey<T, K> key) {

            // by virtue of the API we defined, the property associated with
            // key must be a Property<T> (even though the underlying map does not know it):

            return (Property<T>) map.get(key);
        }
    }
}

请注意,由于引言中概述的相同原因,很难将其设为ObservableMapObservableMap接口定义了齐次方法(确实从{{继承) 1}} interface)您无法以满足您要求的方式实施。但是,您可以轻松地创建此工具Map,这将允许您向其注册javafx.beans.Observable,并在绑定中使用它:

InvalidationListener

答案 1 :(得分:0)

如果您希望继续使用类型,请尝试此操作:

        IntegerProperty p1 = new SimpleIntegerProperty(5);
        IntegerProperty p2 = new SimpleIntegerProperty(10);
        p1.setValue(p2.getValue());

或者这没有类型安全:

        Property p1 = new SimpleIntegerProperty(5);
        Property p2 = new SimpleIntegerProperty(10);
        p1.setValue(p2.getValue());

我。即删除通配符。

答案 2 :(得分:0)

这不是JavaFX问题,这是一个泛型问题。获得通配符类型后,setValue方法的类型为T ...您尚未指定。换句话说,这不行:

Property<?> p1 = new SimpleIntegerProperty(5);
Property<?> p2 = new SimpleIntegerProperty(10);
p1.setValue(p2.getValue());

但这没关系:

Property<Number> p1 = new SimpleIntegerProperty(5);
Property<Number> p2 = new SimpleIntegerProperty(10);
p1.setValue(p2.getValue());

您应该能够使用instanceof检查解决此问题。丑陋,但它会奏效。

Property<?> mapProperty = map2.get("key1");
if(mapProperty instanceof IntegerProperty) { 
   ((IntegerProperty) mapProperty).setValue((Integer) map1.get("key1").getValue());
} else {
   // handle the error
}

答案 3 :(得分:-1)

制作Property个界面,SimpleIntegerPropertySimpleStringProperty实施Property。然后你会有一个HashMap<String, Property>