javac confused by generics on class?
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Tue Jun 10 16:03:04 UTC 2025
(Adding compiler-dev and dropping core-libs-dev)
I think the compiler is correct here.
In the first example, you call List::stream() on a raw List (because "c"
has the raw type Child, so "c.getValues()" returns a raw list). This
gives you a raw stream (Stream), which means the Stream::collect is also
erased, meaning it just returns Object. But since the method return type
is String, you get an error as Object cannot be returned if a String
type is expected.
In the second example you also first stash the result of "c.getValues()"
(again, a raw list) into a variable of type List<String>. This is an
unchecked operation -- as you are unsafely adding generic type
parameters to raw type. The compiler lets you do it, with a warning.
From that point on, it's as if there's no raw types. You have a
List<String>, so calling List::stream gives you a Stream<String>. Which
means Stream::collect no longer has an erased type, and a String is
returned from the collect call, as expected. So you get a warning for
the unchecked operation above, but then no errors.
I hope this helps understanding what is going on.
Maurizio
On 10/06/2025 16:50, Jean-Noël Rouvignac wrote:
> Hello,
>
> When doing refactorings to take advantage of newer Java features, I
> hit a new and weird edge case. I trimmed down the code several times,
> and ended up with the following tiny reproducer, and I don't
> understand what javac is complaining about even with javac 24:
>
> (Note: unlike the original code, this reproducer is very contrived, so
> there's no need to make comments on how bad it is: I fully agree)
>
> ```java
> 1 import java.util.ArrayList;
> import java.util.List;
> import java.util.stream.Collectors;
>
> public class Main {
> private static final class Child<T> {
> public List<String> getValues() {
> return new ArrayList<>();
> }
> }
>
> @SuppressWarnings("unchecked")
> private static String getString1(Child c) {
> // Compilation error:
> // Main.java:16: error: incompatible types: Object cannot be
> converted to String
> return c.getValues().stream().collect(Collectors.joining());
> }
>
> private static String getString2(Child c) {
> // Compilation error:
> // Main.java:27: warning: [unchecked] unchecked conversion
> // List<String> values = c.getValues();
> // ^
> // required: List<String>
> // found: List
> //1 warning
> List<String> values = c.getValues();
> return values.stream().collect(Collectors.joining());
> }
> }
> ```
>
> It turns out IntelliJ is a bit more eloquent than javac, and when
> hovering over the warning on `c.getValues()` at the line with
> `List<String> values = c.getValues();`, it reports the following:
>
> > Unchecked assignment: 'java.util.List' to
> 'java.util.List<java.lang.String>'. Reason: 'c' has raw type, so
> result of getValues is erased
>
> Is it possible that javac is doing early type erasure when analysing
> this code, erasing a bit too much? Even if the code uses `Child`
> without type argument, I would expect the return type of `getValues()`
> to be well defined as `List<String>`?
>
> What do you think?
> I am sure there is some rational explanation that I missed for this
> behaviour. I could not find a JBS issue showing the same case as here.
>
> Thank you,
> Jean-Noël Rouvignac
>
> /CONFIDENTIALITY NOTICE: This email may contain confidential and
> privileged material for the sole use of the intended recipient(s). Any
> review, use, distribution or disclosure by others is strictly
> prohibited. If you have received this communication in error, please
> notify the sender immediately by e-mail and delete the message and any
> file attachments from your computer. Thank you./
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/compiler-dev/attachments/20250610/11903c64/attachment-0001.htm>
More information about the compiler-dev
mailing list