开发者

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

开发者 https://www.devze.com 2022-12-18 03:02 出处:网络
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 th

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 safet开发者_运维技巧y: 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?


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.


The wildcard in Java generics means "unknown type". Here, you define the doubleValue() method as accepting a Unit<something1> where the something1 is not specified by the method. You then pass a value that the caller knows as Unit<something2> for some unknown something2. The compiler error message means that there is nothing which guarantees that the something1 and the something2 designate the same thing.

Try this:

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

which means that doubleValue() does not care about what T is.


To expand on the other (excellent) answers, but be a bit more JSR-275 specific (I'm using it for a project at the moment).

This bit is interesting

Fix suggested: @SuppressWarnings which I would prefer to avoid (for paranoid reasons I guess).

You're right to be paranoid, but think about it: you're telling the Measure class to parse an arbitrary String, and return a Measure of any type. Obviously in that case you may or may not get a Measure<Length> (you might pass "3 kg") so the only option left to the library is return Measure<?>. If you WANT a Measure<Length>, then you have to coerce it to one somehow -- this can't possibly be guaranteed safe at compile-time.

In this case, I would argue that @SuppressWarnings is perfectly acceptable, assuming you know that the string will always be a valid length. If not, you're delaying an inevitable ClassCastException until later, which is pretty bad.

But even better (hooray!) JSR-275 does give you a way around this, which pushes the error handling to the 'right' place (at the time of obtaining the Measure, not at some point afterwards when it's used). Try

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

asType returns the appropriate generic version of Measure, or fails with an exception if the dimension of the parsed unit is NOT length -- this is almost certainly what you want, right?

I think this solves your problem.


I can not reproduce your problem, i.e. the following compiles fine (at least with eclipse):

static double doubleValue(Unit<?> unit) {
    return 0;
}

static void bla(Unit<?> u) {
    doubleValue(u);
}

public static void main(String[] args) {
    doubleValue(new Unit<String>());
}
0

精彩评论

暂无评论...
验证码 换一张
取 消