java - Cannot set value of generic Property in Map -
i trying set value of property in map following error:
the method setvalue(capture#7-of ?) in type writablevalue<capture#7-of ?> not applicable arguments (capture#8-of ?)
here code:
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 map, of implement property map2.put("key1", new simpleintegerproperty(5)); map2.put("key2", new simplestringproperty("hi")); //i can assume value of properties same key of same type map2.get("key1").setvalue(map1.get("key1").getvalue()); //error map2.get("key2").setvalue(map1.get("key2").getvalue()); //error
i cannot this, must copied value only:
map2.put("key1", map1.get("key1")); map2.put("key2", map1.get("key2"));
i can simplify more without map:
property<?> p1 = new simpleintegerproperty(5); property<?> p2 = new simpleintegerproperty(10); p1.setvalue(p2.getvalue());//same (basic) error p1.setvalue(new object());//same (basic) error
i using java 1.8 jdk
the problem trying inherently not type safe. may know programming logic type of property associated "key1"
in map1
same type of property associated "key1"
in map2
, compiler cannot possibly guarantee fact. current structure have, choice abandon compile-time safety.
the underlying issue here map
has wrong api requirements (even though basic functionality need). map
homogeneous container, meaning values in given map of same type. enforced api: public v get(k key);
, public void put(k key, v value);
use same type v
, fixed single map
instance. want heterogeneous container, in value varies depending on key. want api in v
not fixed instances of container, changes each invocation of methods get
, put
, depending on value of key. need get
, put
methods generic methods:
public interface container<k> { // k type of key... public <v> v get(k key) ; public <v> void put(k key, v value); }
an implementation of documented in josh bloch's "effective java", , called "typesafe heterogeneous container" pattern.
start defining type key maintains type corresponding property:
/** * @param <t> type associated key * @param <k> actual type of key */ 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 , key must 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); } }
here t
type of property, , k
actual type of key. modify code to
// string key map property<number>: typedkey<number, string> key1 = new typedkey<>(number.class, "key1"); // string key map property<string>: typedkey<string, string> key2 = new typedkey<>(string.class, "key2");
now define container class act map. basic idea here have 2 methods:
public <t> void put(typedkey<t, k> key, property<t> property) public <t> property<t> get(typedkey<t, k> key)
the implementation pretty straightforward:
/** * @param <k> type of key in 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) { // virtue of api defined, property associated // key must property<t> (even though underlying map not know it): return (property<t>) map.get(key); } }
there couple of subtleties here. because underlying map private
, assured way access through our put
, get
methods. when call get()
on underlying map typedkey<t,k>
, assured corresponding property must (null
or) property<t>
(since thing compiler have allowed have been inserted earlier). though compiler not aware of it, guaranteed cast succeeds, , @suppresswarnings
justified.
now if create typedpropertymap<k>
(k
here type of actual key), compile-time guaranteed map.get(key1)
returns property<number>
(because key1
has compile-time type of typedkey<number, string>
) , map.get(key2)
returns property<string>
(because key2
has compile-time type of typedkey<string, string>
).
here's complete runnable example:
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> type associated key * @param <k> actual type of key */ 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 , key must 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> type of key in 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) { // virtue of api defined, property associated // key must property<t> (even though underlying map not know it): return (property<t>) map.get(key); } } }
note it's hard make observablemap
, same reasons outlined in introduction: observablemap
interface defines homogeneous methods (which indeed inherited map
interface) cannot implement in way meets requirements. can, however, make implement javafx.beans.observable
, allow register invalidationlistener
s it, , use in bindings:
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 before... }
Comments
Post a Comment