简体   繁体   中英

Misunderstanding Java generics

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> 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);

The type of .getClass() is not what you think it is. .getClass() returns Class<? extends |X|> 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> Class<? extends Measure> , ie 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> Class<? extends Measure> , because the resulting of .cast() is ? extends Measure ? 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);
}

From Java API for

Class<?> getClass()
[...] The actual result type is Class<? extends |X|> 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> Class<? extends T>
Your signature should be:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM