简体   繁体   English

Java Stream Generics Type Mismatch

[英]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. 在操作Java 8流时,我遇到了一个错误,编译器似乎“忘记”了我的泛型参数的类型。

The following snippet creates a stream of class names and attempts to map the stream to a stream of Class<? extends CharSequence> 以下代码段创建了一个类名流,并尝试将流映射到Class<? extends CharSequence> Class<? extends CharSequence> . 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 ? 类型不匹配:无法转换为Class <capture#15-of? extends CharSequence> to Class<Object> 将CharSequence>扩展为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. PS:这里的代码有些随意,很容易让错误消失:在应用过滤器之前将映射流分配给临时变量。 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? 编辑:正如@Holger所指出的那样,这个问题并不完全与Java 8 Streams重复:为什么Collectors.toMap对于带有通配符的泛型有不同的行为? because the problematic snippet there currently compiles without issues while the snippet here does not. 因为那里有问题的片段目前编译没有问题,而这里的片段没有。

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. 该类型是从它的目标“猜测”的:我们知道map(任何东西)必须返回"Stream<Class<? extends CharSequence>>"因为它是函数的返回类型。 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. 请注意,我修改了代码以返回始终为null,以显示不同的类型不是来自lambda返回类型。

And we see that the type of map1 is infered by the variable declaration, its target. 我们看到map1的类型受变量声明的影响,它的目标。 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> 第一个映射声明没有目标,因此默认情况下定义了impred类型: 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. 使其工作的另一种方法是使类型推断使用Lambda返回值(而不是目标),您需要指定带有强制转换的返回类型。 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. 请注意,这是因为操作链接,你可以用map(x-> x)替换.filter(x - > x!= null)你会遇到同样的问题。

Edit: modify examples to match exactly the question. 编辑:修改示例以完全匹配问题。

In addition to @pdem's answer , also this works for you : 除了@pdem的答案 ,这也适合你:

public class Test {

    public static void main(String[] args) {
        getAsSubclasses(CharSequence.class, "java.lang.String", "java.lang.StringBuilder", "Kaboom!")
                .forEach(System.out::println);
    }

    public static <C> Stream<Class<? extends C>> getAsSubclasses(Class<C> type, String... classNames) {
        return Arrays.stream(classNames)
                .map(new ToSubclass<>(type))
                .filter(c -> c != null);
    }

    static final class ToSubclass<C> implements Function<String, Class<? extends C>> {

        final Class<C> type;

        ToSubclass(Class<C> type) {
            this.type = type;
        }

        @Override
        public Class<? extends C> apply(String s) {
            try {
                return Class.forName(s).asSubclass(type);
            } catch (Exception e) {
                return null;
            }
        }

    }

}

Because return type of your lambda function cannot be determined (or compiler just doesn't try to do so) correctly. 因为无法确定lambda函数的返回类型(或编译器只是不尝试这样做)。 Using explicit anonymous Function object with correct type parameters completely removes problems with type inference: 使用具有正确类型参数的显式匿名Function对象可以完全消除类型推断的问题:

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

    return Arrays.asList("java.lang.String",
                         "java.lang.StringBuilder",
                         "Kaboom!")
    .stream().map(
        new Function<String, Class<? extends CharSequence>>() {
            public Class<? extends CharSequence> apply(String name) {
                try {
                    return Class.forName(name).asSubclass(CharSequence.class);
                } catch (Exception e) {
                }
                return null;
            }
        }
    ).filter(Objects::nonNull);

}

To see, what actual return type of lambda function is resolved by compiler, try asking Eclipse to assign the expression ...stream().map(<your initial lambda>) to local variable (press Ctrl+2 , then L with cursor standing just before the expression). 要看,编译器解决了lambda函数的实际返回类型,请尝试让Eclipse将表达式...stream().map(<your initial lambda>)分配给局部变量(按Ctrl+2 ,然后用光标L站在表达之前)。 It is Stream<Class<? extends Object>> 它是Stream<Class<? extends Object>> Stream<Class<? extends Object>> return type resolved by compiler, not expected Stream<Class<? extends CharSequence>> Stream<Class<? extends Object>>返回类型由编译器解析,而不是预期的Stream<Class<? extends CharSequence>> Stream<Class<? extends CharSequence>> . Stream<Class<? extends CharSequence>>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM