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?
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 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);
}
}
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);
In the below code, interface Dictionary has some methods using Object type as parameter.
/* Dictionary.java */
package cs61b.homework6.dict;
public interface Dictionary {
public int size();
public boolean isEmpty();
class Entry {
protected Object key;
protected Object value;
public Object key() {
return key;
}
public Object value() {
return value;
}
}
public Entry insert(Object key, Object value);
public Entry find(Object key);
public Entry remove(Object key);
public void makeEmpty();
}
below is the implementation class HashTableChained of interface Dictionary,
/* HashTableChained.java */
package cs61b.homework6.dict;
import java.util.ArrayList;
import java.util.Iterator;
import JavaCollections.list.DblyLinkList;
public class HashTableChained implements Dictionary {
private long tableSize;
private ArrayList<DblyLinkList<Entry>> defTable;
public HashTableChained(long sizeEstimate) {... }
public HashTableChained() { ... }
private static boolean isPrime(long n) { ...}
private static long nextPrime(long previous) { .. }
int compFunction(int code) { ... }
public int size() { ... }
public boolean isEmpty() { ... }
public Entry insert(Object key, Object value) { ... }
public Entry find(Object key) { ... }
public Entry remove(Object key) { ... }
public void makeEmpty() { ...}
}
I would like to understand, Is there an advantage of introducing interface Dictionary<K, V> syntax with K key and V value?
Note: Java beginner. Complete code is available here. Teacher encourages to write own packages instead of using java.util collection package.
There is an advantage. It will keep you safe(r) during compilation by verifying you're not doing anything completely wrong (like putting a key or a value of the wrong type).
It will also remove (most of) the need to cast in your code when using the map.
if you use Entry with Objects as keys and values for doing a word count:
Dictionary dict = new Dictionary();
dict.insert("word", new Integer(42));
Object count = dict.find("word"); // gives an Object, not an Integer
// need to cast - annoying, not safe
Integer countAsInteger = (Integer)count;
If you introduce generics:
Dictionary dict = new Dictionary<String, Integer>();
dict.insert("word", new Integer(42));
Integer count = dict.find("word"); // gives an Integer
The generic typing also protects you from creating a heterogeneous map. In you implementation this is allowed:
dict.insert("word", "42");
But it was probably an error. You intended the count to be an Integer.
In the generic implementation you will be able to implement:
public void insert(K key, V value);
Which will not allow (at compilation time) anything other than K and V in the map.
I know this sounds a little crazy but here it is. I have an enum type that represents represents a bunch of different properties. Each could be just a string but it would be nice to enforce some kind of type safety. So basically check the type associated with each enum value and throw an exception if there is a mismatch. I guess it could be done with instance of but I am curious if there is another way to do this without instanceof. I know that may not be possible but I am curious.
Edit, I created a new example that I think illustrates what I am asking better:
public class CmisProperties {
public enum CmisPropEnum{
Name (PropertyIds.NAME, new String() ),
CreatedBy (PropertyIds.CREATED_BY, new String() ),
CreationDate (PropertyIds.CREATION_DATE, new Date() ),
LastModifiedBy (PropertyIds.LAST_MODIFIED_BY, new String() ),
LastModificationDate (PropertyIds.LAST_MODIFICATION_DATE, new Date() ),
ChangeToken (PropertyIds.CHANGE_TOKEN, new String() );
private String propId;
CmisPropEnum ( String propId , Object templateObject ){
this.propId = propId;
}
public <T> String getPropId(){
return propId;
}
}
private Map<CmisPropEnum, Object> propertyMap = new HashMap<CmisPropEnum, Object>();
public Object getProperty(CmisPropEnum propEnum){
return propertyMap.get(propEnum.getPropId());
}
public void setProperty( CmisPropEnum propEnum, Object value){
propertyMap.put(propEnum, value);
}
}
Later on I want this to happen:
CmisProperties props = new CmisProperties();
/* This causes a compile time exception */
props.setProperty(CmisPropEnum.CreationDate, "foobar" );
/* This I want to be ok, because the type matches that in the enum */
props.setProperty(CmisPropEnum.CreationDate, new Date() );
Check out Josh Bloch's Effective Java, Item 29, where he describes a "typesafe heterogeneous container" that he calls Favorites. The API is
public class Favorites {
public <T> void putFavorite(Class<T> type, T instance);
public <T> T getFavorite(Class<T> type);
}
I think it would fit your needs (probably change the name???). You could call
favorite.putFavorite(Name.getClass(), "Fred");
favorite.putFavorite(ADate.getClass(), new Date(1234));
and later
Date date = favorite.getFavorite(ADate.getClass());
As already mentioned by irreputable, you need classes to have variability based on types (i.e. generics). This is a corresponding generic version of your example:
public class Properties {
public static class Property<E> {
private Property(String name) { this.name = name; }
private final String name;
public String getName() { return name; }
}
public static final Property<String> NAME = new Property<String>("name");
// ... other properties
private Map<Property<?>, Object> propertyMap =
new HashMap<Property<?>, Object>();
#SuppressWarnings("unchecked")
public <E> E getProperty(Property<E> property){
return (E) propertyMap.get(property);
}
public <E> void setProperty(Property<E> property, E value){
propertyMap.put(property, value);
}
}
The usage is type-safe and checked at compile-time:
Properties p = new Properties();
p.setProperty(Properties.NAME, "a string"); // only strings allowed for NAME
String s = p.getProperty(Properties.NAME); // can only get strings for NAME
Enums can't be generic, so we need a normal class
public class Prop<T>
{
// some predefined props
static public final Prop<String> NAME = new Prop<>("Name", String.class);
...
public Prop(String name, Class<T> type) // it's ok, anyone can create new kind of Prop
{...}
Class<T> getClassT() {...}
}
then set/get property methods can have stronger static type checking:
private Map< Prop,Object > propMap = new HashMap<>();
public <T> void setProperty(Prop<T> key, T value){
propMap.put(key, value);
}
#SuppressWarnings("unchecked")
public <T> T getProperty(Prop<T> key)
{
return (T)propMap.get(key);
}
so that this won't compile
setProperty(Prop.NAME, new Integer(1)); // fail
int x = getProperty(Prop.NAME); //fail
note that, each entry in the propMap has a key Prop<X> and value X for some X, and X can be different from entry to entry. We cannot really express that constraint on Map in Java; but the constraint is indeed enforced by app logic (i.e. setProperty() only inserts such entries)
In getProperty we must suppress unchecked warning. It is justified, since we know the value for the key must be of type T, due to previously mentioned constraint. One trick to avoid explicitly suppressing the warning is by Class.cast()
public <T> T getProperty(Prop<T> key)
{
return key.getClassT().cast( propMap.get(key) );
}
but it's only a trick since essentially we moved #SupressWarnings to Class.cast(). This is a worse version in performance and in semantic clarity.