2

Imagine an interface like this

public interface MessageParameter<T> {

  public List<T> unmarshal(byte[] array);
  public int getLength(List<T> values);

}

and a consumer of that interface

public class GenericUser {

  List<MessageParameter<?>> payload = new ArrayList<>();

  public void run() {
     byte[] byteArray = new byte[] { 1, 2 };

     for (MessageParameter<?> element : payload) {
        element.getLength(element.unmarshal(byteArray)); //compiler error
     }
  }
}

The compiler gives an error

 The method getLength(List<capture#1-of ?>) in the type MessageParameter<capture#1-of ?> is not applicable for the arguments (List<capture#2-of ?>)

Clearly since I am using element in both method calls, the type of both is the same and it should be allowed. Another way to ask the same question, why is the compiler creating capture#2?? why can't it deduce that they are logically both the same capture?

Am I missing something? is there a counter-example where this code would throw a runtime exception??

My main question is not how to fix the code (although that would be interesting as well, my current solution is to use Object instead of ?), but what is the logical reason for this error? It looks to me like a shortcoming on the implementation of the compiler more than a logical limitation

Hilikus
  • 9,954
  • 14
  • 65
  • 118

2 Answers2

4

The answer is that the compiler is not that smart to accept that the runtime type corresponding to ? is the same because it does not care that your one-line expression involves the same element:

element.getLength(element.unmarshal(byteArray));

is semantically similar to:

List<?> unmarshalledList = element.unmarshal(byteArray);
element.getLength(unmarshalledList);

In this case, it is not so obvious that the list unmarshalledList would surely have to have the same "any-type" as the one expected by getLength(). The above are two separate statements (even though they're contiguous). Imagine that they're not contiguous. You may have something like:

MessageParameter<?> otherElement = getOtherElement();
for (MessageParameter<?> element : payload) {
    List<?> unmarshalledList = element.unmarshal(byteArray);
    // unmarshalledList can be re-assigned from another parameterized type
    unmarshalledList = otherElement.unmarshal(byteArray);
    element.getLength(unmarshalledList);  // error
}

In other words, the compiler cannot assume that the variable unmarshalledList will retain the same ? type from element when the program reaches the statement invoking getLength on the same element. It can be re-assigned to a different parameterized type in between.

M A
  • 71,713
  • 13
  • 134
  • 174
  • The fact that I made it a one-liner was to demonstrate even more the problem, but what I actually have in the code is exactly what you did. I just don't agree that `it is not so obvious`. It is still obvious (since both forms are equivalent) that it should be allowed. In other words, my surprise is not due to the fact that it is one line. Is there a reason why even the two-lines is not allowed? – Hilikus Jul 16 '15 at 19:34
  • 1
    @Hilikus `?` is not the same type as `?`. – Sotirios Delimanolis Jul 16 '15 at 19:38
  • @SotiriosDelimanolis that would explain it. However, if that were true, `List> unmarshalled = element.unmarshal(byteArray);` would not compile since the `?` in the for-loop variable would not be the same as the one in the List declaration, but it does – Hilikus Jul 16 '15 at 19:47
  • No. At the time of assignment, the `?` is captured and bound. `unmarshalledList` has the type `List>` where `?` is an unknown but fixed type (called `capture#1` by the compiler). At the same time, `element` has the type `MessageParameter>` where the `?` is an unknown but fixed type (called `capture#2` by the compiler). These are not compatible. That's what I mean by `?` is not the same type as `?`. – Sotirios Delimanolis Jul 16 '15 at 19:55
  • 1
    The example in this answer can be made more obvious if you do `List> unmarshalledList = Arrays.asList("not the type expected by the element");` If your `element` was built with some other type, passing it this `List`, which is hidden behind `?`, would break type safety. Since the compiler cannot guarantee the types match, it refuses the assignment (method argument). – Sotirios Delimanolis Jul 16 '15 at 19:57
  • @SotiriosDelimanolis in that case and in the case from @manouti I would understand the error since the compiler would need to create another `capture` since you are assigning something completely new to `unmarshalledList` so `?` might change its meaning. But when you DON'T reassign, why would you need a different capture? why does the compiler create a new one? – Hilikus Jul 16 '15 at 20:07
  • @hilikus So that's the thing: where do you (the compiler) stop? Some cases it can, some cases it can't. Handling both probably adds tons of complexity. – Sotirios Delimanolis Jul 16 '15 at 20:09
  • @Hilikus Even in the one-line statement, I guess there are two different captures seen by the compiler. As Sotirios said, `element`'s type argument is bound to a different capture than the return value of `element.unmarshal(byteArray)`. – M A Jul 16 '15 at 20:11
0

I believe you're misinterpreting the meaning of ? in a generic. That symbol is known as the wildcard; and it refers to a truly unknown type. This is different from your current effort, in which it would literally be better to use Object, as your types are not completely unknown—you know that they both implement Object and can reference them as such. (? extends Object might be better in some places).

As to the reason why ? is not synonymous with Object, remember that primitives do not inherit from Object, but may be referenced with the wildcard. Therefore, after type erasure, your program cannot be certain that the two wildcards are referring to compatible entities; unless you explicitly tell it as much.

Michael Macha
  • 1,729
  • 1
  • 16
  • 25
  • I know `?` is not synonymous to `Object`. My point is that even if it is `unknown` as it is referred by Oracle, you can still know that it is the same in two places. Would you say that x+x = 2*x is false in math because x is "truly unknown"? you have no idea what x is, but you can still make deductions with it, assuming that x is the same everywhere it's used. i.e. you can't have 2+3 = 2*99 since x was not consistently replaced. In my code that `?` can be deduced to be the same – Hilikus Jul 16 '15 at 19:41