3

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);
jahroy
  • 22,322
  • 9
  • 59
  • 108
  • 3
    Could you elaborate on the motivation for the above code? It seems to defeat the purpose of using generics. Could you maybe provide: public T getValue() { return value; } and have the client worry about extracting the "true" value of the attribute by performing the appropriate cast? – eternaln00b Jul 09 '12 at 19:31
  • the client will have to worry about either a list of Strings, a String, a Float, or a long, that's why it could be better to put them in the class –  Jul 09 '12 at 19:45
  • 1
    as eternaln00b said - need more background on what exactly you are trying to do. The code snippet you posted seem to defeat the purpose of Generics. And Not sure what you meant by "each Data objects can then be heterogeous" - if you are receiving a JSON and passing it through GSON to deserialize - a sample JSON would be helpful. – TS- Jul 09 '12 at 20:10
  • Most often, the right answer is to step back and make sure you don't lose track of the type of the value in the first place. Where are you getting this from? Why did you discard the variable type? – Louis Wasserman Jul 09 '12 at 20:11
  • I agree that your code attempts to defeat the purpose of generics. You should either use Objects and cast OR use generics properly to eliminate the need to cast. – jahroy Jul 09 '12 at 20:14
  • If you want to create a long from a double, use `Double.longValue()`. Don't be afraid to read the documentation: http://docs.oracle.com/javase/6/docs/api/java/lang/Double.html – jahroy Jul 09 '12 at 20:19
  • thx jahroy... new Double(d.getValue().toString()).longValue() <- ! –  Jul 09 '12 at 20:23
  • @Louis, I don't konw how to handle a hereterogeous List of 3-tuple Object whose last attribute is a {List, String, FLoat, long}, I wanted clear methods for that –  Jul 09 '12 at 20:25
  • You can use `getValue().longValue()` if you know getValue returns a Double. The whole point of generics would be to put you in a position where you know what type `getValue()` returns. I think you need to do some reading on generics and types in java. – jahroy Jul 09 '12 at 20:28
  • If your list/object is _heterogeneous_, then you probably need to use Objects. This would indicate that generics do not apply. – jahroy Jul 09 '12 at 20:29
  • 2
    Don't use a heterogeneous `List`, write a class with three fields. You should only ever use `List`s when all the elements carry more or less the same meaning. – Louis Wasserman Jul 09 '12 at 20:34
  • yes but,, that's how the message is coming can't change that, what I'm doing is having this list of classes (Data) with attributes int code, int token, Object Value <- now –  Jul 09 '12 at 20:38
  • actually the deal was to pass a list of Strings with a hardcoded separator, but it's as much ugly, but the List would be homogeneous –  Jul 09 '12 at 20:41
  • If you define `value` as an `Object`, then you're not using generics. If your class was generic, it would be `T value` where `T` is the type parameter – jahroy Jul 09 '12 at 20:46

4 Answers4

12

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.

jahroy
  • 22,322
  • 9
  • 59
  • 108
1

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 ..
Sebastian Zarnekow
  • 6,609
  • 20
  • 23
0

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));
    }
}
esaj
  • 15,875
  • 5
  • 38
  • 52
0

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);
}
eternaln00b
  • 1,043
  • 9
  • 18