2

I am working on a class that will have functionality similar to JTable's setDefaultRenderer method. I want to have Class-specific formatters which convert objects to strings suitable for displaying.

public interface Formatter<T> {
    String format(T value);
}

private Map<Class<?>, Formatter<?>> formatters;

public <T> void addFormatter(Formatter<T> formatter) {
    formatters.put(T.class, formatter);
}

That's the code I have right now, but Java doesn't accept T.class.

error: cannot select from a type variable
        formatters.put(T.class, formatter);
                        ^
1 error

Is there a way to write this without passing a separate Class<T> parameter? I'm trying to avoid that. It seems redundant.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • That's the third time this has been asked this week: http://stackoverflow.com/questions/17815218/how-to-set-names-of-classes, http://stackoverflow.com/questions/17784067/getting-an-enumerated-value-out-of-an-android-spinner – Paul Bellora Jul 24 '13 at 18:36

6 Answers6

6

No, it's impossible because of generic type erasure in Java. All information about a generic class is lost at runtime, the only solution is to explicitly pass around the class as a parameter.

Óscar López
  • 232,561
  • 37
  • 312
  • 386
2

Not a solution but a hack.

You can do it. But through dirty tricks and reflection. Look at below code for example. Courtesy here:

class ParameterizedTest<T> {

/**
 * @return the type parameter to our generic base class
 */
@SuppressWarnings("unchecked")
protected final Class<T> determineTypeParameter() {
    Class<?> specificClass = this.getClass();
    Type genericSuperclass = specificClass.getGenericSuperclass();
    while (!(genericSuperclass instanceof ParameterizedType) && specificClass != ParameterizedTest.class) {
        specificClass = specificClass.getSuperclass();
        genericSuperclass = specificClass.getGenericSuperclass();
    }
    final ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;

    final Type firstTypeParameter = parameterizedType.getActualTypeArguments()[0];
    return (Class<T>) firstTypeParameter;
}



}

//change the type of PrameterizedTest<Integer> to Parameterized<String> or something to    display different output
public class Test extends ParameterizedTest<Integer>{
 public static void main(String... args){
    Test test = new Test();
    System.out.println(test.determineTypeParameter());
}
}

Here on the runtime, you get the Type Parameter. So instead in your class, you will have to define a Class object which gets the class as explained above. Then using Class.newInstance you get a new Object. But you will have to manually handle type cast and so on.

The question is: Is all this worth it??

No according to me as most of it can be avoided by using bounds in generic types and interfacing to the bound type. So you should be looking for alternative solution

Jatin
  • 31,116
  • 15
  • 98
  • 163
  • 1
    The conclusion is really important. It is not worth doing it this way. But interesting way... – Christian Kuetbach Jul 24 '13 at 18:24
  • 1
    This doesn't actually work in general. It will only work if every `Formatter` implementation implements `Formatter` at a particular, concrete type. For instance, if you have a `MyToStringFormatter` implements formatting just by calling `toString`, there's not a type parameter to find in the superclass chain, just the variable `T`. – jacobm Jul 24 '13 at 18:24
  • @jacobm I agree. Hence I said this is not the solution but a interesting hack around if at all possible – Jatin Jul 24 '13 at 18:30
  • This has nothing to do with the question. Of course the declarations metadata of a class are stored. That is not what this question is about. He has an object in the code, and he wants to know its "parameter". Such a thing does not exist. – newacct Jul 24 '13 at 21:08
1

Due to type-erasure T.class is not available at runtime, so you would have to pass in a separate parameter. In general, generic type-information is not available at runtime (unless you are using unbounded wildcards).

Vivin Paliath
  • 94,126
  • 40
  • 223
  • 295
1

That's not possible in Java language. All the generic types are determited at compilation time, so you can't access class value in execution. So you need to pass the Class<T> parameter as well in order to be able to access it.

public Class MyGenericClassContainer<T>{

    public T instance;

    public Class<T> clazz;

    public MyGenericClassContainer(Class<T> clazz){
        intance = clazz.newInstance();
    }

}
Aritz
  • 30,971
  • 16
  • 136
  • 217
1

In general it's not possible. In your case, though, it might be reasonable to have Formatter implement a Class<T> getFormatterTargetClass() method, which you could then use instead of T.class in your code.

jacobm
  • 13,790
  • 1
  • 25
  • 27
1

An instructive exercise is to ask yourself, "How would I do this without generics?" The answer to that is also the answer to your question.

A program with generics can be written into an equivalent program without generics, by removing generics and inserting casts in the right places, without changing anything else in the code. This transformation is called "type erasure". This means that if something cannot be written without generics, then it cannot be written with generics either.

Without generics, your code looks like this:

public interface Formatter {
    String format(Object value);
}

private Map formatters;

public void addFormatter(Formatter formatter) {
    formatters.put(?, formatter);
}

So, I ask you, how would you do it?

newacct
  • 119,665
  • 29
  • 163
  • 224