5

The following fails to compile:

    @NotNull String defaultFormatter(@Nullable Object value) {
        if (value instanceof Collection) {
            return ((Collection) value).stream()
                        .map(MyClass::defaultFormatter)
                        .collect(Collectors.joining(eol));
        }
        return String.valueOf(value);
    }

In particular, when compiled with javac, the error would be:

Error:(809, 94) java: incompatible types: 
      java.lang.Object cannot be converted to 
      @org.jetbrains.annotations.NotNull java.lang.String

But the following compiles just fine:

    @NotNull String defaultFormatter(@Nullable Object value) {
        if (value instanceof Collection) {
            Stream<String> stream = ((Collection) value).stream()
                         .map(MyClass::defaultFormatter);
            return stream.collect(Collectors.joining(eol));
        }
        return String.valueOf(value);
    }

The only difference would be that I introduced an extra variable. Note that I didn't cast, so no semantic change.

Can anybody explain why is this needed?

ddimitrov
  • 3,293
  • 3
  • 31
  • 46
  • Are you using `javac` or an IDE's compiler? – Kayaman Jun 22 '17 at 13:27
  • Both IntelliJ IDEA's syntax highlighter and confirmed by compiling with javac. – ddimitrov Jun 22 '17 at 13:29
  • 3
    Possible duplicate of https://stackoverflow.com/questions/2770321/what-is-a-raw-type-and-why-shouldnt-we-use-it. See the section under *"A raw type is the erasure of that type"*. You need to use `Collection>` instead of `Collection`. – Radiodef Jun 22 '17 at 13:31
  • Thanks @Radiodef - this explains why it failed. The next question is in that case why am I able to assign to the explicit variable without casting? I.e. if the compiler allows this coercion, why wouldn't it allow it in an expression? – ddimitrov Jun 22 '17 at 13:33
  • 2
    The assignment works because of [unchecked conversion](https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.9). Basically any assignment of the form `AnObject = AnObject` or `AnObject = AnObject` is valid, the reason being backwards compatibility with old libraries. – Radiodef Jun 22 '17 at 13:35
  • Thank you, this answers the question. Not sure what is the etiquette when there is no answer to mark as correct though - can anybody advise? – ddimitrov Jun 22 '17 at 13:36
  • 1
    @ddimitrov encourage Radiodef to write it as an answer. And do it before some grumpy soul with the dupe hammer comes and marks this a dupe of "What is a raw type". – Andy Turner Jun 22 '17 at 13:38
  • @Radiodef could you paste this as an answer please? – ddimitrov Jun 22 '17 at 13:39
  • See: [What is a raw type and why shouldn't we use it?](https://stackoverflow.com/questions/2770321/what-is-a-raw-type-and-why-shouldnt-we-use-it) – Jesper Jun 22 '17 at 13:59

1 Answers1

4

This top part of this answer is basically what Radiodef said in comments above. I'm not wanting to steal those words, but the answer below the --- doesn't really work without the prior explanation.

As pointed out by Radiodef, the reason why this doesn't work in the first case is because it's using a raw type, Collection. Instead, use Collection<?>, and it will work:

        return ((Collection<?>) value).stream()
                    .map(MyClass::defaultFormatter)
                    .collect(Collectors.joining(eol));

The reason why it works with the explicit variable is because of unchecked conversion. Note that the following produces an unchecked conversion warning:

        Stream<String> stream = ((Collection) value).stream()
                     .map(MyClass::defaultFormatter);

The actual type of the expression on the RHS is Stream; you're allowed to coerce that to a Stream<String>, as described in JLS Sec 5.1.9:

There is an unchecked conversion from the raw class or interface type (§4.8) G to any parameterized type of the form G<T1,...,Tn>.


The reason why you can't do the same without the variable is a bit more subtle. This answer addresses the issue more directly: when you use a raw type, all generics are erased from the type, not just ones directly related to the omitted type.

So, the type of Stream.collect when the Stream is raw is the erasure of the type when it is generic:

  • Stream.collect(Collector<? super T,A,R> collector) returns an R;
  • The erasure of R is Object

so the return type of the collect call is Object, as you observe here. This can't be automatically coerced to a List<String> via unchecked conversion because it's not List.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243