We send JSON strings from our front end as input to our java code. The Java side turns that into beans using Gson. Now my front end guy approached me with these requirements:
sometimes he wants to pass a new value, that the backend simply writes into the database
sometimes he wants to pass no value, which tells the backend to not do anything about this value
sometimes he wants to pass a null, which tells the backend to reset to some "default value" (known to the backend, but the front end doesn't care about it)
it should also work with strings, numbers, boolean, ...
We came up with this idea:
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import java.lang.reflect.Type;
import java.util.Objects;
import org.junit.Test;
import com.google.gson.*;
class ResetableValue<T> {
private static enum Content {
VALUE, RESET, NOT_PROVIDED
};
private final T value;
private final Content content;
public ResetableValue(T value) {
this(value, Content.VALUE);
}
private ResetableValue(T value, Content content) {
this.value = value;
this.content = content;
}
static <T> ResetableValue<T> asReset() {
return new ResetableValue<>(null, Content.RESET);
}
static <T> ResetableValue<T> asNotProvided() {
return new ResetableValue<>(null, Content.NOT_PROVIDED);
}
T getValue() {
if (content != Content.VALUE) {
throw new IllegalStateException("can't provide value for " + content);
}
return value;
}
boolean isReset() {
return content == Content.RESET;
}
boolean isNotProvided() {
return content == Content.NOT_PROVIDED;
}
#Override
public String toString() {
if (content == Content.VALUE) {
return Objects.toString(value);
}
return content.toString();
}
}
class ResetableValueDeserializer implements JsonDeserializer<ResetableValue<String>> {
public ResetableValue<String> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return new ResetableValue<String>(json.getAsJsonPrimitive().getAsString());
}
}
class ExampleBean {
private ResetableValue<String> property = ResetableValue.asNotProvided();
public ResetableValue<String> getProperty() {
if (property == null) {
return ResetableValue.asReset();
}
return property;
}
#Override
public String toString() {
return "property: " + Objects.toString(property);
}
}
public class GsonStuffTest {
Gson gson = new GsonBuilder().registerTypeAdapter(ResetableValue.class, new ResetableValueDeserializer()).create();
#Test
public void testValue() {
String serializedContent = "{\"property\":\"foo\"}";
ExampleBean bean = gson.fromJson(serializedContent, ExampleBean.class);
assertThat(bean.getProperty().getValue(), is("foo"));
}
#Test
public void testIsNotProvided() {
String serializedContent = "{}";
ExampleBean bean = gson.fromJson(serializedContent, ExampleBean.class);
assertThat(bean.getProperty().isNotProvided(), is(true));
}
#Test
public void testIsReset() {
String serializedContent = "{\"property\":null}";
ExampleBean bean = gson.fromJson(serializedContent, ExampleBean.class);
assertThat(bean.getProperty().isReset(), is(true));
}
}
Please note: the idea is of course to have multiple different fields of that type ResetableValue in a bean. And then one field might care a value, one is omitted, another is set to to null.
Question(s):
The above example "works" - but I really dislike the fact that I have to handle the "reset" case within the getProperty() method of my bean. As that means: it is not enough to have a custom deserializer, I also need to put that special check into any getter method. So: are there more elegant solutions to this? Is there a way to have Gson distinguish between "a property is not showing up" vs. "a property is set to null"?
The above example claims to be generic; but obviously the deserializer code only works for string properties. Is there a way to make this "really generic"?
I guess a different way to express my question is: is there something like "Optionals" support when deserializing JSON into beans using Gson?
The above example "works" - but I really dislike the fact that I have to handle the "reset" case within the getProperty() method of my bean. As that means: it is not enough to have a custom deserializer, I also need to put that special check into any getter method. So: are there more elegant solutions to this? Is there a way to have Gson distinguish between "a property is not showing up" vs. "a property is set to null"?
Sort of. Your getProperty seems to have a redundant check: it should never check for null and just return the property field in any case assuming that Gson managed to instantiate it.
The above example claims to be generic; but obviously the deserializer code only works for string properties. Is there a way to make this "really generic"?
Yes, via type adapter factories and type adapters (regarding the latter: JsonSerializer and JsonDeserializer classes use JSON trees consuming more memory, but type adapters are stream-fashioned and consume much less).
Let's consider you have a generic tri-state value holder like the following one.
I would also hide away the constructor to make it more fluent and encapsulate the way it's instantiated (or not instantiated).
final class Value<T> {
private static final Value<?> noValue = new Value<>(State.NO_VALUE, null);
private static final Value<?> undefined = new Value<>(State.UNDEFINED, null);
private enum State {
VALUE,
NO_VALUE,
UNDEFINED
}
private final State state;
private final T value;
private Value(final State state, final T value) {
this.value = value;
this.state = state;
}
static <T> Value<T> value(final T value) {
if ( value == null ) {
return noValue();
}
return new Value<>(State.VALUE, value);
}
static <T> Value<T> noValue() {
#SuppressWarnings("unchecked")
final Value<T> value = (Value<T>) noValue;
return value;
}
static <T> Value<T> undefined() {
#SuppressWarnings("unchecked")
final Value<T> value = (Value<T>) undefined;
return value;
}
T getValue()
throws IllegalStateException {
if ( state != State.VALUE ) {
throw new IllegalStateException("Can't provide value for " + state);
}
return value;
}
boolean isValue() {
return state == State.VALUE;
}
boolean isNoValue() {
return state == State.NO_VALUE;
}
boolean isUndefined() {
return state == State.UNDEFINED;
}
#Override
public String toString() {
if ( state != State.VALUE ) {
return state.toString();
}
return Objects.toString(value);
}
}
Next, define a simple data bag to hold the values.
Note that you must either declare them as undefined() in order to preserve the null-object semantics, or assign it a null so Gson would take care of it below (you choose).
final class DataBag {
final Value<Integer> integer = null;/* = undefined()*/
final Value<String> string = null;/* = undefined()*/
private DataBag() {
}
}
Some reflection utilities code here to analyze type parameterization and build sub-to-super class hierarchy iterators (I don't know how to create a Java 8 stream from scratch yet):
final class Reflection {
private Reflection() {
}
static Type getTypeParameter0(final Type type) {
if ( !(type instanceof ParameterizedType) ) {
return Object.class;
}
final ParameterizedType parameterizedType = (ParameterizedType) type;
return parameterizedType.getActualTypeArguments()[0];
}
static Iterable<Class<?>> subToSuperClass(final Class<?> subClass) {
return subToSuperClass(Object.class, subClass);
}
static <SUP, SUB extends SUP> Iterable<Class<?>> subToSuperClass(final Class<SUP> superClass, final Class<SUB> subClass) {
if ( !superClass.isAssignableFrom(subClass) ) {
throw new IllegalArgumentException(superClass + " is not assignable from " + subClass);
}
return () -> new Iterator<Class<?>>() {
private Class<?> current = subClass;
#Override
public boolean hasNext() {
return current != null;
}
#Override
public Class<?> next() {
if ( current == null ) {
throw new NoSuchElementException();
}
final Class<?> result = current;
current = result != superClass ? current.getSuperclass() : null;
return result;
}
};
}
}
ValueTypeAdapterFactory
ValueTypeAdapterFactory is responsible for any generic values by delegating (de)serialization process to downstream type adapters.
final class ValueTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory valueTypeAdapterFactory = new ValueTypeAdapterFactory();
private ValueTypeAdapterFactory() {
}
static TypeAdapterFactory getValueTypeAdapterFactory() {
return valueTypeAdapterFactory;
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !Value.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
final Type valueTypeParameter = getTypeParameter0(typeToken.getType());
// Some boring Java unchecked stuff here...
#SuppressWarnings("unchecked")
final TypeAdapter<Object> innerTypeAdapter = (TypeAdapter<Object>) gson.getDelegateAdapter(this, TypeToken.get(valueTypeParameter));
final TypeAdapter<Value<Object>> outerTypeAdapter = new ValueTypeAdapter<>(innerTypeAdapter);
#SuppressWarnings("unchecked")
final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) outerTypeAdapter;
return typeAdapter;
}
private static final class ValueTypeAdapter<T>
extends TypeAdapter<Value<T>> {
private final TypeAdapter<T> innerTypeAdapter;
private ValueTypeAdapter(final TypeAdapter<T> innerTypeAdapter) {
this.innerTypeAdapter = innerTypeAdapter;
}
#Override
public void write(final JsonWriter out, final Value<T> value)
throws IOException {
if ( value.isValue() ) {
final T innerValue = value.getValue();
innerTypeAdapter.write(out, innerValue);
return;
}
// Considering no-value is undefined in order not to produce illegal JSON documents (dangling property names, etc)
if ( value.isNoValue() || value.isUndefined() ) {
innerTypeAdapter.write(out, null);
return;
}
throw new AssertionError();
}
#Override
public Value<T> read(final JsonReader in)
throws IOException {
final JsonToken token = in.peek();
if ( token == NULL ) {
in.nextNull();
return noValue();
}
return value(innerTypeAdapter.read(in));
}
}
}
PostValueTypeAdapterFactory
PostValueTypeAdapterFactory is responsible for "adjusting" POJOs that have null-initialized Value fields by using reflection.
By not registering this factory all Value fields must be initialized with undefined() manually.
Any sequential data structures like iterables/collections/lists|sets, maps or arrays are not implemented here for simplicity.
final class PostValueTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory postValueTypeAdapterFactory = new PostValueTypeAdapterFactory();
private PostValueTypeAdapterFactory() {
}
static TypeAdapterFactory getPostValueTypeAdapterFactory() {
return postValueTypeAdapterFactory;
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
final List<Field> valueFields = collectValueFields(typeToken.getRawType());
if ( valueFields.isEmpty() ) {
return null;
}
final TypeAdapter<T> delegateTypeAdapter = gson.getDelegateAdapter(this, typeToken);
return new PostValueTypeAdapter<>(delegateTypeAdapter, valueFields);
}
// Just scan class the whole type hierarchy (except java.lang.Object) to find any occurrences of Value<T> fields
private static List<Field> collectValueFields(final Class<?> type) {
return StreamSupport.stream(subToSuperClass(type).spliterator(), false)
.filter(clazz -> clazz != Object.class)
.flatMap(clazz -> Stream.of(clazz.getDeclaredFields()))
.filter(field -> field.getType() == Value.class)
.peek(field -> field.setAccessible(true))
.collect(toImmutableList());
}
private static final class PostValueTypeAdapter<T>
extends TypeAdapter<T> {
private final TypeAdapter<T> delegateTypeAdapter;
private final List<Field> valueFields;
private PostValueTypeAdapter(final TypeAdapter<T> delegateTypeAdapter, final List<Field> valueFields) {
this.delegateTypeAdapter = delegateTypeAdapter;
this.valueFields = valueFields;
}
#Override
public void write(final JsonWriter out, final T value)
throws IOException {
delegateTypeAdapter.write(out, value);
}
#Override
public T read(final JsonReader in)
throws IOException {
try {
final T value = delegateTypeAdapter.read(in);
for ( final Field valueField : valueFields ) {
// A Value<T> field is null? Make it undefined
if ( valueField.get(value) == null ) {
valueField.set(value, undefined());
}
}
return value;
} catch ( final IllegalAccessException ex ) {
throw new IOException(ex);
}
}
}
}
JUnit test:
public final class GsonStuffTest {
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(getValueTypeAdapterFactory())
.registerTypeAdapterFactory(getPostValueTypeAdapterFactory())
.create();
#Test
public void testIsValue() {
final DataBag dataBag = parseDataBag("{\"integer\":100,\"string\":\"foo\"}");
assertThat(dataBag.integer.isValue(), is(true));
assertThat(dataBag.integer.getValue(), is(100));
assertThat(dataBag.string.isValue(), is(true));
assertThat(dataBag.string.getValue(), is("foo"));
}
#Test
public void testIsNoValue() {
final DataBag dataBag = parseDataBag("{\"integer\":null,\"string\":null}");
assertThat(dataBag.integer.isNoValue(), is(true));
assertThat(dataBag.string.isNoValue(), is(true));
}
#Test
public void testIsUndefined() {
final DataBag dataBag = parseDataBag("{}");
assertThat(dataBag.integer.isUndefined(), is(true));
assertThat(dataBag.string.isUndefined(), is(true));
}
private static DataBag parseDataBag(final String json) {
return gson.fromJson(json, DataBag.class);
}
}
Related
We're updating a Hibernate (3.6) application that defines a custom type for money, extending org.hibernate.type.ImmutableType. It's been fairly straightforward to make it instead extend AbstractSingleColumnStandardBasicType and create a Java type descriptor to store Money as BigInteger.
However, various parts of the application use HQL queries that perform aggregate functions (usually SUM) on money fields. The old style, extending ImmutableType, automatically converted the result to Money, but with the new style, that's not happening; the result is a Long.
Does anyone know how to make Hibernate custom types automatically convert the result of aggregate functions?
Old user type:
public class MoneyUserType extends ImmutableType {
private final BigIntegerType bigIntegerType = new BigIntegerType();
#Override
public Object fromStringValue(final String string) {
final BigInteger bigInteger = (BigInteger) bigIntegerType.fromStringValue(string);
return Money.inCents(bigInteger);
}
#Override
public Object get(final ResultSet rs, final String name) throws SQLException {
final BigInteger bigInteger = (BigInteger) bigIntegerType.get(rs, name);
if (null == bigInteger) {
return null;
}
return Money.inCents(bigInteger);
}
#Override
public void set(final PreparedStatement st, final Object object, final int index) throws SQLException {
final Money money = (Money) object;
bigIntegerType.set(st, money.getAmountInCents(), index);
}
#Override
public int sqlType() {
return bigIntegerType.sqlType();
}
#Override
public String toString(final Object object) {
final Money money = (Money) object;
return bigIntegerType.toString(money.getAmountInCents());
}
public String getName() {
return Money.class.getName();
}
#SuppressWarnings("unchecked")
public Class getReturnedClass() {
return Money.class;
}
}
New user type:
public class MoneyUserType extends AbstractSingleColumnStandardBasicType<Money> {
public MoneyUserType() {
super(BigIntTypeDescriptor.INSTANCE, MoneyJavaTypeDescriptor.INSTANCE);
}
#Override
public String getName() {
return Money.class.getName();
}
}
public class MoneyJavaTypeDescriptor extends AbstractTypeDescriptor<Money> {
public static final MoneyJavaTypeDescriptor INSTANCE = new MoneyJavaTypeDescriptor();
public MoneyJavaTypeDescriptor() {
super(Money.class, ImmutableMutabilityPlan.INSTANCE);
}
#Override
public Money fromString(final String string) {
final BigInteger bigInteger = BigIntegerTypeDescriptor.INSTANCE.fromString(string);
return Money.inCents(bigInteger);
}
#Override
public <X> X unwrap(Money value, Class<X> type, WrapperOptions options) {
if (value == null) {
return null;
}
if (type.isAssignableFrom(BigInteger.class)) {
return (X) value.getAmountInCents();
}
if (type.isAssignableFrom(Long.class)) {
return (X) Long.valueOf(value.getAmountInCents().longValue());
}
if (type.isAssignableFrom(Integer.class)) {
return (X) Integer.valueOf(value.getAmountInCents().intValue());
}
throw unknownUnwrap(type);
}
#Override
public <X> Money wrap(X value, WrapperOptions options) {
if (value == null) {
return null;
}
if (Number.class.isInstance(value)) {
return Money.inCents((Number) value);
}
throw unknownWrap(value.getClass());
}
#Override
public String toString(final Money money) {
return BigIntegerTypeDescriptor.INSTANCE.toString(money.getAmountInCents());
}
}
Looks like we can work around this by adding extra setters on our DTOs and putting aliases in the queries as per Custom type / converter in conjunction with Hibernate's Transformers.aliasToBean
It's not ideal but it can be made to work.
You can add an INSTANCE static field to your MoneyUserType:
public class MoneyUserType extends AbstractSingleColumnStandardBasicType<Money> {
public static final MoneyUserType INSTANCE = new MoneyUserType();
// ...
}
then add for example the following function to your custom hibernate dialect:
public class MyPostgreSQLDialect extends PostgreSQL10Dialect
{
public MyPostgreSQLDialect()
{
registerFunction("sum_money", new StandardSQLFunction("sum", MoneyUserType.INSTANCE));
}
}
declare this dialect in your persistence.xml or hibernate.cfg.xml
and then you will be able to use the sum_money function in your hql:
Money sum = entityManager.createQuery(
"select sum_money(m.money) from MoneyEntity m",
Money.class
).getSingleResult();
Given 2 Class objects how can I get the Class object of a Map?
For example, assume I have:
Class keyClass = Long.class;
Class valueClass = String.class;
How can I get the Class object ofMap<Long,String>?
There is no such class of Map<Long, String>. What you want is Map.class. (or HashMap.class, etc)
Map<String, Integer> map1 = new HashMap<>();
Map<Long, String> map2 = new HashMap<>();
System.out.println(map1.getClass().equals(map2.getClass()));
The result is true.
Map<Long, String> is not a class, but it is a type, ParameterizedType to be exact, sadly java code for constructing them is private, but they are 2 ways to get it, more dynamic way is to implement that interface:
final class ParameterizedTypeImpl implements ParameterizedType {
private final Type[] actualTypeArguments;
private final Class rawType;
#Nullable private final Type ownerType;
ParameterizedTypeImpl(Class rawType, Type[] actualTypeArguments, #Nullable Type ownerType) {
this.actualTypeArguments = actualTypeArguments.clone();
this.rawType = rawType;
if ((ownerType != null) || (rawType.getDeclaringClass() == null)) {
this.ownerType = ownerType;
}
else {
Class declaringClass = rawType.getDeclaringClass();
if (Modifier.isStatic(rawType.getModifiers())) {
this.ownerType = declaringClass;
}
else {
TypeVariable[] typeParameters = declaringClass.getTypeParameters();
if (typeParameters.length == 0) {
this.ownerType = declaringClass;
}
else {
this.ownerType = new ParameterizedTypeImpl(declaringClass, typeParameters, null);
}
}
}
}
#Override
public Type[] getActualTypeArguments() { return this.actualTypeArguments.clone(); }
#Override
public Class getRawType() { return this.rawType; }
#Nullable #Override
public Type getOwnerType() { return this.ownerType; }
#Override public boolean equals(Object o) {
if (o instanceof ParameterizedType) {
ParameterizedType that = (ParameterizedType) o;
if (this == that) return true;
Type thatOwner = that.getOwnerType();
Type thatRawType = that.getRawType();
return Objects.equals(this.ownerType, thatOwner) && Objects.equals(this.rawType, thatRawType) && Arrays.equals(this.actualTypeArguments, that.getActualTypeArguments());
}
return false;
}
#Override
public int hashCode() {
return Arrays.hashCode(this.actualTypeArguments) ^ Objects.hashCode(this.ownerType) ^ Objects.hashCode(this.rawType);
}
#Override
public String toString() {
StringBuilder sb = new StringBuilder(256);
if (this.ownerType != null) {
sb.append(this.ownerType.getTypeName());
sb.append("$");
if (this.ownerType instanceof ParameterizedTypeImpl) {
sb.append(this.rawType.getName().replace(((ParameterizedTypeImpl) this.ownerType).rawType.getName() + "$", ""));
}
else {
sb.append(this.rawType.getSimpleName());
}
}
else {
sb.append(this.rawType.getName());
}
StringJoiner joiner = new StringJoiner(", ", "<", ">");
joiner.setEmptyValue("");
for (Type type : this.actualTypeArguments) {
joiner.add(type.getTypeName());
}
sb.append(joiner.toString());
return sb.toString();
}
}
And then you can just do new ParameterizedTypeImpl(Map.class, new Type[]{String.class, Long.class}, null) note that it would be a good practice to make this class not visible to others and just create some factory methods.
Other less dynamic way is to use type tokens like in gson:
public class TypeToken<T> {
final Type type;
protected TypeToken() {
this.type = this.getClass().getGenericSuperclass();
}
public final Type getType() { return this.type; }
#Override public final int hashCode() { return this.type.hashCode(); }
#Override public final boolean equals(Object o) { return (o instanceof TypeToken<?>) && this.type.equals(((TypeToken<?>) o).type); }
#Override public final String toString() { return this.type.toString(); }
}
and then new TypeToken<Map<String, Long>>{}.getType(); - types needs to be provided at compile time.
There should be some libraries that provide both this methods, but I don't know any right now as I needed to make my own to also support parsing from string.
Is there a way in Java to have a map where the type parameter of a value is tied to the type parameter of a key? What I want to write is something like the following:
public class Foo {
// This declaration won't compile - what should it be?
private static Map<Class<T>, T> defaultValues;
// These two methods are just fine
public static <T> void setDefaultValue(Class<T> clazz, T value) {
defaultValues.put(clazz, value);
}
public static <T> T getDefaultValue(Class<T> clazz) {
return defaultValues.get(clazz);
}
}
That is, I can store any default value against a Class object, provided the value's type matches that of the Class object. I don't see why this shouldn't be allowed since I can ensure when setting/getting values that the types are correct.
EDIT: Thanks to cletus for his answer. I don't actually need the type parameters on the map itself since I can ensure consistency in the methods which get/set values, even if it means using some slightly ugly casts.
You're not trying to implement Joshua Bloch's typesafe hetereogeneous container pattern are you? Basically:
public class Favorites {
private Map<Class<?>, Object> favorites =
new HashMap<Class<?>, Object>();
public <T> void setFavorite(Class<T> klass, T thing) {
favorites.put(klass, thing);
}
public <T> T getFavorite(Class<T> klass) {
return klass.cast(favorites.get(klass));
}
public static void main(String[] args) {
Favorites f = new Favorites();
f.setFavorite(String.class, "Java");
f.setFavorite(Integer.class, 0xcafebabe);
String s = f.getFavorite(String.class);
int i = f.getFavorite(Integer.class);
}
}
From Effective Java (2nd edition) and this presentation.
The question and the answers made me come up with this solution: Type-safe object map. Here is the code. Test case:
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
public class TypedMapTest {
private final static TypedMapKey<String> KEY1 = new TypedMapKey<String>( "key1" );
private final static TypedMapKey<List<String>> KEY2 = new TypedMapKey<List<String>>( "key2" );
#Test
public void testGet() throws Exception {
TypedMap map = new TypedMap();
map.set( KEY1, null );
assertNull( map.get( KEY1 ) );
String expected = "Hallo";
map.set( KEY1, expected );
String value = map.get( KEY1 );
assertEquals( expected, value );
map.set( KEY2, null );
assertNull( map.get( KEY2 ) );
List<String> list = new ArrayList<String> ();
map.set( KEY2, list );
List<String> valueList = map.get( KEY2 );
assertEquals( list, valueList );
}
}
This is the Key class. Note that the type T is never used in this class! It's purely for the purpose of type casting when reading the value out of the map. The field key only gives the key a name.
public class TypedMapKey<T> {
private String key;
public TypedMapKey( String key ) {
this.key = key;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ( ( key == null ) ? 0 : key.hashCode() );
return result;
}
#Override
public boolean equals( Object obj ) {
if( this == obj ) {
return true;
}
if( obj == null ) {
return false;
}
if( getClass() != obj.getClass() ) {
return false;
}
TypedMapKey<?> other = (TypedMapKey<?>) obj;
if( key == null ) {
if( other.key != null ) {
return false;
}
} else if( !key.equals( other.key ) ) {
return false;
}
return true;
}
#Override
public String toString() {
return key;
}
}
TypedMap.java:
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class TypedMap implements Map<Object, Object> {
private Map<Object, Object> delegate;
public TypedMap( Map<Object, Object> delegate ) {
this.delegate = delegate;
}
public TypedMap() {
this.delegate = new HashMap<Object, Object>();
}
#SuppressWarnings( "unchecked" )
public <T> T get( TypedMapKey<T> key ) {
return (T) delegate.get( key );
}
#SuppressWarnings( "unchecked" )
public <T> T remove( TypedMapKey<T> key ) {
return (T) delegate.remove( key );
}
public <T> void set( TypedMapKey<T> key, T value ) {
delegate.put( key, value );
}
// --- Only calls to delegates below
public void clear() {
delegate.clear();
}
public boolean containsKey( Object key ) {
return delegate.containsKey( key );
}
public boolean containsValue( Object value ) {
return delegate.containsValue( value );
}
public Set<java.util.Map.Entry<Object, Object>> entrySet() {
return delegate.entrySet();
}
public boolean equals( Object o ) {
return delegate.equals( o );
}
public Object get( Object key ) {
return delegate.get( key );
}
public int hashCode() {
return delegate.hashCode();
}
public boolean isEmpty() {
return delegate.isEmpty();
}
public Set<Object> keySet() {
return delegate.keySet();
}
public Object put( Object key, Object value ) {
return delegate.put( key, value );
}
public void putAll( Map<? extends Object, ? extends Object> m ) {
delegate.putAll( m );
}
public Object remove( Object key ) {
return delegate.remove( key );
}
public int size() {
return delegate.size();
}
public Collection<Object> values() {
return delegate.values();
}
}
No, you can't do it directly. You'll need to write a wrapper class around Map<Class, Object> to enforce that Object will be instanceof Class.
It's possible to create a class which stores a map of type safe key to a value, and cast when necessary. The cast in get method is safe, as after using new Key<CharSequence>(), it's not possible to safely cast it to Key<String> or Key<Object>, so the type system enforces the correct usage of a class.
The Key class needs to be final, as otherwise an user could override equals and cause type-unsafety if two elements with different types were to be equal. Alternatively, it's possible to override equals to be final if you want to use inheritance despite the issues with it.
public final class TypeMap {
private final Map<Key<?>, Object> m = new HashMap<>();
public <T> T get(Key<? extends T> key) {
// Safe, as it's not possible to safely change the Key generic type,
// hash map cannot be accessed by an user, and this class being final
// to prevent serialization attacks.
#SuppressWarnings("unchecked")
T value = (T) m.get(key);
return value;
}
public <T> void put(Key<? super T> key, T value) {
m.put(key, value);
}
public static final class Key<T> {
}
}
You can use below 2 classes, Map class: GenericMap, Map-Key class: GenericKey
For example:
// Create a key includine Type definition
public static final GenericKey<HttpServletRequest> REQUEST = new GenericKey<>(HttpServletRequest.class, "HttpRequestKey");
public void example(HttpServletRequest requestToSave)
{
GenericMap map = new GenericMap();
// Saving value
map.put(REQUEST, requestToSave);
// Getting value
HttpServletRequest request = map.get(REQUEST);
}
Advantages
It forces the user to put and get correct types by compilation error
It's doing casing for you inside
Generic Key helps to avoid write the class type each time you calling put(..) or get
No typo mistakes, like if key is 'String' type
GenericMap
public class GenericMap
{
private Map<String, Object> storageMap;
protected GenericMap()
{
storageMap = new HashMap<String, Object>();
}
public <T> T get(GenericKey<T> key)
{
Object value = storageMap.get(key.getKey());
if (value == null)
{
return null;
}
return key.getClassType().cast(value);
}
/**
* #param key GenericKey object with generic type - T (it can be any type)
* #param object value to put in the map, the type of 'object' mast be - T
*/
public <T> void put(GenericKey<T> key, T object)
{
T castedObject = key.getClassType().cast(object);
storageMap.put(key.getKey(), castedObject);
}
#Override
public String toString()
{
return storageMap.toString();
}
}
GenericKey
public class GenericKey<T>
{
private Class<T> classType;
private String key;
#SuppressWarnings("unused")
private GenericKey()
{
}
public GenericKey(Class<T> iClassType, String iKey)
{
this.classType = iClassType;
this.key = iKey;
}
public Class<T> getClassType()
{
return classType;
}
public String getKey()
{
return key;
}
#Override
public String toString()
{
return "[classType=" + classType + ", key=" + key + "]";
}
}
T as a type must be defined generically in the class instance. The following example works:
public class Test<T> {
private Map<Class<T>, T> defaultValues;
public void setDefaultValue(Class<T> clazz, T value) {
defaultValues.put(clazz, value);
}
public T getDefaultValue(Class<T> clazz) {
return defaultValues.get(clazz);
}
}
Alternatively, you can use Paul Tomblin's answer, and wrap the Map with your own object which will enforce this type of generics.
Is there a way in Java to have a map where the type parameter of a value is tied to the type parameter of a key? What I want to write is something like the following:
public class Foo {
// This declaration won't compile - what should it be?
private static Map<Class<T>, T> defaultValues;
// These two methods are just fine
public static <T> void setDefaultValue(Class<T> clazz, T value) {
defaultValues.put(clazz, value);
}
public static <T> T getDefaultValue(Class<T> clazz) {
return defaultValues.get(clazz);
}
}
That is, I can store any default value against a Class object, provided the value's type matches that of the Class object. I don't see why this shouldn't be allowed since I can ensure when setting/getting values that the types are correct.
EDIT: Thanks to cletus for his answer. I don't actually need the type parameters on the map itself since I can ensure consistency in the methods which get/set values, even if it means using some slightly ugly casts.
You're not trying to implement Joshua Bloch's typesafe hetereogeneous container pattern are you? Basically:
public class Favorites {
private Map<Class<?>, Object> favorites =
new HashMap<Class<?>, Object>();
public <T> void setFavorite(Class<T> klass, T thing) {
favorites.put(klass, thing);
}
public <T> T getFavorite(Class<T> klass) {
return klass.cast(favorites.get(klass));
}
public static void main(String[] args) {
Favorites f = new Favorites();
f.setFavorite(String.class, "Java");
f.setFavorite(Integer.class, 0xcafebabe);
String s = f.getFavorite(String.class);
int i = f.getFavorite(Integer.class);
}
}
From Effective Java (2nd edition) and this presentation.
The question and the answers made me come up with this solution: Type-safe object map. Here is the code. Test case:
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
public class TypedMapTest {
private final static TypedMapKey<String> KEY1 = new TypedMapKey<String>( "key1" );
private final static TypedMapKey<List<String>> KEY2 = new TypedMapKey<List<String>>( "key2" );
#Test
public void testGet() throws Exception {
TypedMap map = new TypedMap();
map.set( KEY1, null );
assertNull( map.get( KEY1 ) );
String expected = "Hallo";
map.set( KEY1, expected );
String value = map.get( KEY1 );
assertEquals( expected, value );
map.set( KEY2, null );
assertNull( map.get( KEY2 ) );
List<String> list = new ArrayList<String> ();
map.set( KEY2, list );
List<String> valueList = map.get( KEY2 );
assertEquals( list, valueList );
}
}
This is the Key class. Note that the type T is never used in this class! It's purely for the purpose of type casting when reading the value out of the map. The field key only gives the key a name.
public class TypedMapKey<T> {
private String key;
public TypedMapKey( String key ) {
this.key = key;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ( ( key == null ) ? 0 : key.hashCode() );
return result;
}
#Override
public boolean equals( Object obj ) {
if( this == obj ) {
return true;
}
if( obj == null ) {
return false;
}
if( getClass() != obj.getClass() ) {
return false;
}
TypedMapKey<?> other = (TypedMapKey<?>) obj;
if( key == null ) {
if( other.key != null ) {
return false;
}
} else if( !key.equals( other.key ) ) {
return false;
}
return true;
}
#Override
public String toString() {
return key;
}
}
TypedMap.java:
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class TypedMap implements Map<Object, Object> {
private Map<Object, Object> delegate;
public TypedMap( Map<Object, Object> delegate ) {
this.delegate = delegate;
}
public TypedMap() {
this.delegate = new HashMap<Object, Object>();
}
#SuppressWarnings( "unchecked" )
public <T> T get( TypedMapKey<T> key ) {
return (T) delegate.get( key );
}
#SuppressWarnings( "unchecked" )
public <T> T remove( TypedMapKey<T> key ) {
return (T) delegate.remove( key );
}
public <T> void set( TypedMapKey<T> key, T value ) {
delegate.put( key, value );
}
// --- Only calls to delegates below
public void clear() {
delegate.clear();
}
public boolean containsKey( Object key ) {
return delegate.containsKey( key );
}
public boolean containsValue( Object value ) {
return delegate.containsValue( value );
}
public Set<java.util.Map.Entry<Object, Object>> entrySet() {
return delegate.entrySet();
}
public boolean equals( Object o ) {
return delegate.equals( o );
}
public Object get( Object key ) {
return delegate.get( key );
}
public int hashCode() {
return delegate.hashCode();
}
public boolean isEmpty() {
return delegate.isEmpty();
}
public Set<Object> keySet() {
return delegate.keySet();
}
public Object put( Object key, Object value ) {
return delegate.put( key, value );
}
public void putAll( Map<? extends Object, ? extends Object> m ) {
delegate.putAll( m );
}
public Object remove( Object key ) {
return delegate.remove( key );
}
public int size() {
return delegate.size();
}
public Collection<Object> values() {
return delegate.values();
}
}
No, you can't do it directly. You'll need to write a wrapper class around Map<Class, Object> to enforce that Object will be instanceof Class.
It's possible to create a class which stores a map of type safe key to a value, and cast when necessary. The cast in get method is safe, as after using new Key<CharSequence>(), it's not possible to safely cast it to Key<String> or Key<Object>, so the type system enforces the correct usage of a class.
The Key class needs to be final, as otherwise an user could override equals and cause type-unsafety if two elements with different types were to be equal. Alternatively, it's possible to override equals to be final if you want to use inheritance despite the issues with it.
public final class TypeMap {
private final Map<Key<?>, Object> m = new HashMap<>();
public <T> T get(Key<? extends T> key) {
// Safe, as it's not possible to safely change the Key generic type,
// hash map cannot be accessed by an user, and this class being final
// to prevent serialization attacks.
#SuppressWarnings("unchecked")
T value = (T) m.get(key);
return value;
}
public <T> void put(Key<? super T> key, T value) {
m.put(key, value);
}
public static final class Key<T> {
}
}
You can use below 2 classes, Map class: GenericMap, Map-Key class: GenericKey
For example:
// Create a key includine Type definition
public static final GenericKey<HttpServletRequest> REQUEST = new GenericKey<>(HttpServletRequest.class, "HttpRequestKey");
public void example(HttpServletRequest requestToSave)
{
GenericMap map = new GenericMap();
// Saving value
map.put(REQUEST, requestToSave);
// Getting value
HttpServletRequest request = map.get(REQUEST);
}
Advantages
It forces the user to put and get correct types by compilation error
It's doing casing for you inside
Generic Key helps to avoid write the class type each time you calling put(..) or get
No typo mistakes, like if key is 'String' type
GenericMap
public class GenericMap
{
private Map<String, Object> storageMap;
protected GenericMap()
{
storageMap = new HashMap<String, Object>();
}
public <T> T get(GenericKey<T> key)
{
Object value = storageMap.get(key.getKey());
if (value == null)
{
return null;
}
return key.getClassType().cast(value);
}
/**
* #param key GenericKey object with generic type - T (it can be any type)
* #param object value to put in the map, the type of 'object' mast be - T
*/
public <T> void put(GenericKey<T> key, T object)
{
T castedObject = key.getClassType().cast(object);
storageMap.put(key.getKey(), castedObject);
}
#Override
public String toString()
{
return storageMap.toString();
}
}
GenericKey
public class GenericKey<T>
{
private Class<T> classType;
private String key;
#SuppressWarnings("unused")
private GenericKey()
{
}
public GenericKey(Class<T> iClassType, String iKey)
{
this.classType = iClassType;
this.key = iKey;
}
public Class<T> getClassType()
{
return classType;
}
public String getKey()
{
return key;
}
#Override
public String toString()
{
return "[classType=" + classType + ", key=" + key + "]";
}
}
T as a type must be defined generically in the class instance. The following example works:
public class Test<T> {
private Map<Class<T>, T> defaultValues;
public void setDefaultValue(Class<T> clazz, T value) {
defaultValues.put(clazz, value);
}
public T getDefaultValue(Class<T> clazz) {
return defaultValues.get(clazz);
}
}
Alternatively, you can use Paul Tomblin's answer, and wrap the Map with your own object which will enforce this type of generics.
I have a class Data<T>
with a generic attribute
private T value;
is there nicer way to do the following?
ie returning the generic type in different forms?
public List<String> getValues() {
if (value.getClass() != ArrayList.class)
throw new Exception("Wrong Enum value '%s'", value);
return (ArrayList<String>) value;
//ugly
}
public String getStringValue() {
if (value.getClass() != String.class)
throw new Exception("Wrong value type '%s'", value);
return (String) value;
//ugly
}
public Float getFloatValue() {
if (value.getClass() != Double.class)
throw new Exception("Wrong value type '%s'", value);
return (Float) value;
//ugly
}
public Long getLongValue() {
if (value.getClass() != Double.class)
throw new Exception("Wrong value type '%s'", value);
return (Long) value;
//ugly
}
public T getValue() {
return value;
}
Precision, I'm using Gson as deserializer, to get a List, each Data objects can then be heterogeous
Could also register adapters for float and long detection, but it wouldn't be faster or nicer
edit: gson fails to retrieve longs:
either:
((Long) d.getValue())
java.lang.Double cannot be cast to java.lang.Long
or
Long.parseLong( d.getValue().toString())
java.lang.NumberFormatException: For input string: "212231.0"
I tried to register a LongAdpater
gsonBuilder.registerTypeAdapter(Long.class, new LongAdapter());
private static class LongAdapter implements
JsonSerializer<Long>, JsonDeserializer<Long>
{
#Override public Long deserialize(
JsonElement json,
Type type,
JsonDeserializationContext arg2) throws JsonParseException
{
return json.getAsLong();
}
#Override
public JsonElement serialize(Long l, Type arg1,
JsonSerializationContext arg2) {
return new JsonPrimitive(new Double(l));
}
}
java.lang.IllegalArgumentException: Cannot register type adapters for class java.lang.Long
edit2 for tsOverflow:
Data<Float> d1 = new Data<Float>( new Float(6.32));
List<String> l = new ArrayList<String>();
l.add("fr");
l.add("it");
l.add("en");
Data<List<String>> d2 = new Data<List<String>>( l);
Data<Long> d3 = new Data<Long>(new Long(212231));
List<Data> data = new ArrayList<Data>();
data.add(d1);
data.add(d2);
data.add(d3)
new Gson().toJson(data);
The point of generics is NOT to allow a class to use different types at the same time.
Generics allow you to define/restrict the type used by an instance of an object.
The idea behind generics is to eliminate the need to cast.
Using generics with your class should result in something like this:
Data<String> stringData = new Data<String>();
String someString = stringData.getValue();
Data<Long> longData = new Data<Long>();
Long someLong = longData.getValue();
Data<List<String>> listData = new Data<List<String>>();
List<String> someList = listData.getValue();
You should either use Objects and casting --OR-- use generics to avoid casting.
You seem to believe that generics allow for heterogeneous typing within the same instance.
That is not correct.
If you want a list to contain a mixed bag of types, then generics are not appropriate.
Also...
To create a long from a double, use Double.longValue().
To create a float from a double, use Double.floatValue().
I recommend reading the documentation.
The design looks suspicious to me, but to answer your actual question:
The case for Long-values looks wrong. Your snippet contains a c&p error
public Long getLongValue() {
if (value.getClass() != Double.class) // <<- should be Long.class
throw new Exception("Wrong value type '%s'", value);
return (Long) value;
//ugly
}
thus it should read:
public Long getLongValue() {
if (value.getClass() != Long.class)
throw new Exception("Wrong value type '%s'", value);
return (Long) value;
//ugly
}
However, in order to reduce code duplication, you could introduce a generic helper method
private T getValue() {
return value;
}
private <V> V castValue(Class<V> type) {
if (!type.isInstance(value)) {
// exception handling
}
return type.cast(value);
}
public List<String> getValues() {
return castValue(ArrayList.class);
}
public String getStringValue() {
return castValue(String.class);
}
If you decide to go for that approach, I'd recommend to de-generify the data class since it's irritating to have a type parameter if there is actually no constraint on the instance itself. I'd use Object instead for the field type:
private Object getValue() {
return value;
}
private <V> V castValue(Class<V> type) {
if (!type.isInstance(value)) {
// exception handling
}
return type.cast(value);
}
public List<String> getValues() {
return castValue(ArrayList.class);
}
public String getStringValue() {
return castValue(String.class);
}
// .. more cases ..
You could just use the type T directly for a simple getter and Class.cast -method for other types:
public class GenericDataTest
{
private static class DataTest<T>
{
private T value;
public DataTest(T value)
{
this.value = value;
}
public T getValue()
{
return value;
}
public Object getValueAsType(Class<?> type)
{
return type.cast(value);
}
}
#Test
public void testGeneric()
{
DataTest<String> stringTest = new DataTest<String>("Test");
Assert.assertEquals("Test", stringTest.getValue());
Assert.assertEquals("Test", stringTest.getValueAsType(String.class));
DataTest<Double> doubleTest = new DataTest<Double>(1.0);
Assert.assertEquals(1.0, doubleTest.getValue());
Assert.assertEquals(1.0, doubleTest.getValueAsType(Double.class));
}
#Test(expected = ClassCastException.class)
public void testClassCastFailure()
{
DataTest<String> stringTest = new DataTest<String>("Test");
Assert.assertEquals("Test", stringTest.getValueAsType(Float.class));
}
}
You could ask if "value" is assignable to the expected class.
private T value;
.
.
.
public Object getValueAsObjectOfClass(Class<?> expectedClass) {
if(!expectedClass.isAssignableFrom(value.getClass())) {
// abort gracefully
}
return expectedClass.cast(value);
}