Java 流泛型类型不匹配

2022-09-04 04:50:15

在操作Java 8流时,我遇到了一个错误,编译器似乎“忘记”了我的通用参数的类型。

以下代码段创建类名流,并尝试将该流映射到 流 。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)
        ;

}

当我取消注释过滤器以从流中删除空条目时,我收到编译错误

类型不匹配:无法从类<捕获 #15-of ?将 CharSequence> 扩展到 Class<Object>

有人可以向我解释为什么添加过滤器会导致此错误吗?

PS:这里的代码有点武断,很容易使错误消失:在应用过滤器之前将映射的流分配给临时变量。我感兴趣的是为什么上面的代码片段会生成编译时错误。

编辑:正如@Holger所指出的,这个问题并不是Java 8 Streams的完全重复:为什么Collecters.toMap对于带有通配符的泛型的行为不同?因为有问题的片段目前编译没有问题,而这里的片段没有。


答案 1

这是因为类型推断:

类型是从它的目标“猜测”的:我们知道map(anything)必须返回a,因为它是函数的返回类型。如果将返回到另一个操作(例如过滤器或映射)进行链接,我们将失去此类型推断(它不能“通过”链接)"Stream<Class<? extends CharSequence>>"

类型推断有他的局限性,你找到了它。

解决方案很简单:你有没有说过,如果你使用一个变量,你可以指定目标,然后帮助类型推断。

此编译:

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);

请注意,我修改了代码以返回始终为null,以显示推断的类型不是来自lambda返回类型。

我们看到 map1 的类型是由变量声明(其目标)推断出来的。如果我们返回它,它是等价的,目标是返回类型,但是如果我们链接它:

这不会编译:

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);

第一个映射声明没有目标,因此默认情况下定义推断的类型:Stream<Object>

编辑

使其工作的另一种方法是使类型推断使用Lambda返回值(而不是目标),例如,您需要使用cast指定返回类型。这将编译:

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);

}

请注意,这是因为操作链接,您可以将.filter(x -> x != null)替换为map(x->x),您会遇到同样的问题。

编辑:修改示例以完全匹配问题。


答案 2

除了@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;
            }
        }

    }

}

推荐