4

I've always been wondering about some weird aspect of Java generics and the use of wildcards. Let's say, I have the following API:

public interface X<E> {
    E get();
    E set(E e);
}

And then, let's say we declare the following methods:

public class Foo {
    public void foo(X<?> x) {
        // This does not compile:
        x.set(x.get());
    }

    public <T> void bar(X<T> x) {
        // This compiles:
        x.set(x.get());
    }
}

From my "intuitive" understanding, X<?> is in fact the same as X<T>, except that the unknown <T> is formally unknown to client code. But inside of foo(), I would guess that the compiler could infer (pseudo JLS code) <T0> := <?>[0], <T1> := <?>[1], etc.... This is what most programmers do explicitly and intuitively. They delegate to a private helper method, which leads to a lot of useless boiler-plate code:

public class Foo {
    public void foo(X<?> x) {
        foo0(x);
    }

    private <T> void foo0(X<T> x) {
        x.set(x.get());
    }
}

Another example:

public class Foo {
    public void foo() {
        // Assuming I could instanciate X
        X<?> x = new X<Object>();
        // Here, I cannot simply add a generic type to foo():
        // I have no choice but to introduce a useless foo0() helper method
        x.set(x.get());
    }
}

In other words, the compiler knows that the wildcard in x.set() is formally the same as the wildcard in x.get(). Why can't it use that information? Is there a formal aspect in the JLS, that explains this lacking compiler "feature"?

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
  • Isn't it simply a design error to not parameterize the foo method, the same way as foo0 is parameterized? – JB Nizet Jul 16 '12 at 08:16
  • @JBNizet: Good point, I'll add another example that should be clearer, as this doesn't just apply to wildcards declared in method signatures. You can run into the same problem if you have locally-scoped wildcards – Lukas Eder Jul 16 '12 at 08:16
  • Are you asking *how* or *why*? – user207421 Jul 16 '12 at 10:43
  • @EJB: *How*. *Why* would be an open-ended question not suited for Stack Overflow – Lukas Eder Jul 16 '12 at 10:57
  • @LukasEder So why does 'why' appear in the body of your question? – user207421 Jul 16 '12 at 11:01
  • @EJB: "Why" as in, *what formal aspect of the JLS specifies that generic type information contained in wildcards cannot be used to compile `x.set(x.get())`*. Not as in *"why" would anyone design the JLS like that.* – Lukas Eder Jul 16 '12 at 11:27
  • It just struck me that even though the compiler can't "solve" it automatically, it would be really nice with a quick-fix option in Eclipse :) You think that's doable? – aioobe Aug 01 '12 at 08:48
  • @aioobe: What would be fixed? Do you think Eclipse could add the required (formally unsafe, yet known to be safe) cast? Or extracting the helper method? – Lukas Eder Aug 01 '12 at 08:50
  • ...or just change the `foo` in your first example into something like the `bar` method. Wouldn't that make sense in some (fairly easy to detect) situations? Otherwise a helper method would be neat :-) – aioobe Aug 01 '12 at 08:56
  • Yes, I guess both options would be nice. The helper method is the only way to go, if the wildcard is set on a local variable... I'll try to phrase a feature request at Eclipse, when I get the time – Lukas Eder Aug 01 '12 at 09:00

3 Answers3

5

You wonder why isn't it supported? An equally valid question is, why should it be supported?

The way wildcards work, given the declaration X<? extends Y> x, the type of the expression x.get() is not ? extends Y or some type inferred by the compiler, but Y. Now, Y isn't assignable to ? extends Y, so you can't pass x.get() to x.set which has the effective signature of void set(? extends Y e). The only thing you could actually pass to it is null, which is assignable to every reference type.

I follow the JDK developer mailing lists fairly closely, and the general spirit there is that every feature the JLS specifies should pull its own weight - the cost/benefit ratio should be heavy on the benefit side. Now, keeping track of the origin of a value would be quite possible, but it would only solve one special case, which has several other ways to solve it. Consider these classes:

class ClientOfX {
    X<?> member;
    void a(X<?> param) {
        param.set(param.get());
    }
    void b() {
        X<?> local = new XImpl<Object>();
        local.set(local.get());
    }
    void c() {
        member.set(member.get());
    }
    void d() {
        ClassWeCantChange.x.set(ClassWeCantChange.x.get());
    }
}

class ClassWeCantChange {
    public static X<?> x;
}

This obviously doesn't compile, but with your proposed enhancement, it does. However, we can get three of the four methods compiling without changing the JLS:

  1. For a, when we receive the generic object as a parameter, we can make the method generic and receive an X<T> instead of X<?>.
  2. For b and c, when using a locally declared reference, we don't need to declare the reference with a wildcard (Eric Lippert of Microsoft's C# compiler team calls this situation "if it hurts when you do that, then don't do that!").
  3. For d, when using a reference whose declaration we can't control, we have no option but to write a helper method.

So the smarter type system would really only help in the case where we can't control the declaration of a variable. And in that situation, the case for doing what you want to do is kind of sketchy - the very act of wildcarding a generic type normally means that the object is either intended to work only as a producer (? extends Something) or a consumer (? super Something) of objects, not both.

The TL;DR version is that it would bring little benefit (and require wildcards to be something else than what they are today!) while adding complexity to the JLS. If someone comes up with a very compelling case to add it, the spec could be extended - but once it's there, it's there forever, and it may cause unforeseen problems down the road, so leave it out until you really need it.

Edit: The comments contain more discussion about what wildcards are and aren't.

gustafc
  • 28,465
  • 7
  • 73
  • 99
  • 1
    That is a good answer explaining why such a feature might have been omitted. I might not entirely agree with the *cost/benefit ratio* in this specific case, although I don't understand the details of specifying something like that, so it might be justified. However, I'm also looking for the formal specification of wildcards, and how they explain that with the current version of the JLS, it is indeed not possible... – Lukas Eder Jul 16 '12 at 09:17
  • For case 'a', why does the [generics documentation](http://docs.oracle.com/javase/tutorial/java/generics/capture.html) use List> as parameter and give helper method as a solution, when you can just use List as parameter – vikky.rk Jul 16 '12 at 09:22
  • Note, case `a` might lead into a whole new set of corner cases when method overloading is involved. A very interesting case can be seen here: http://stackoverflow.com/questions/5361513/reference-is-ambiguous-with-generics. So to the JLS, `a(original)` and `a(patched)` are quite different method signatures... – Lukas Eder Jul 16 '12 at 09:27
  • 1
    @Lukas Eder: The closest I can find resembling a definition of wildcards in the JLS is in [section 4.5.1: Types Arguments and Wildcards](http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.5.1): *Type arguments may be either reference types or wildcards.* That is, wildcards can only be used as type arguments and are more like assumptions about types, than actual types. So, when you have a declaration `X> x`, the type of `x.get()` isn't `? extends Object`, it's "a subtype of `Object`". Thus, the wildcard "type" doesn't carry - because there *is* no wildcard type. – gustafc Jul 16 '12 at 13:01
  • ... which means that there is no explanation of why things like `x.set(x.get())` won't work - there is no reason why it *should* work. You *could* make it work, but that would require wildcards to be a different thing to what they are now. It's not in the spec because if every string that isn't semantically correct Java was in the spec, the spec would be infinitely long. – gustafc Jul 16 '12 at 13:09
  • OK, I see. I guess that's my misunderstanding then about what a wildcard *is* and what it *isn't*... – Lukas Eder Jul 16 '12 at 13:25
  • When you write "For b and c, when using a locally declared reference, we don't need to declare the reference with a wildcard" you are advocating the use of raw types. I don't know what is C#'s position on that but in Java that is definitely frowned upon. So it is only case a that is easily fixable. – olefevre Feb 10 '14 at 23:49
  • @olefevre With case b, there's just no reason why you should wildcard the type parameters on the left hand side when you have them exactly specified on the right hand side. For case c, raw types can be sort of OK ([they do it in the JDK, as you can see below](http://stackoverflow.com/a/11982573/58956)). For most cases, however, the sensible solution is usually _not to use a wildcard-parameterized type_ if you're gonna go and poke about in it. – gustafc Feb 11 '14 at 07:48
  • Think of it this way: What's the _actual use case_ for having a list-of-god-knows-what, which the class (1) is responsible for modifying, and (2) obviously isn't interested in retrieving or adding items from for clients (since the only thing you could give/take would be `Object`)? If really you want a list-of-who-cares-what-it-is, use `List`. If you want to be more specific, parameterize with the actual type you're interested in, or use a type parameter on the enclosing class. It has a bit more impact, but it only makes sense. – gustafc Feb 11 '14 at 07:50
  • We are veering off-track. The spirit of the Java spec is that any instance of a parametrized type should be parametrized, if only with >. Otherwise just don't parametrize the type to begin with. – olefevre Feb 11 '14 at 16:57
  • @olefevre Indeed, and that's what I meant with "if it hurts, don't do that": Don't use wildcards if wildcards aren't appropriate for your use case. – gustafc Feb 12 '14 at 08:22
2

I've just seen this peculiar implementation of Collections.swap():

public static void swap(List<?> list, int i, int j) {
    final List l = list;
    l.set(i, l.set(j, l.get(i)));
}

The JDK guys resort to a raw type, in order to handle this, locally. To me, this is a strong statement indicating that this probably should be supported by the compiler, but for some reason (e.g. lack of time to formally specify this) just wasn't done

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
1

Not having been involved in writing the JLS, my best guess is simplicity. Imagine silly, yet (syntactically) valid expressions such as (x = new X<Integer>()).set(x.get());. Tracking captures of wildcards can get tricky.

I think of capturing wildcards not as boilerplate, but as having done something right. It's a sign that your API is free of type parameters which client code does not need.

Edit: The current behavior is specified in JLS §4.5.2. The x gets capture-converted for each member access -- once for x.set(...) and once for x.get(). The captures are distinct and thus not known to represent the same type.

Ben Schulz
  • 6,101
  • 1
  • 19
  • 15
  • The API, yes. The implementation, no. I often run into this problem. As @gustafc explained well, I can circumvent the problem with tricks. Yet they are tricks. I'm wondering about the parts of the JLS that define the current behaviour, not necessarily the suspected reasoning behind it. – Lukas Eder Jul 16 '12 at 09:19
  • @LukasEder Added a reference to the relevant paragraph. – Ben Schulz Jul 16 '12 at 09:47