2

I'm trying to do something like this:

class MyClass<T extends Number> {
    T value;
    NumberFormat formatter = NumberFormat.getInstance();
    public void setValueFromString(String s) {
        try {
            value = formatter.parse(s).doubleValue();
        } catch (ParseException e) { }
    }
}

Except, of course, doubleValue() isn't always the right method to use -- for example, if T is Integer, I want to use intValue(). The only way I have found so far to deal with this is to make the setValueFromString() method abstract and only define it in subclasses when T has been specified.

Is there a way to define the setValueFromString() method in the generic class where it tests T and selects the appropriate conversion method from Number? I have tried instanceof, Class.isInstance(), and Class.isAssignableFrom(), but none of those worked, giving compile-time errors about not being allowed to use T that way.

user128390
  • 139
  • 2
  • 1
    There's nothing wrong with the abstract approach. Just subclass it statically like `MyClass.OfInteger`, `MyClass.OfDouble` for whatever types you need. It's 5 minutes tops of boilerplate code and you're done. – Radiodef May 15 '15 at 20:58
  • you do realize that `NumberFormat.parse(s)` does not actually exist right? [`.parse(s)` is not a `static` method](http://docs.oracle.com/javase/7/docs/api/java/text/NumberFormat.html#parse(java.lang.String)) –  May 15 '15 at 20:58
  • There is already a very good question on this topic: http://stackoverflow.com/questions/182636/how-to-determine-the-class-of-a-generic-type – mrak May 15 '15 at 21:01
  • @Jarrod -- right, it isn't static, edited with the appropriate factory call – user128390 May 15 '15 at 21:02
  • 1
    @mrak - doing branching logic based on `Type` is an anti-pattern at best and just adds complexity where there is none needed. You have to have an implementation per `Type` why not just code the conversion in there instead of this ridiculously complex approach. The entire approach is wrong. This is an X-Y Problem at best. –  May 16 '15 at 14:01

1 Answers1

3

You have to have a concrete class per Type regardless:

Trying to infer something like this from Generics at Runtime and branch the logic based on that information is only a path of pain and makes your code extremely hard to understand and easier to break.

What you are trying to do is more like an abuse of the type system than leveraging it. As soon as you get to testing for types with instanceof or .getClass() or any of the TypeToken magic you are probably doing it wrong, especially for something that can be done without all that mumbo-jumbo.

There are only so many Number subclasses, just hard code them and chain them where the first non-null return value from a Converter is the appropriate type.

Guava has something called Converter just for things like this.

For example:

final static ShortConverter sc = new ShortConverter();
final static IntegerConverter ic = new IntegerConverter();
final static DoubleConverter dc = new DoubleConverter();

public static void main(String[] args) throws ParseException
{
    System.out.println("getNumber(\"1\").getClass().toString() = " + getNumber("1").getClass().toString());
    System.out.println("getNumber(\"64000\").getClass().toString() = " + getNumber("64000").getClass().toString());
    System.out.println("getNumber(\"3.14\").getClass().toString() = " + getNumber("3.14").getClass().toString());
}

public static Number getNumber(@Nonnull final String s)
{
    /* nest the firstNonNull() as deeply as you need for the limited
       number of types */
    return firstNonNull(sc.doForward(s), firstNonNull(ic.doForward(s), dc.doForward(s)));
}

public static class DoubleConverter extends Converter<String, Double>
{
    @Override
    protected String doBackward(@Nonnull final Double d) { return d.toString(); }

    @Override
    protected Double doForward(@Nonnull final String s) { return Doubles.tryParse(s); }
}

public static class IntegerConverter extends Converter<String, Integer>
{
    @Override
    protected String doBackward(@Nonnull final Integer i) { return i.toString(); }

    @Override
    protected Integer doForward(@Nonnull final String s) { return Ints.tryParse(s); }
}

public static class ShortConverter extends Converter<String, Short>
{
    @Override
    protected String doBackward(@Nonnull final Short s) { return s.toString(); }

    @Override
    protected Short doForward(@Nonnull final String s) { try { return Short.parseShort(s); } catch (NumberFormatException e) { return null; } }
}

Outputs:

getNumber("1").getClass().toString() = class java.lang.Short
getNumber("64000").getClass().toString() = class java.lang.Integer
getNumber("3.14").getClass().toString() = class java.lang.Double

In case how to apply this to the question is not obvious:

public class MyClass<T extends Number> 
{
    T value;
    final Converter<String,T> converter;

    public MyClass(final Converter<String,T> c)
    {
       this.converter = c;
    }

    public void setValueFromString(String s) 
    {
        this.value = this.converter.doForward(s);
    }
}