Search code examples
javagenericswildcardparameterized

Why can't I use wildcard with methods receiving a parameterized argument?


For example, I use a method Measure.doubleValue(Unit<?> unit) which returns the double value of a measurement, expressed in the specified Unit. If I pass a Unit<?> variable to it, I get the still very cryptic (to me) error message:

The method doubleValue(Unit<capture#27-of ?>) in the type Measurable<capture#27-of ?> is not applicable for the arguments (Unit<capture#28-of ?>)

I would appreciate if someone could explain what that #27-of ? (or any other number) means, and if there is an elegant way to get rid of this. Thus far, I remove the <?> and set the calling method @SuppressWarnings("unchecked") (so I pass an unchecked Unit instead of a Unit<?>) and everything works as desired, but I'm just curious about this, and I feel like suppressing warnings is not a good practice (isn't it a little like empty catch blocks?).

Thanks!

Edit: Adding some code.

(I'm sorry, this is quite long, but it explains in details what I'm stuck on.)

I am using JSR-275 version 0.9.4 (most recent).

So... If I write this (very dumb example):

Measure measure = Measure.valueOf("3 m");
measure = Measure.valueOf(measure.doubleValue(Unit.valueOf("km")), Unit.valueOf("km"));
System.out.println(measure);

It works and prints "0.0030 km". But I get warning "Measure is a raw type. References to generic type Measure<Q> should be parameterized" over the first Measure occurrence and warning "Type safety: The method doubleValue(Unit) belongs to the raw type Measurable. References to generic type Measurable<Q> should be parameterized" over measure.doubleValue(Unit.valueOf("km")).

Seeing these warnings, I thought I could adjust this way (first line only):

Measure<Length> measure = Measure.valueOf("3 m");

And then I get error message on right part of assignation "Type mismatch: cannot convert from Measure<capture#1-of ?> to Measure<Length>". It (Eclipse) offers me to cast the right part to (Measure&lt;Length>). But then I get warning message over the right part "Type safety: Unchecked cast from Measure<capture#1-of ?> to Measure<Length>". Fix suggested: @SuppressWarnings which I would prefer to avoid (for paranoid reasons I guess).

So, I step back to Measure measure = Measure.valueOf("3 m"); and try to give wildcard to Measure, as obviously, it doesn't know what "3 m" means at this moment. It could be a Length, but also a Mass or a Time. So I get:

Measure<?> measure = Measure.valueOf("3 m");

And no warning or error on this line; fantastic. But, on the second line:

measure = Measure.valueOf(measure.doubleValue(Unit.valueOf("km")), Unit.valueOf("km"));

I get and error message for doubleValue: "The method doubleValue(Unit<capture#3-of ?>) in the type Measurable<capture#3-of ?> is not applicable for the arguments (Unit<capture#4-of ?>)". It suggests to cast Unit.valueOf("km") as a (Unit<?>). Fine. Now I get error message at the exact same location: "The method doubleValue(Unit<capture#3-of ?>) in the type Measurable<capture#3-of ?> is not applicable for the arguments (Unit<capture#5-of ?>)". Notice that the numbers have changed, so that's not the exact same parameters, but a similar reason. Then it does the exact same suggestion which leads to no change whatsoever in the code, since it has already been done.

So that's what is bugging me. The only way to get it working seems to @SuppressWarnings or just ignore them. Isn't it strange?


Solution

  • Does the measure class look something like this?

    class Measure<T> {
    
      double doubleValue(Unit<T> unit) {
        ...
      }
    
    }
    

    In that case, have a reference type of Measure<?> doesn't mean that you are allowed to pass any type of Unit to the doubleValue method. Rather, it means that the Measure instance has an unknown generic type, and it isn't safe to pass a Unit to its doubleValue method because the compiler cannot ensure that the types are compatible.

    A wildcard does not mean "any type"; it means "unknown type".


    Update:

    The valueOf(CharSequence) returns an unknown type of Measure—a Measure<?>. To convert this safely to the type of Measure you expect, you must use the Measure.asType() method. Likewise with the target Unit, created by the Unit.valueOf(CharSequence) method.

    Measure<?> unknownMeasure = valueOf("3 m");
    Unit<?> unknownUnit = Unit.valueOf("km");
    Measure<Length> length = unknownMeasure.asType(Length.class);
    Unit<Length> kilometer = unknownUnit.asType(Length.class);
    length.doubleValue(kilometer);
    

    Look at the examples in the Measure class documentation. They will provide some additional depth.