无法在地图中设置通用 属性 的值
Cannot set value of generic Property in Map
我试图在地图中设置 属性 的值,但出现以下错误:
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
如果您想继续使用以下类型,请尝试此操作:
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());
我。 e.删除通配符。
这不是 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
}
使 Property
成为接口并让 SimpleIntegerProperty
和 SimpleStringProperty
实现 Property
。然后你会有一个 HashMap<String, Property>
.
问题是你正在尝试做的事情本质上是不安全的。从编程逻辑可以知道,map1
中"key1"
关联的属性类型与"key1"
关联的属性类型相同在 map2
中,但编译器无法保证这一事实。因此,对于您当前的结构,您唯一的选择就是放弃编译时安全。
这里的根本问题是 Map
的 API 不符合您的要求(即使它的基本功能正是您所需要的)。 Map
是一个 homogeneous 容器,这意味着给定映射中的所有值都属于同一类型。这是由 API 强制执行的:public V get(K key);
和 public void put(K key, V value);
始终使用相同的类型 V
,它对于任何单个 Map
实例都是固定的。你真正想要的是一个 heterogeneous 容器,其中的值因键而异。因此,您需要一个 API,其中 V
对于容器的实例而言不是固定的,而是会根据值的不同在每次调用方法 get
和 put
时发生变化的关键。所以你需要一些东西,其中 get
和 put
方法是通用方法:
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 的 "Effective Java" 中记录了这种实现,称为 "Typesafe Heterogeneous Container" 模式。
首先为您的密钥定义一个类型,该类型维护相应 属性:
的类型
/**
* @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");
现在定义一个容器class作为地图。这里的基本思想是有两种方法:
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
,我们确信访问它的唯一方法是通过我们的 put
和 get
方法。因此,当我们用 TypedKey<T,K>
在基础地图上调用 get()
时,我们可以确保相应的 属性 必须是(null
或)一个 Property<T>
(因为这是编译器唯一允许早先插入的东西)。因此,即使编译器不知道它,我们也可以保证转换成功,并且 @SuppressWarnings
是合理的。
现在如果我创建一个 TypedPropertyMap<K>
(K
这里只是实际密钥的类型),我在编译时保证 map.get(key1)
returns 一个Property<Number>
(因为 key1
的编译时类型是 TypedKey<Number, String>
)和 map.get(key2)
returns a 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);
}
}
}
请注意,由于介绍中概述的相同原因,很难将其设为 ObservableMap
:ObservableMap
接口定义了同类方法(它们确实继承自 Map
接口),您无法以满足您要求的方式实现。但是,您可以轻松地制作此工具 javafx.beans.Observable
,这将允许您向其注册 InvalidationListener
,并在绑定中使用它:
public class TypedPropertyMap<K> implements Observable {
private final ObservableMap<TypedKey<?, K>, Property<?>> map = FXCollections.observableHashMap();
@Override
public void addListener(InvalidationListener listener) {
map.addListener(listener);
}
@Override
public void removeListener(InvalidationListener listener) {
map.removeListener(listener);
}
// remaining code as before...
}
我试图在地图中设置 属性 的值,但出现以下错误:
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
如果您想继续使用以下类型,请尝试此操作:
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());
我。 e.删除通配符。
这不是 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
}
使 Property
成为接口并让 SimpleIntegerProperty
和 SimpleStringProperty
实现 Property
。然后你会有一个 HashMap<String, Property>
.
问题是你正在尝试做的事情本质上是不安全的。从编程逻辑可以知道,map1
中"key1"
关联的属性类型与"key1"
关联的属性类型相同在 map2
中,但编译器无法保证这一事实。因此,对于您当前的结构,您唯一的选择就是放弃编译时安全。
这里的根本问题是 Map
的 API 不符合您的要求(即使它的基本功能正是您所需要的)。 Map
是一个 homogeneous 容器,这意味着给定映射中的所有值都属于同一类型。这是由 API 强制执行的:public V get(K key);
和 public void put(K key, V value);
始终使用相同的类型 V
,它对于任何单个 Map
实例都是固定的。你真正想要的是一个 heterogeneous 容器,其中的值因键而异。因此,您需要一个 API,其中 V
对于容器的实例而言不是固定的,而是会根据值的不同在每次调用方法 get
和 put
时发生变化的关键。所以你需要一些东西,其中 get
和 put
方法是通用方法:
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 的 "Effective Java" 中记录了这种实现,称为 "Typesafe Heterogeneous Container" 模式。
首先为您的密钥定义一个类型,该类型维护相应 属性:
的类型 /**
* @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");
现在定义一个容器class作为地图。这里的基本思想是有两种方法:
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
,我们确信访问它的唯一方法是通过我们的 put
和 get
方法。因此,当我们用 TypedKey<T,K>
在基础地图上调用 get()
时,我们可以确保相应的 属性 必须是(null
或)一个 Property<T>
(因为这是编译器唯一允许早先插入的东西)。因此,即使编译器不知道它,我们也可以保证转换成功,并且 @SuppressWarnings
是合理的。
现在如果我创建一个 TypedPropertyMap<K>
(K
这里只是实际密钥的类型),我在编译时保证 map.get(key1)
returns 一个Property<Number>
(因为 key1
的编译时类型是 TypedKey<Number, String>
)和 map.get(key2)
returns a 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);
}
}
}
请注意,由于介绍中概述的相同原因,很难将其设为 ObservableMap
:ObservableMap
接口定义了同类方法(它们确实继承自 Map
接口),您无法以满足您要求的方式实现。但是,您可以轻松地制作此工具 javafx.beans.Observable
,这将允许您向其注册 InvalidationListener
,并在绑定中使用它:
public class TypedPropertyMap<K> implements Observable {
private final ObservableMap<TypedKey<?, K>, Property<?>> map = FXCollections.observableHashMap();
@Override
public void addListener(InvalidationListener listener) {
map.addListener(listener);
}
@Override
public void removeListener(InvalidationListener listener) {
map.removeListener(listener);
}
// remaining code as before...
}