double wildcards

17 views
Skip to first unread message

y s

unread,
Aug 11, 2015, 6:57:48 PM8/11/15
to java.lang.fans
This code fails to compile (tried with javac 1.7.0_67 and 1.8.0_45 on the Mac):

import java.util.*;

public class AccessWeirdness {
  private final String name;

  public AccessWeirdness(String name) { this.name = name; }

  public static String firstNameFrom(Iterable<? extends AccessWeirdness> items) {
    return getFirst(items).name; // <--- HERE
  }

  public static <T> T getFirst(Iterable<T> iterable) {
    return iterable.iterator().next();
  }
}

AccessWeirdness.java:9: error: name in AccessWeirdness is defined in an inaccessible class or interface
    return getFirst(items).name;

We can get it to compile a few different ways:
  • pull the result of getFirst(items) into its own var (not very exciting)
  • change firstNameFrom to take an Iterable<AccessWeirdness> (without the wildcard)
  • change getFirst to take an Iterable<? extends T> (with the wildcard)
I believe what's happening is this:
  • In the code as-is, the call-site argument forces the compiler to infer getFirst's <T> as being "? extends AccessWeirdness," and thus returns a value of that type. The compiler treats this as a subclass of AccessWeirdness, and as such doesn't let it read its super class's private field
  • if you change firstNameFrom to take Iterable<AccessWeirdness>, there's no capture, so everything works
  • If getFirst takes an Iterable<? extends T>, then the compiler is able to infer <T> as a non-wildcarded AccessWeirdness, and the method can still accept the "items" argument due to the covariant declaration of the input. Therefore, getFirst returns the non-wildcarded, plain AccessWeirdness, and everything still works fine.
So, it all makes sense -- but it's a bit unintuitive!

Yuval

Zhong Yu

unread,
Aug 11, 2015, 8:04:40 PM8/11/15
to y s, java.lang.fans
You are right. This is a great example demonstrating multiple subtleties of the language.

A more pedantic analysis -

In expression `getFirst(items)`, the sub-expression `items` goes through capture conversion first; it's type is converted to `Iterable<X>` where X<:AccessWeirdness&Object.  (JLS#5.1.10)

Capture conversion is applied on every expression before it's used in a parent expression. This is so that the compiler can remove wildcards and only deal with values in "concrete" types.

Then, type inference on `getFirst(Iterable<X>)` results in `T=X`. Therefore the method returns `X`.

Now, we need to know the members of type `X`. Per JLS#4.4, the members of type variable `X` are the members of its upper bound AccessWeirdness&Object, an intersection type.

Per JLS#4.9, the intersection type induces a "notional" class for the purpose of identifying members; the notional class has `AccessWeirdness` as the direct superclass.

Therefore type `X` does not contain member `name`.

One solution is to up-cast `X` to `AccessWeirdness` to access `name`

    AccessWeirdness a = getFirst(items); // assign X to AccessWeirdness
    return a.name;

 
Zhong Yu
bayou.io

Zhong Yu

unread,
Aug 11, 2015, 8:12:56 PM8/11/15
to y s, java.lang.fans
The solution of

    <T> T getFirst(Iterable<? extends T> iterable)

is quite interesting. The type inference has constraints

    X<:AccessWeirdness
    X<:T

How does the compiler infers that T=AccessWeirdness is very complicated (see #15.12.2.7 in old JLS5/6/7). At one time I probably understood the process, but not any more:) And java8 type inference is on a whole nother level of complexity.


Zhong Yu
bayou.io

Zhong Yu

unread,
Aug 11, 2015, 8:25:43 PM8/11/15
to y s, java.lang.fans
An up-cast is needed somewhere to access the parent private member. In this solution

  public static <T> T getFirst(Iterable<? extends T> iterable) {
    return iterable.iterator().next();
  }

`iterable` is capture converted to `Iterable<X>` where X<:T. Therefore the return type of `next()` is `X`. It is then up-casted to T as the return type.

Zhong Yu

Reply all
Reply to author
Forward
0 new messages