3

I am facing a confusing issue in my code.

I have one method which signature is

public <T extends Measure> void sendNewMeasure(Class<T> type, T measure);

In another class, I have this method, which calls the previous one :

public <T extends Measure> void onNewMeasure(NewMeasureEvent<T> event) {
    T measure = event.getMeasure();

    APIInterface.getInstance().sendNewMeasure(measure.getClass(), measure);
}

The error I get is Wrong argument type, found 'T', required <? extends com.blablabla.Measure> but I don't get why, as the measure object is of type T which extends Measure.

Is there any way to fix this, and most importantly, why is it not working ?

Thanks !

EDIT :

This is the implementation of the sendNewMeasure method :

public <T extends Measure> void sendNewMeasure(Class<T> type, T measure) {
    String measureType = measure.getJSONMeasureTypeName();

    List<T> measures = T.find(type, measure.timestamp, true, false);

    measures.add(measure);
    sendMeasures(siteId, sensorId, measureType, measures);
}

EDIT 2 : And this is the find method signature, the one I cannot change:

public static <T> List<T> find(Class<T> type, int timestamp, boolean includeStart, boolean inclueEnd);
newacct
  • 119,665
  • 29
  • 163
  • 224
ErGo_404
  • 1,801
  • 4
  • 18
  • 22
  • 2
    In your 1st method you have 2 parameters, but when you call it your have 4 arguments. Can you fix the code? – Peter Lawrey Dec 30 '15 at 09:50
  • Yes, sorry, I had removed some irrevelant parameters from the signature but not from the method call itself. I edited the question. – ErGo_404 Dec 30 '15 at 09:52
  • 1
    You can specify the generic rather than let it infer it (perhaps incorrectly) `APIInterface.getInstance().sendNewMeasure(measure.getClass(), measure);` – Peter Lawrey Dec 30 '15 at 09:55
  • Hi, basically your first parameter should be subtype of "Measure". Use .class instead .getclass() – Sunny Dec 30 '15 at 09:55
  • Possible duplicate of [How to get a class instance of generics type T](http://stackoverflow.com/questions/3437897/how-to-get-a-class-instance-of-generics-type-t) - the question's not quite the same, but I suspect the accepted answer is getting at your problem. – DaveyDaveDave Dec 30 '15 at 09:56
  • @PeterLawrey : I does fix my error, but then there is a new error, the first parameter is of type Class extends Measure> but is expected to be Class. – ErGo_404 Dec 30 '15 at 10:00
  • Why do you need a Class argument in sendNewMeasure()? You can get the class by calling getClass() on the measure argument. – JB Nizet Dec 30 '15 at 10:01
  • In this instance, would it not be easier to just use Measure directly and let the inheritance take care of it? All types that inherit from Measure would be usable as a parameter value. Usually when constraining generics, you would constrain on another generic rather than a knowable interface or superclass. If you know the interface or superclass, you can address it much easier directly. – Richard Robertson Dec 30 '15 at 10:08
  • @JBNizet : I edited the code to include the sendNewMeasure's definition. There is a call to a "find" method that is part of a library I use and that I cannot change. – ErGo_404 Dec 30 '15 at 10:09
  • Use Measure.class as the first parameter or .class. As T extends Measure. – Sunny Dec 30 '15 at 10:10
  • 1
    Can you show us the signature of that find method? – ArcticLord Dec 30 '15 at 10:16
  • @ArcticLord : I edited the original question to add the signature – ErGo_404 Dec 30 '15 at 13:17
  • Ok that method is very type unsafe. But this should produce warnings and no errors. Which line produces what error now? – ArcticLord Dec 30 '15 at 14:55

2 Answers2

5

The type of .getClass() is not what you think it is. .getClass() returns Class<? extends |X|>, where |X| is the erasure of the static type of the expression it's called on. In this case, measure has static type T, which has erasure Measure, so measure.getClass() has type Class<? extends Measure>, i.e. the type parameter is an unknown subtype of Measure, and .sendNewMeasure(measure.getClass(), measure) there is no way the compiler can guarantee that measure (of type T) is an instance of this unknown type.

Basically, the problem is that .getClass() loses type information. Its return type is not directly linked to the type of the thing it is called on, because the Java type system cannot express the concept of "the real runtime type of the thing it's called on". However, intuitively, you know that the call to the method with the current signature .sendNewMeasure(measure.getClass(), measure) is type-safe, because the type of measure.getClass() should really be Class<U> where U is the real runtime class of measure, and you know that measure is obviously an instance of that same type U, so there exists some type argument, this U (which is not necessarily the same as T) for which the call to .sendNewMeasure() type-checks, but the question is how to convince the compiler of this without using unchecked operations.

The problem is that the type returned by measure.getClass() is not sufficiently linked to the type of measure. One way to re-link them is to use the class to cast the object to its type (which will always succeed), using the class's method .cast(). But it doesn't help to do this with an expression of type Class<? extends Measure>, because the resulting of .cast() is ? extends Measure which just degrades to Measure, so we still don't have a link between the two types. We need a real name for the type, not a wildcard, for us to maintain this link. The way to turn a wildcard into a named type is capture, which requires passing it into a generic method:

public <T extends Measure> void onNewMeasure(NewMeasureEvent<T> event) {
    T measure = event.getMeasure();

    helper(measure.getClass(), measure);
}

private <U extends Measure> void helper(Class<U> clazz, Measure measure) {
    U castedMeasure = clazz.cast(measure);
    APIInterface.getInstance().sendNewMeasure(clazz, castedMeasure);
}
newacct
  • 119,665
  • 29
  • 163
  • 224
3

From Java API for

Class<?> getClass()
[...] The actual result type is Class<? extends |X|> where |X| is the erasure of the static type of the expression on which getClass is called.

So the result of measure.getClass() is not Class<T> but Class<? extends T>
Your signature should be:

public <T extends Measure> void sendNewMeasure(Class<? extends T> type, T measure);
ArcticLord
  • 3,999
  • 3
  • 27
  • 47
  • "So the result of `measure.getClass()` is not `Class` but `Class extends T>`" No, because `|X|` is the erasure of the static type of the expression on which getClass is called, and the erasure of `T` is `Measure`, so the type of `measure.getClass()` is `Class extends Measure>`. The signature you have there "works" only because `T` can be inferred to be `Measure`. In any case, your signature changes the type safety of the method, because it allows passing `Measure1.class, instanceOfMeasure2`. – newacct Dec 30 '15 at 20:33