I got a class
public class ValueObject<T> {
private T value;
public void setValue(T value){
this.value = value
}
}
In an other class i got an Array of the Objects from the first Class
ArrayList<ValueObject<?>> valueObjects = new ArrayList<>();
ArrayList<String> valueNames = new ArrayList<>();
now i want to write a Methode wich looks in a second array for a name and assigns a new value to an instance of the first object in that arrayList
ValueObject<?> get(String name) {
return valueObjects.get(valueNames.indexOf(name));
}
public <T> void set(String name, T value) {
get(name).setValue(value);
}
But i dont get this to work. Do i need to write something with ? in the set() Method?
Thanks =)
You don't provide a full example, so not sure which will help you.
Version 1 if you can use List<ValueObject<T>> because all ValueObjects hold the same type.
static class Lookup<T2> {
List<ValueObject<T2>> valueObjects = new ArrayList<>();
List<String> valueNames = new ArrayList<>();
ValueObject<T2> get(String name) {
return valueObjects.get(valueNames.indexOf(name));
}
public void set(String name, T2 value) {
get(name).setValue(value);
}
}
Version 2 if valueObjects really contains ValueObject with different contained classes:
#SuppressWarnings("unchecked")
static class Lookup2 {
List<ValueObject<?>> valueObjects = new ArrayList<>();
List<String> valueNames = new ArrayList<>();
/* unsafe get */
ValueObject<?> get(String name) {
return valueObjects.get(valueNames.indexOf(name));
}
/* set using unsafe get */
public <T> void setUnsafe(String name, T value) {
/* might add handling of runtime exceptions */
((ValueObject<T>)get(name)).setValue(value);
}
/* safe get when client knows class */
<T> ValueObject<T> get(String name, Class<T> clazz) {
/* might do instanceOf check here to throw custom exception */
return (ValueObject<T>) valueObjects.get(valueNames.indexOf(name));
}
/* set using safe get */
public <T> void set(String name, T value) {
/* might add handling of runtime exceptions */
get(name, (Class<T>) value.getClass()).setValue(value);
}
}
Related
This is an odd question. I don't think there's a solution, but I thought I'd ask anyway.
Say I have an enum:
public enum Key {
RED(String.class),
GREEN(Integer.class),
BLUE(Short.class);
private Class<?> expectedType;
Key(Class<?> expectedType) { this.expectedType = expectedType; }
public Class<?> getExpectedType() { return expectedType; }
}
I want to use the 'expectedType' field from the Key enum as the return type of a method. See:
public class Cache {
private static Map<Key, Object> cache = new HashMap<>();
public void put(Key key, Object value) {
// Easy to validate that 'value' is of type key.getExpectedType()...
}
public <T> T get(Key key) {
Object value = cache.get(key);
// TODO need to define <T> as key.getExpectedType(). How?
}
}
See that TODO? I'd like for get() to define the return type of the 'expectedType' defined by the key parameter. E.g. if the key parameter were RED, the get() method would return a String and you could write:
String s = cache.get(Key.RED);
Is there a way to do that?
I'm thinking there isn't, but I'd love to hear of a clever solution.
Enums don't support generics, but you could use a regular class as a generic pseudo-enum:
public class Key<T> {
public static final Key<String> RED = new Key<>(String.class);
public static final Key<Integer> GREEN = new Key<>(Integer.class);
public static final Key<Short> BLUE = new Key<>(Short.class);
private final Class<T> expectedType;
private Key(Class<T> expectedType) { this.expectedType = expectedType; }
public Class<T> getExpectedType() { return expectedType; }
}
public class Cache {
private Map<Key<?>, Object> cache = new HashMap<>();
public <T> void put(Key<T> key, T value) {
cache.put(key, key.getExpectedType().cast(value));
}
public <T> T get(Key<T> key) {
return key.getExpectedType().cast(cache.get(key));
}
}
shmosel's answer is almost certainly sufficient for what you need; however, it has the slight limitation that you can't store/retrieve a generic type, because you can't get a class literal for a generic type.
Instead, you can use something like Guava's TypeCapture:
abstract class GenericKey<T> {
Type getExpectedType() {
return ((ParameterizedType) getClass().getGenericSuperclass())
.getActualTypeArguments()[0];
}
}
which is a bit of reflective grossness that you shouldn't spend too much time looking at.
Notice that it's abstract, so you have to instantiate like:
new GenericKey<Integer>() {}
This is creating an anonymous subclass of GenericKey, which is part of the magic that makes it work with generic types.
Then, it's basically the same:
public class Cache {
private Map<GenericKey<?>, Object> cache = new HashMap<>();
public <T> void put(GenericKey<T> key, T value) {
cache.put(key.getExpectedType(), value);
}
public <T> T get(GenericKey<T> key) {
return (T) cache.get(key.getExpectedType());
}
}
Now you could have a GenericKey<List<Integer>>, using new new GenericKey<List<Integer>() {}, if you should so desire.
The downside of this approach is that you lose the ability to do checking on the value on the way in/out of the cache, so you could get heap pollution if you are careless with raw types.
I have been working on this solution for months and I have come to the conclusion that there is no clean way to achieve what I am trying to achieve. I feel as though my education in polymorphism is failing me, so I've come to StackOverflow to get a second opinion. Sorry if this seems long and convoluted. That's been my brain for the past couple of months and at this point I'm out of ideas. I'm hoping somebody can take a look and see that I could've avoided all this mess by doing it some other way.
What I am trying to achieve is two generic classes: One that can represent any "saveable" object, and one that can represent a list of saveable objects (or what I call a "store"). A saveable object can save itself using GSON, and a store can also save itself using GSON to a JSON file. The difference being that saveable objects are generically representing any GSON object that can be saved, whereas stores are extending from saveables to become a saveable hash map of objects via IDs.
An example output I am looking for is as so:
Imagine I have an object with a uuid string field and a name string field. I want to be able to create a Store, which is a LinkedHashMap, of these objects, but also extend a Saveable to allow the objects to be saved as so:
test.json
{"dbf39199209e466ebed0061a3491ed9e":{"uuid":"dbf39199209e466ebed0061a3491ed9e","name":"Example Name"}}
I would also like to be able to load this JSON back into the objects via the Store's load method.
An example code usage would be like so:
Store<User> users = new Store<>();
users.load();
users.add(new User("dbf39199209e466ebed0061a3491ed9e", "Example Name"));
users.save();
My Attempts
Saveables
What I expect a "saveable" object to be able to do is as follows: provide a non-argumented method for saving and provide a non-argumented method for loading. A saveable object represents any object that can be saved via GSON. It contains two fields: a Gson gson object and a Path location. I provide those in the constructor of my saveable. I then want to provide two methods: a Saveable#save() method and a Saveable#load() method (or a static Saveable#load() method, I am indifferent). The way you use a Saveable object is by extending it (so it is abstract) to another object representing something, say, TestSaveable, and then the usage is as so:
TestSaveable saveable = new TestSaveable(8);
saveable.save(); // Saves data
saveable.setData(4);
saveable = saveable.load(); // Loads old data
I also would like a saveable object to be able to handle a generic, such as an integer (think of the last example but with an integer generic). This would allow me to execute my next plan for Stores.
My attempt at an implementation was the following:
public abstract class Saveable {
private transient Gson gson;
private transient Path location;
public Saveable(Gson gson, Path location) {
this.gson = gson;
this.location = location;
}
#SuppressWarnings("unchecked")
public <T extends Saveable> T save() throws IOException {
if (location.getParent() != null) {
Files.createDirectories(location.getParent());
}
Files.write(location, gson.toJson(this).getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, LinkOption.NOFOLLOW_LINKS);
return (T) this;
}
protected <T extends Saveable> T load(Class<T> clazz, #NotNull Class<?>... generics) throws IOException {
if (!Files.exists(location)) {
return this.save();
} else {
InstanceCreator<Saveable> creator = type -> this;
Type type = TypeToken.getParameterized(clazz, generics).getType();
Gson newGson = gson.newBuilder().registerTypeAdapter(type, creator).create();
return newGson.fromJson(Files.newBufferedReader(location), type);
}
}
}
Unfortunately, this attempt failed in my goal, because upon making my TestSaveable class users still had to pass the generic through for loading:
public class TestSaveable<T> extends Saveable {
public boolean testBool = false;
public T value;
public TestSaveable(T value) {
super(new Gson(), Path.of("test.json"));
this.value = value;
}
public final TestSaveable<T> load(Class<T> generic) throws IOException {
return super.load(TestSaveable.class, generic);
}
}
However, through this I did get a fairly clean implementation with the exception of little to no type checking at all and constantly having to add supressions for it:
public class Test {
public static void main(String[] args) {
try {
TestSaveable<Integer> storeB4 = new TestSaveable<>(5).save();
storeB4.value = 10;
TestSaveable<Integer> store = storeB4.load(Integer.class);
System.out.println("STORE: " + store);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Stores
Stores are an extension of saveables. A store is a LinkedHashMap which will quickly and easily save all of the objects in it as a map in GSON. Unfortunately, I'm not even sure where to start on this. I cannot extend two objects (the two being a LinkedHashMap<String, T> and a Saveable), but I also cannot use interfaces for the Saveable object.
I previously tried the following using the IStorable and ISaveable classes as an alternative to the abstract Saveable class I've shown you above, but this resulted in another very ugly and non-robust solution to my issue.
Saveable.java
public class Saveable {
// Suppress default constructor
private Saveable() {}
// Save a class to the specified location using the specified gson
public static <T extends ISaveable> T save(T instance) throws IOException {
Files.createDirectories(instance.getLocation().getParent());
Files.write(instance.getLocation(), instance.getGson().toJson(instance).getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, LinkOption.NOFOLLOW_LINKS);
return instance;
}
// Load a file from the specified location using the specified gson and cast it to the specified class using the specified generic
public static <T extends ISaveable> ISaveable load(Path location, Gson gson, Class<T> clazz, Class<?> genericClazz) throws IOException {
if (!Files.exists(location)) {
return null;
} else {
TypeToken<?> type = genericClazz == null ? TypeToken.get(clazz) : TypeToken.getParameterized(clazz, genericClazz);
ISaveable saveable = gson.fromJson(Files.newBufferedReader(location), type.getType());
saveable.setGson(gson);
saveable.setLocation(location);
return saveable;
}
}
}
ISaveable.java
public interface ISaveable {
// Gson
Gson getGson();
void setGson(Gson gson);
// Location
Path getLocation();
void setLocation(Path location);
}
IStorable.java
public interface IStoreable {
String getUuid();
}
Store.java
public class Store<T extends IStoreable> extends LinkedHashMap<String, T> implements ISaveable {
private transient Path location;
private transient Gson gson;
public Store(Path location, Gson gson) {
this.location = location;
this.gson = gson;
}
public Store() {
this.location = null;
this.gson = null;
}
public Store<T> put(T value) {
this.put(value.getUuid(), value);
return this;
}
public Store<T> remove(T value) {
this.remove(value.getUuid());
return this;
}
public Store<T> save() throws IOException {
return Saveable.save(this);
}
#SuppressWarnings("unchecked")
public static <T extends IStoreable> Store<T> load(Path location, Gson gson, Class<T> genericClazz) throws IOException {
ISaveable saveable = Saveable.load(location, gson, Store.class, genericClazz);
if (saveable == null) {
return new Store<T>(location, gson).save();
} else {
return (Store<T>) saveable;
}
}
}
This solution achieved me almost the result I was looking for, but fell short quickly on the loading process as well as just not being a robust solution, excluding the hundreds of Java practices I'm sure to have ruined at this point:
Store<ExampleStoreable> store = Store.load(Paths.get("storetest.json"), new Gson(), ExampleStoreable.class);
store.put(new ExampleStoreable("Example Name"));
store.save();
And before I get any comments saying I shouldn't be posting this on StackOverflow: if not here, where else? Please help point me in the right direction, I'd love to not be left in the dark.
Thanks if anyone is able to help and if not I understand. This isn't the easiest question by any means.
I was extremely close to the correct solution, but my logic just wasn't lining up.
The fixed load method is as follows:
default <T extends ISaveable> T load() throws IOException {
if (!Files.exists(getLocation())) {
return save();
} else {
InstanceCreator<?> creator = type -> (T) this;
Gson newGson = getGson().newBuilder().registerTypeAdapter(getType(), creator).create();
return newGson.fromJson(Files.newBufferedReader(getLocation()), getType());
}
}
Instead of attempting to prevent type erasure, and instead of passing the class every time we call the method, we just... pass it in the constructor. It was really that simple. I don't care about sending the type through the constructor, as long as .load() and .save() do not result in hundreds of lines of repetitive code.
I can't believe I was this close to the solution the whole time. It's incredible to me how simple this was. Guess that's the life of programming, right?
Here is the full class, which I determined was better as an interface called ISaveable.java:
public interface ISaveable {
Type getType();
Gson getGson();
Path getLocation();
/**
* Saves this object.
*
* #param <T> The extended object to cast to.
* #return The object after having been saved.
* #throws IOException Thrown if there was an exception while trying to save.
*/
#SuppressWarnings("unchecked")
default <T extends ISaveable> T save() throws IOException {
Path location = getLocation().toAbsolutePath();
if (location.getParent() != null) {
Files.createDirectories(location.getParent());
}
Files.write(getLocation(), getGson().toJson(this).getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, LinkOption.NOFOLLOW_LINKS);
return (T) this;
}
/**
* Loads this object.
*
* #param <T> The extended object to cast to.
* #return The object after loading the new values.
* #throws IOException Thrown if there was an exception while trying to load.
*/
#SuppressWarnings("unchecked")
default <T extends ISaveable> T load() throws IOException {
if (!Files.exists(getLocation())) {
return save();
} else {
InstanceCreator<?> creator = type -> (T) this;
Gson newGson = getGson().newBuilder().registerTypeAdapter(getType(), creator).create();
return newGson.fromJson(Files.newBufferedReader(getLocation()), getType());
}
}
}
An example implementation:
public class ExampleSaveable implements ISaveable {
private boolean testBoolean = false;
private String myString;
public ExampleSaveable(String myString) {
this.myString = myString;
}
#Override
public Gson getGson() {
return new Gson();
}
#Override
public Type getType() {
return TypeToken.get(ExampleSaveable.class).getType();
}
#Override
public Path getLocation() {
return Path.of("test.json");
}
}
And an example usage is like so:
ExampleSaveable saveable = new ExampleSaveable("My Data!").load();
saveable.myString = "This is a replacement string!";
saveable.save();
On the first run, the output is "My Data!", on the second, the output is "This is a replacement string!"
The corresponding output JSON was:
{"testBoolean":false,"myString":"This is a replacement string!"}
This allowed me to subsequently extend the class to create my Store.
IStorable.java
public interface IStorable {
String getUuid();
}
Store.java
public class Store<T extends IStorable> extends LinkedHashMap<String, T> implements ISaveable {
// GSON & Location
private transient Gson gson;
private transient Path location;
private transient Type type;
/**
* Constructs a new store.
*
* #param gson The gson to use for saving and loading.
* #param location The location of the JSON file.
* #param generic The generic that this instance of this class is using (due to type erasure).
*/
public Store(Gson gson, Path location, Class<T> generic) {
this.gson = gson;
this.location = location;
this.type = TypeToken.getParameterized(Store.class, generic).getType();
}
// Putting
public Store<T> put(T value) {
this.put(value.getUuid(), value);
return this;
}
public Store<T> putAll(T... values) {
for (T value : values) {
this.put(value.getUuid(), value);
}
return this;
}
// Replacing
public Store<T> replace(T value) {
this.replace(value.getUuid(), value);
return this;
}
// Removing
public Store<T> remove(T value) {
this.remove(value.getUuid());
return this;
}
// Implement ISaveable
#Override
public Gson getGson() {
return gson;
}
#Override
public Path getLocation() {
return location;
}
#Override
public Type getType() {
return type;
}
// Setters
public void setLocation(Path location) {
this.location = location;
}
}
I have a problem with Java's Generic System.
In my program is a wrapper for lists, that should have a method to return it's inner list:
public class Wrapper<T, S extends List<T>> {
private S list;
public Wrapper(S list) {
this.list = list;
}
public S get() {
return list;
}
}
Then there is a Context that holds a Map with different Wrappers and a method that returns the list of the wrapper associated with the id:
public class Context {
private Map<String, Wrapper> map;
public Wrappers() {
map.put("a", new Wrapper(ArrayList<String>());
map.put("b", new Wrapper(LinkedList<Integer>());
}
public <T, S extends List<T>> S getList(String id) {
return map.get(id).get();
}
}
Now when I call getList() I want to have a compiler warning or at least a way to realise an error before a ClassCastException gets thrown.
public class Receiver {
public doSomething() {
Context c = new Context();
c.createWrappers();
// Ok
ArrayList<String> list1 = c.getList("a");
LinkedList<Integer> list2 = c.getList("b");
// Compiler error or some way do check in getList().
ArrayList<Integer> list3 = c.getList("a");
LinkedList<String> list4 = c.getList("b");
}
}
I've actually tried a lot of things like changing the Wrapper definition to:
public class Wrapper<T, S extends List>
But when I want to implement the get() function I run into a problem I can either define the function like this:
public List<T> get() {
return list;
}
or like this
public S get() {
return list;
}
In the first example it would still be possible to do this.
public doSomething() {
//...
LinkedList<String> list = c.getList("a");
}
and in the second example it would be possible to do this.
public doSomething() {
//...
ArrayList<Integer> list = c.getList("a");
}
Is there any way to define the get method in a way like this?
public S<T> get() {
return list;
}
It seems to me like there is no way to check both the type of the list and the type of the elements at the same time.
The compiler has no way of knowing what return type is associated with the particular string you passed (strings cannot be made type-safe).
However, you could replace strings with type-safe marker objects:
class ListId<T> {
public ListId(string name) { ... }
public static final ListId<ArrayList<String>> A = new ListId<>("a");
public static final ListId<LinkedList<Integer>> B = new ListId<>("b");
}
public T getList<T>(ListId<T> id)
So here's a slightly tricky question (for me).
I have a generic object. Call it MyObject. This object has a method which returns something of the type T:
public class MyObject<T>
{
private T _t;
public MyObject(T t)
{
_t = t;
}
//...
public T get()
{
return _t;
}
}
(Obviously my "MyObject" does a bit more but that's the gist).
Now, I want to have a map of this type:
Map<String, MyObject<?>> m = new HashMap<>();
I want to be able to fetch maps using some predefined string name, and these maps can be of any MyObject. For example, I could call:
m.put("map_1", new MyObject<String>("String"));
m.put("map_2", new MyObject<Integer>(new Integer(3));
m.put("map_3", new MyObject<Long>(new Long(5));
etc.
But - and here's the tricky part - I want the map to "remember" the parameterized type of MyObject when I fetch some value from the map. Using
m.get("map_1");
would return a
MyObject<Object>
type, since the map was defined as containing
MyObject<?>
values. Thus:
m.get("map_1").get() // <-- This is an Object, not a String!
What modification (if any) is possible, in order to be able to get the correct - full - information regarding the MyObject fetched object, such that invoking the last line (m.get("map_1")) would return a
MyObject<String>
Thanks :)
Amir.
Typesafe Heterogeneous Containers from Joshua Bloch's Effective Java might work here. Basically you add a Class object to represent the type.
public class MyObject<T>
{
private T _t;
private Class<T> type;
public MyObject( Class<T> type, T t)
{
_t = t;
this.type = type;
}
//...
public T get()
{
return _t;
}
public Class<T> getType() { return type; }
}
Then you could do something like this:
public <T> T get( Map<String, MyObject<?>> map, String key, Class<T> type ) {
return type.cast( m.get( key ).get() );
}
Which is safe and will compile, but will throw a runtime error if you get the type wrong.
(Note I didn't actually compile that, so I might have syntax errors floating around. But most folks don't know how to use Class to cast objects.)
You can get the class.
Class c = m.get("map_1").get().getClass();
if (String.class.equals(c)) {
System.out.println("its a String");
}
Here is a full test.
public class GenericsTest {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
Map<String, MyObject<?>> map = new HashMap<>();
MyObject<String> obj = new MyObject<>("hello");
map.put("greeting", obj);
Class c = map.get("greeting").get().getClass();
if (String.class.equals(c)) {
System.out.println("its a String");
}
}
static class MyObject<T> {
T t;
public MyObject(T t) {
this.t = t;
}
T get() {
return t;
}
}
}
The type system only knows about types, not objects, and therefore can not distinguish "key1" from "key2", because both are of type String.
If keys have different types, the easiest way is to encapsulate a weakly typed map, and use reflective casts to prove to the compiler the types are correct:
class Favorites {
private Map<Class<?>,?> map = new HashMap<>();
<V> V get(Class<V> clazz) {
return clazz.cast(map.get(clazz));
}
<V> void put(Class<V> clazz, V value) {
map.put(clazz, value);
}
}
Favorites favs = new Favorites();
favs.put(String.class, "hello");
favs.put(Integer.class, 42);
favs.get(String.class).charAt(1);
I'm attempting to hold node properties in the following manner (this may be inherently wrong):
public class Property<T> {
protected String key;
protected T value;
public Property(String key, T value) {
this.key = key;
this.value = value;
}
}
public class Node {
protected HashMap<String,Property> properties;
public Node() {
properties = new HashMap<>();
}
However, this has the unfortunate side effect of making my a giant mess of casting. I've been reading everything that might be relevant, but nothing seems to address the essentail problem. Here's my current thought:
public void add(String key, Object value) {
if ( ! (value instanceof Property)) {
value = new Property<>(key, value);
}
properties.put(key, (Property)value);
}
public long get(String key, long x) {
return (long)properties.get(key).value;
}
public long[] get(String key, long[] x) {
return (long[])properties.get(key).value;
}
public String get(String key, String x) {
return (String)properties.get(key).value;
}
// etc
Now this is obviously incrediably stupid, but I'm going around in circles trying to simply be able to grab a node property by key, and be assured of it's type based on the key.
It's that simple. A given key must correspond to a given type, both for adding and for getting.
I honestly feel like I'm misunderstanding something fundamental about the nature of Java.
Try this
// a node representing things of type T
public class Node<T> {
protected HashMap<String,Property<T>> properties;
public Node() {
properties = new HashMap<>();
}
// add a T to the map
public void add(String key, T value) {
properties.put(key, new Property<T>(string, value));
}
}
What concerns me about your example, so far, is that a "Node" looks quite a lot like a hashmap Entry. A better question would be "what are you REALLY trying to do"?
A given key must correspond to a given type, both for adding and for getting.
Assuming you mean for the String key to be an identifier for the element and its type, then you're out of luck, it's simply impossible with generics. One option is to define a custom class with appropriately typed field/getters for each known property.
If you mean for the x parameter, then you can use generics to do something like
public <T> T get(String key, T x) {
return (T) properties.get(key).value;
}
but this sets you up for all sorts of ClassCastException. Your compiler should warn against this. (Note, also, that you won't be able to use primitive types directly.)
As the node class can hold properties of any value type, you need to make an unchecked cast anyways. There's no need to overload the get function, you can cast to whatever the expected return type is:
#SuppressWarnings("unchecked")
public <T> T get(String key) {
return (T) properties.get(key).value;
}
Examples:
Node node = new Node();
node.add("x", 123);
node.add("y", "ABC");
node.add("z", new Date());
int valueX = node.get("x"); // cast to integer and autobox to int
String valueY = node.get("y"); // cast to String
Date valueZ = node.get("z"); // cast to Date
String valueFail = node.get("z"); // this will throw a ClassCastException
It is possible to write an externally typesafe implementation of this, though it'll require some internal casting that the compiler can't prove is correct.
class TypeSafeMap {
public static final class Key<T> {
// deliberately empty; we're knowingly using reference equality
}
private final Map<Key<?>, Object> map;
TypeSafeMap() {
this.map = new HashMap<>();
}
public <T> T get(Key<T> key) {
return (T) map.get(key); // cast is safe, but the compiler can't prove it
}
public <T> void put(Key<T> key, T value) {
map.put(key, value);
}
}
class SomewhereElse {
static final Key<Integer> myIntKey = new Key<Integer>();
static final Key<String> myStringKey = new Key<String>();
public void doWhatever(TypeSafeMap myMap) {
int myInt = myMap.get(myIntKey);
String myString = myMap.get(myStringKey);
}
}
...That said, if you know the entire set of keys in advance, you can (and should) make a custom class with appropriately typed fields, rather than trying to squeeze the whole thing into a map-like structure.
public class Node
{
public static void main (String[] args)
{
Node node = new Node();
node.addProperty("a", 12L);
node.addProperty("b", "i'm a string");
long number = node.getProperty("a");
String string = node.getProperty("b");
}
private Map<String, Object> properties = new HashMap<>();
public void addProperty(String key, Object value){
this.properties.put(key, value);
}
public <T> T getProperty(String key){
return (T) this.properties.get(key);
}
}
The OP is trying to handle a collection of different objects, so generics aren't the way forward. What he's trying to do is have type-safe processing of each specific object within a collection. Here's how you could do that using the visitor pattern.
// Implement this interface in something which needs to process
// an item from the collection in a way specific to the type of that item
interface Visitor {
void visit(Circle c);
void visit(Square s);
}
class Collection {
Map<String, Shape> shapes = new HashMap<>();
void add(String key, Shape shape) {
shapes.put(key, shape);
}
// when you want to process what's behind a key, send in a visitor
void visit(String key, Visitor visitor) {
// ask the shape to be visited by the visitor
shapes.get(key).visit(visitor);
}
}
interface Shape {
void visit(Visitor visitor);
}
class Circle implements Shape {
void visit(Visitor visitor) {
// tells the visitor to treat this object as a circle
visitor.visit(this);
}
}
Let's say you wanted something which draws a particular shape from the collection.
class DrawingVisitor implements Visitor {
void visit(Circle c) {
// use properties only a circle has to draw it
graphics2d.ellipse(c.getRadius(), c.getCenterPoint());
}
void visit(Square s) {
graphics2d.rectangle(s.getTopLeft(), s.getBottomRight());
}
}
Etc
Make sense?