Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Stream Generics Type Mismatch

While manipulating Java 8 streams I've encountered an error where the compiler seems to 'forget' the type my generic parameters.

The following snippet creates a stream of class names and attempts to map the stream to a stream of Class<? extends CharSequence>.

public static Stream<Class<? extends CharSequence>> getClasses() {

    return Arrays.asList("java.lang.String", "java.lang.StringBuilder", "Kaboom!")
        .stream()
        .map(x -> {
            try {
                Class<?> result = Class.forName(x);

                return result == null ? null : result.asSubclass(CharSequence.class);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            return null;
        })
        //.filter(x -> x != null)
        ;

}

When I uncomment the filter to remove the null entries from the stream I get a compile error

Type mismatch: cannot convert from Class<capture#15-of ? extends CharSequence> to Class<Object>

Can someone please explain to me why adding the filter causes this error?

PS: The code here is somewhat arbitrary and it's easy enough to make the error go away: Assign the mapped stream to a temporary variable before applying the filter. What I'm interested in is why the above code snippet generates a compile time error.

Edit: As @Holger pointed out the this question is not an exact duplicate of Java 8 Streams: why does Collectors.toMap behave differently for generics with wildcards? because the problematic snippet there currently compiles without issues while the snippet here does not.

like image 997
Chris Kerekes Avatar asked Feb 22 '16 22:02

Chris Kerekes


1 Answers

This is because of type inference:

The type is "guessed" from it's target: we know that map(anything) must return a "Stream<Class<? extends CharSequence>>" because it is the return type of the function. If you chain that return to another operation, a filter or a map for example, we loose this type inference (it can't go "through" chainings)

The type inference has his limits, and you find it.

The solution is simple: has you said, if you use a variable, you can specify the target then help the type inference.

This compile:

public static Stream<Class<? extends CharSequence>> getClasses() {
Stream<Class<? extends CharSequence>> map1 = Arrays.asList ("java.lang.String", "java.lang.StringBuilder", "Kaboom!").stream ().map (x -> {
  try {
    Class<?> result = Class.forName (x);
    return result == null ? null : result.asSubclass(CharSequence.class);
  } catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace ();
  }

  return null;
});
return map1.filter(x -> x != null);

Note that i modified the code to return always null to show that infered type doesn't come from lambda return type.

And we see that the type of map1 is infered by the variable declaration, its target. If we return it, it is equivalent, the target is the return type, but if we chain it:

This doesn't compile:

public static Stream<Class<? extends CharSequence>> getClasses () {

return Arrays.asList ("java.lang.String", "java.lang.StringBuilder", "Kaboom!").stream ().map (x -> {
  try {
    Class<?> result = Class.forName (x);
    return result == null ? null : result.asSubclass(CharSequence.class);
  } catch (Exception e) {

    e.printStackTrace ();
  }

  return null;
}).filter(x -> x != null);

The first map declaration has no target, so the infered type is defined by default: Stream<Object>

Edit

Another way to make it work would be to make the type inference work with Lambda return value (instead of target), you need to specify the return type with cast for example. This will compile:

public static Stream<Class<? extends CharSequence>> getClasses2 () {

return Arrays.asList ("java.lang.String", "java.lang.StringBuilder", "Kaboom!").stream ().map (x -> {
  try {
    Class<?> result = Class.forName (x);
     return (Class<? extends CharSequence>)( result == null ? null : result.asSubclass(CharSequence.class));
  } catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace ();
  }

  return (Class<? extends CharSequence>)null;
}).filter(x -> x != null);

}

Note that this is because of operation chaining, you could replace .filter(x -> x != null) with map(x->x) you would have the same problem.

Edit: modify examples to match exactly the question.

like image 59
pdem Avatar answered Oct 12 '22 22:10

pdem