6

Consider the following generic converter class (simplified/incomplete for brevity)...

public abstract class BaseConverter <TModel> { public void convert(String data, Class<TModel> classOfModelType) { } public void convert(String data) { convert(data, TModel.class); // This doesn't work! } } 

Externally in the subclass I can call the first convert without issue. Consider this subclass with Car as the generic type argument. I can pass in Car.class, like so...

public class CarConverter extends BaseConverter<Car> { public void someFunction() { String someString; convert(someString, Car.class); } } 

What I'm trying to do is move as much as possible to the base class. Since 'Car' is known here and therefore convert expects Car.class, and TModel represents Car in the base class (for this concrete BaseConverter anyway), I was trying to use TModel.class in the base class, but as stated, it doesn't work.

So how do you achieve this in Java?

5
  • I don't think there is an equivalent. The type is unknown at compile time, so what would the compiler substitute? You pass in the class type in convert, why not use that same technique? Add a class type to the ctor, it'll give you a base class to use for the converter. Commented Aug 3, 2017 at 18:51
  • But that's just it... it is known at compile time. For instance, if I add a function in BaseConverter that returned TModel, if you look at the signature on the subclass, it would be returning Car because the compiler knows that type. The point is the compiler does know what's passed into it or else it couldn't resolve any of the usages. Other languages allow this. Guess it's just a limitation of Java. Commented Aug 3, 2017 at 18:58
  • Actually I think what you want to achieve in the second convert method is what the first convert exactly does. The Class<TModel> will be the Class object of the generic type argument of the subclass. Commented Aug 3, 2017 at 19:01
  • Maybe what you wanted to achieve in the first convert should have been something like: public void convert(String data, Class<? extends TModel> classOfModelType) { } Commented Aug 3, 2017 at 19:02
  • But isn't TModel only a placeholder for the type? How can you extend something that doesn't actually exist? I admit I'm new to Java so I'm not sure what this is as I've never seen '? extends TModel' before. Commented Aug 3, 2017 at 19:09

3 Answers 3

5

This is a limitation of generics in Java. The generics only exist at compile time, i.e. at runtime the object is just a BaseConverter, and so you can't query it about its generic type. The easiest solution is usually to pass in a Class<TModel> object (as you are doing) when you call the method. You can also pass it to the constructor for BaseConverter if you don't want to have to pass it in multiple times.

Sign up to request clarification or add additional context in comments.

3 Comments

It's just frustrating because as you said, at runtime it's compiled to BaseConverter essentially replacing TModel with Car. In other words, if you have four subclasses that pass in four different TModel types, you end up with four concrete classes (i.e. all types are known at compile time) and the generic doesn't actually exist anywhere. Why it can't get the class at compile time in that case is beyond me and very frustrating.
You don't end up with 4 classes, but with just one: the one in which TModel is replaced with Object. So the initial type information is lost (the actual term is erased) and it cannot be restored.
Yeah, I just read that. That's how Java differs from other languages where Generics are first-class citizens. There you do end up with four new concrete types. Such a disappointment how they implemented it here in Java when being spoiled with how things could be.
3

Store a reference to the concrete Class by accepting it in the BaseConverter constructor. Something like this:

public abstract class BaseConverter <TModel> { private Class<TModel> clazz; public BaseConverter(Class<TModel> clazz) { this.clazz = clazz; } public void convert(String data, Class<TModel> classOfModelType) { } public void convert(String data) { convert(data, clazz); } } public class CarConverter extends BaseConverter<Car> { public CarConverter() { super(Car.class); } public void someFunction() { String someString; convert(someString, Car.class); } } 

3 Comments

While I know I can do that, and I do appreciate that I can then omit it in other cals, I was trying to infer as much as possible from the generic's type so I wouldn't have to do that. After all, at compile time, internally it will know they are Car types, so how can it not know the Car class? Other languages allow this kind of thing so it looks to be a limitation of the Java compiler/language, not the design of generics themselves.
At compile time, in BaseConverter, all it knows is the generic type TModel. Why would the compiler think TModel = Car? After all, it's not like CarConverter is the only class that can extend BaseConverter.
But that's just it... the compiler (in other languages) is not compiling TModel. They are compiling Car and all the other instances. If you create ten subclasses of a single generic, specifying ten different type parameters, you end up with ten concrete types, again, in other languages. I only found out today about Java's type erasure which in the case I just described you only have one type, not ten, and TModel is essentially changed to Object. That was the missing piece of information. I incorrectly assumed that like in other languages, Java did the same thing, but it doesn't.
3

Generics are erased during compilation and do not exist at run time, so T.class is not possible to do. If you have BaseConverter<T> in your code, it will become BaseConverter<Object> at run time.

But you can save the class yourself:

public abstract class BaseConverter <TModel> { private final Class<TModel> clazz; protected BaseConverter(Class<TModel> clazz) { this.clazz = clazz; } public void someFunction() { String someString; convert(someString, clazz); // use class here } } public class CarConverter extends BaseConverter<Car> { public CarConverter() { super(Car.class); } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.