173

This is a real-world example from a 3rd party library API, but simplified.

Compiled with Oracle JDK 8u72

Consider these two methods:

<X extends CharSequence> X getCharSequence() {
    return (X) "hello";
}

<X extends String> X getString() {
    return (X) "hello";
}

Both report an "unchecked cast" warning - I get why. The thing that baffles me is why can I call

Integer x = getCharSequence();

and it compiles? The compiler should know that Integer does not implement CharSequence. The call to

Integer y = getString();

gives an error (as expected)

incompatible types: inference variable X has incompatible upper bounds java.lang.Integer,java.lang.String

Can someone explain why would this behaviour be considered valid? How would it be useful?

The client does not know that this call is unsafe - the client's code compiles without warning. Why wouldn't the compile warn about that / issue an error?

Also, how is it different from this example:

<X extends CharSequence> void doCharSequence(List<X> l) {
}

List<CharSequence> chsL = new ArrayList<>();
doCharSequence(chsL); // compiles

List<Integer> intL = new ArrayList<>();
doCharSequence(intL); // error

Trying to pass List<Integer> gives an error, as expected:

method doCharSequence in class generic.GenericTest cannot be applied to given types;
  required: java.util.List<X>
  found: java.util.List<java.lang.Integer>
  reason: inference variable X has incompatible bounds
    equality constraints: java.lang.Integer
    upper bounds: java.lang.CharSequence

If that is reported as an error, why Integer x = getCharSequence(); isn't?

Adam Michalik
  • 9,678
  • 13
  • 71
  • 102
  • 15
    interesting! casting on the LHS `Integer x = getCharSequence();` will compile, but casting on the RHS `Integer x = (Integer) getCharSequence();` fails compile – flakes Apr 04 '16 at 12:41
  • What version of the java compiler are you using? Please specify this info in the question. – fps Apr 04 '16 at 15:20
  • @FedericoPeraltaSchaffner can't see why that matters - this is a question directly about the JLS. – Boris the Spider Apr 04 '16 at 17:46
  • @BoristheSpider Because the type inference mechanism has changed for java8 – fps Apr 04 '16 at 20:25
  • 1
    @FedericoPeraltaSchaffner - I tagged the question already with [java-8], but I added the compiler version in the post now. – Adam Michalik Apr 05 '16 at 09:09

2 Answers2

186

CharSequence is an interface. Therefore even if SomeClass does not implement CharSequence it would be perfectly possible to create a class

class SubClass extends SomeClass implements CharSequence

Therefore you can write

SomeClass c = getCharSequence();

because the inferred type X is the intersection type SomeClass & CharSequence.

This is a bit odd in the case of Integer because Integer is final, but final doesn't play any role in these rules. For example you can write

<T extends Integer & CharSequence>

On the other hand, String is not an interface, so it would be impossible to extend SomeClass to get a subtype of String, because java does not support multiple-inheritance for classes.

With the List example, you need to remember that generics are neither covariant nor contravariant. This means that if X is a subtype of Y, List<X> is neither a subtype nor a supertype of List<Y>. Since Integer does not implement CharSequence, you cannot use List<Integer> in your doCharSequence method.

You can, however get this to compile

<T extends Integer & CharSequence> void foo(List<T> list) {
    doCharSequence(list);
}  

If you have a method that returns a List<T> like this:

static <T extends CharSequence> List<T> foo() 

you can do

List<? extends Integer> list = foo();

Again, this is because the inferred type is Integer & CharSequence and this is a subtype of Integer.

Intersection types occur implicitly when you specify multiple bounds (e.g. <T extends SomeClass & CharSequence>).

For further information, here is the part of the JLS where it explains how type bounds work. You can include multiple interfaces, e.g.

<T extends String & CharSequence & List & Comparator>

but only the first bound may be a non-interface.

Paul Boddington
  • 37,127
  • 10
  • 65
  • 116
  • 63
    I had no idea you could put an `&` in the generic definition. +1 – flakes Apr 04 '16 at 12:52
  • 13
    @flkes You can put more than one, but only the first argument can be a non-interface. `` is ok but `` is not, because `Integer` is not an interface. – Paul Boddington Apr 04 '16 at 12:53
  • 2
    Thanks a lot, Paul, the `foo(List list)` is especially creative! Still, I don't get how that can be practically useful for client and API provider to use such generic API. With ` X getCharSequence()` the only thing the provider promises is that the returned object implements `CharSequence`. The client has no way of knowing what other types it is valid to cast to. It might cast to `SomeClass` or to `OtherClass` - getting a runtime exception. Would you have an example where this could be useful? To me it just seems dangerous, weakening the compile-time checking. – Adam Michalik Apr 04 '16 at 12:59
  • @AdamMichalik I agree with you. In my opinion, methods like ` List someMethod()` are dangerous. It says that the caller can decide what type the method returns without passing any information to the method at all - which makes absolutely no sense. I've seen it in 3rd party APIs too but I think it's really bad practice. On the other hand, if T appears in the argument list too it's ok. E.g ` List shuffle(List list)` makes perfect sense. So it's that specific type method that's unsafe, not generics in general. – Paul Boddington Apr 04 '16 at 13:19
  • 2
    In fact, the compile-time checking is cancelled out solely by the unchecked cast (and, regarding the `SomeClass` example: The implementation can not sensibly return an instance of `X` in any case). Maybe adding a hint at [the JLS section 4](https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-TypeBound) could be worthwhile, where it is made clear that a `TypeBound` may contain at most one `ClassType` (but multiple `InterfaceType`s). – Marco13 Apr 04 '16 at 13:32
  • 7
    @PaulBoddington There is some practical use for these methods. For example if the type is not actually used for stored data. Examples for this are `Collections.emptyList()` as well as `Optional.empty()`. These return implementations of a generic interface, but do not store anything. – Stefan Dollase Apr 04 '16 at 13:36
  • 2
    @StefanDollase Yes that's a good point. My comment is misleading. I like those examples as they show that type erasure has benefits. You couldn't share a single instance for `Collections.emptyList()` in C# because the type can be queried at runtime. – Paul Boddington Apr 04 '16 at 13:51
  • 3
    There are only two kinds of possible correct implementations for a method like ` X getCharSequence()`, first, `return null;`, second, `throw …;`. Having little use is not a good criteria for ruling out actually syntactical valid constructs—the rules are already complicated enough. Note the difference between this method returning a caller-chosen type and the useful methods `Collections.emptyList()` and `Optional.empty()` returning a type, *parametrized* by the caller. There is another family of valid uses, see (the `private` method) `Collectors.throwingMerger()`… – Holger Apr 04 '16 at 15:08
  • 2
    Or `Comparator.naturalOrder()`or `Function.identity()`… – Holger Apr 04 '16 at 15:11
  • @Holger I guess you could also do `while (true);`, although that's even more pointless. When I made my comment about bad practice I was thinking of something like ` T getFromId(long id)`, although that's obviously not what the comment says. In these cases, I think returning `Object` and forcing the client to cast is better. My last SO answer but 2 has a method `static Collector> toShuffledList()`, so I don't know why I said it was bad practice! – Paul Boddington Apr 04 '16 at 15:16
  • Mmmm, this answer sounds 'logical', however there's no way for `Integer x = getCharSequence()` to **NOT** be a compiler bug, since `Integer` does not implement `CharSequence`. – fps Apr 04 '16 at 15:17
  • 2
    @Paul Boddington: that’s not unusual to think about the bad practice of methods like ` T getFromId(long id)` as there are several examples of that on SO, especially in the Java 8 context, where that backfires (even more)… – Holger Apr 04 '16 at 15:21
  • @PaulBoddington Sorry, that was me.. While your answer is logical and explains generics bound types correctly (as well as generics not covariance), I think it fails to explain why `Integer x = getCharSequence()` actually compiles, when it's obvious that it's an error in the compiler type inference system. (I removed the downvote by now) – fps Apr 04 '16 at 15:22
  • 2
    @Federico Peralta Schaffner: there *could* be a type that extends both, `Integer` and `CharSequence` and as this answer explains, the fact that `Integer` is `final` is not considered. It’s new in the Java 8 type inference, that you don’t need to know an actual type for that hypothetical type. – Holger Apr 04 '16 at 15:23
  • @FedericoPeraltaSchaffner Thanks for explaining the downvote. I guess the reason they didn't take `final` into account with these rules is that they're complicated enoungh. – Paul Boddington Apr 04 '16 at 15:24
  • @Holger Do you known why `Integer a = (Integer) getCharSequence();` doesn't compile? – Paul Boddington Apr 04 '16 at 15:24
  • 7
    And nobody says that a class being `final` at compile-time will be `final` at runtime. – Holger Apr 04 '16 at 15:25
  • 4
    @Paul Boddington: no, that’s a strange thing as the cast should provide a target type just like the assignment. I just checked that it will work if you pass the invocation to another one, letting the parameter provide the target type. – Holger Apr 04 '16 at 15:26
  • 1
    @FedericoPeraltaSchaffner : [Here](http://stackoverflow.com/q/28466925/2711488) is a similar example where the compiler assumes that there *could* be a type that implements `Base` and `Collection` making an invalid invocation valid from the compiler’s point of view. The only difference being that `Base` is not `final`, but as said, `final` is not considered by the type system. – Holger Apr 04 '16 at 15:33
  • @Holger I'm starting to learn about *poly expressions* and the role they play in Java8's type inference mechanism. However, despite there *might* exist a type that extends both `Integer` and `CharSequence`, I believe the compiler should check that the type at the left of the `=` sign of the assignment should actually match the type at the right of the `=` sign, no matter if it's an intersection type or not, but *especially* if the type is not an intersection type. – fps Apr 04 '16 at 15:39
  • 7
    @Federico Peralta Schaffner: the point here is, the method `getCharSequence()` promises to return whatever `X` the caller needs, that includes returning a type extending `Integer` and implementing `CharSequence` if the caller needs it and under this promise, it’s correct to allow assigning the result to `Integer`. It’s the method `getCharSequence()` which is broken as it doesn’t keep its promise, but that’s not the compiler’s fault. – Holger Apr 04 '16 at 15:45
  • @Holger I think I understand the mechanism now, thanks for the examples and for taking the time to explain it. Anyways, given I have the class `class Whatever {}`, I fail to see how compiling `Whatever youWant = getCharSequence();` doesn't throw an error. I understand that the method `getCharSequence()` is broken and that the cast inside it is really bad practice. But I think that this opens the door to massive heap pollution. I perfectly understand that `StringBuilder sb = getCharSequence()` compiles fine, though. That's my point. – fps Apr 04 '16 at 16:36
  • 3
    @Federico Peralta Schaffner: in this scenario, there is no heap pollution. The case you’re unwilling to accept is where the code will clearly fail with a `ClassCastException`, so there won’t be a heap pollution. A heap pollution will happen if the returned value is kept in a variable with a non-reifyable type, but that’s exactly the scenario where the compiler can’t determine the (in)correctness anyway (where we have a type variable rather than `Integer`). The scenario here, where the human reader says “that’s obviously wrong”, is a corner case of a more general issue. – Holger Apr 04 '16 at 16:46
  • 3
    @Federico Peralta Schaffner: Keep in mind that with a correct implementation of `getCharSequence()` (e.g. returning `null`, throwing an exception, never completing or returning the correct thing via magic), the compiled code, including the assignment to `Integer` will be correct. – Holger Apr 04 '16 at 16:48
  • 1
    @AdamMichalik "With X getCharSequence() the only thing the provider promises is that the returned object implements CharSequence" No, it promises to return object of any type `X` which implements `CharSequence` which _the client_ chooses by calling `getCharSequence()`. "The client has no way of knowing what other types it is valid to cast to. It might cast to SomeClass or to OtherClass - getting a runtime exception." How is this different from any other method which doesn't return a primitive or a final class? – Alexey Romanov Apr 05 '16 at 09:43
  • 1
    @AlexeyRomanov - that's the point that it seems to promise to return **anything** the client chooses, but it has no way of knowing what the client has chosen and how to comply with that choice. In my example the client has chosen `Integer` - how would the provider know that and how would it return an object that extends `Integer` and implements `CharSequence`, while a different client could call choosing `Boolean` for `X`? My point is that the client does not know that this call is unsafe - the client's code compiles without warning. – Adam Michalik Apr 05 '16 at 09:52
  • @AdamMichalik No, in your example the client has chosen `Integer & CharSequence`. To choose `Integer` they'd need to write `Integer x = getCharSequence()`, which will fail to compile as expected. – Alexey Romanov Apr 05 '16 at 10:19
  • 3
    @AdamMichalik For the first: it doesn't _seem_ to promise this, it _does_ promise this. It just has no way to fulfill that promise (except the ones already mentioned: returning `null`, throwing or looping). This is very different from saying it only promises "that the returned object implements CharSequence": there are plenty of ways to fulfill _that_ promise! – Alexey Romanov Apr 05 '16 at 10:23
  • Was not able to understand the reasoning behind `even if SomeClass does not implement CharSequence it would be perfectly possible to create a class`. If a class uses the keyword implement then it will have to implement the interface otherwise such a class would not compile. What am I missing? – Aseem Bansal Apr 05 '16 at 17:34
  • 1
    @AseemBansal I'm saying that just because `Someclass` doesn't implement `CharSequence`, it doesn't mean some subclass can't implement `CharSequence`. This is different from classes. If `SomeClass` doesn't extend `SomeOtherClass`, no subclass of `SomeClass` can either. – Paul Boddington Apr 05 '16 at 17:41
60

The type that is inferred by your compiler prior to the assignment for X is Integer & CharSequence. This type feels weird, because Integer is final, but it's a perfectly valid type in Java. It is then cast to Integer, which is perfectly OK.

There is exactly one possible value for the Integer & CharSequence type: null. With the following implementation:

<X extends CharSequence> X getCharSequence() {
    return null;
}

The following assignment will work:

Integer x = getCharSequence();

Because of this possible value, there's no reason why the assignment should be wrong, even if it is obviously useless. A warning would be useful.

The real problem is the API, not the call site

In fact, I've recently blogged about this API design anti pattern. You should (almost) never design a generic method to return arbitrary types because you can (almost) never guarantee that the inferred type will be delivered. An exception are methods like Collections.emptyList(), in case of which the emptiness of the list (and generic type erasure) is the reason why any inference for <T> will work:

public static final <T> List<T> emptyList() {
    return (List<T>) EMPTY_LIST;
}
Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509