【问题标题】:Java Stream Generics Type MismatchJava 流泛型类型不匹配
【发布时间】:2016-02-22 22:38:59
【问题描述】:

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

下面的 sn-p 创建一个类名流并尝试将该流映射到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)
        ;

}

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

类型不匹配:无法从 Class 扩展为 Class

谁能解释一下为什么添加过滤器会导致这个错误?

PS:这里的代码有些随意,很容易消除错误:在应用过滤器之前将映射流分配给一个临时变量。我感兴趣的是为什么上面的代码sn-p会产生编译时错误。

编辑:正如@Holger 指出的,这个问题不是Java 8 Streams: why does Collectors.toMap behave differently for generics with wildcards? 的完全重复,因为有问题的sn-p 目前编译没有问题,而这里的sn-p 没有。

【问题讨论】:

  • 嗯。起初我以为这是由于method chaining,但实际上我不确定。 asSubclass 确实返回了 Class&lt;? extends CharSequence&gt;。它也不能与 javac 一起编译,至少 1.8.0_74。
  • 我可以通过对filter 的调用以及对map 的显式类型参数进行编译:.&lt;Class&lt;? extends CharSequence&gt;&gt;map(。或者我可以在catch 块之后使用return CharSequence.class 而不是return null 编译它。这看起来像是类型推断的问题。
  • 只是一些旁注:使用Stream.of(…) 而不是Arrays.asList(…).stream(),进一步Class.forName 永远不会返回null,因此条件已过时。当filter 仅处理map 的错误条件(即null 测试)时,您始终可以使用flatMap(…) 合并.map(…).filter(…)。综上所述,您可以将您的任务解决为return Stream.of("java.lang.String", "java.lang.StringBuilder", "Kaboom!") .flatMap(x -&gt; { try { return Stream.of(Class.forName(x).asSubclass(CharSequence.class)); } catch(Exception e) { e.printStackTrace(); return null; }});
  • @Tunaki:不幸的是,它不是重复的,因为链接问题的问题已得到修复,而所有版本都会出现此问题,包括最新版本。
  • @Tunaki:确实,我不知道我从哪里复制了你的名字。真正让我困惑的是,在被拒绝的.filter(…) 之后简单地附加.map(Function.identity()) 会使错误消失。链接另一个 .filter(x-&gt;true) 甚至是一个简单的 .unordered() 会使它重新出现,添加另一个 map(x-&gt;x).flatMap(Stream::of) 将再次修复它。你可以继续……唯一重要的是链的最后一个操作。

标签: java eclipse java-8 java-stream


【解决方案1】:

这是因为类型推断:

类型是从它的目标“猜测”出来的:我们知道 map(anything) 必须返回一个 "Stream&lt;Class&lt;? extends CharSequence&gt;&gt;" 因为它是函数的返回类型。如果您将返回链接到另一个操作,例如过滤器或映射,我们会失去这种类型推断(它不能“通过”链接)

类型推断有其局限性,你会发现的。

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

这个编译:

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

第一个map声明没有目标,所以默认定义推断类型:Stream&lt;Object&gt;

编辑

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

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) 您会遇到同样的问题。

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

【讨论】:

  • 您的回答完全没有问题。问题是由 filter(…) 操作引起的问题,您在任何代码示例中都没有使用该操作。 null 可以分配给每种类型,或者编译将失败,当您将正确的 Class&lt;? extends CharSequence&gt; 替换为不匹配的 Class&lt;?&gt; 时,这一事实在任何方面都没有帮助。保留提问者的原始代码比您的“解决方案”更简单,因为提问者已经创建了一个 正确 类型的流,该流在不链接 filter(…) 操作时已经有效。
  • 这个问题不是特别是过滤器引起的,而是任何操作,它可能是map()、filter()或任何()。而且我让过滤器在我的代码中,可以不加注释得到原始代码。
  • 在没有filter 操作的情况下,OPs 代码编译不会出错,而您所做的任何事情都只会使 OPs 代码复杂化而没有解决任何问题。添加filter 操作时,会出现同样的错误。
  • @Holger 你真的对解决方案感兴趣,还是只是一场激烈的讨论?
  • 我没有看到激烈的讨论。你没有解决方案,你甚至没有解决问题。您说您可以取消注释过滤器,但显然您从未尝试过。 尝试一下
【解决方案2】:

除了@pdem's answer,这也适合你:

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

    }

}

【讨论】:

    【解决方案3】:

    因为无法正确确定 lambda 函数的返回类型(或者编译器只是不尝试这样做)。使用具有正确类型参数的显式匿名 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);
    
    }
    

    要查看编译器解析 lambda 函数的实际返回类型,请尝试让 Eclipse 将表达式 ...stream().map(&lt;your initial lambda&gt;) 分配给局部变量(按 Ctrl+2,然后按 @987654325 @ 光标位于表达式之前)。它是由编译器解析的Stream&lt;Class&lt;? extends Object&gt;&gt; 返回类型,不是预期的Stream&lt;Class&lt;? extends CharSequence&gt;&gt;

    【讨论】:

    • 呃,当你把返回类型改成Stream&lt;?&gt;时,你不需要对原代码做任何改动,这是一个便宜的解决方案,如果我们要称之为解决方案的话……跨度>
    • 没错,就是想要的类型,只是在这里编辑代码时漏掉了
    • 这解决了问题,但我更喜欢将显式类型与 lambda 表达式一起使用,例如stream().&lt;Class&lt;? extends CharSequence&gt;&gt;map(name -&gt; { try { return Class.forName(name).asSubclass(CharSequence.class); } catch (Exception e) {} return null; }).filter(…) 但请注意,当包含 Eclipse 的行为时,关于 why 的解释是不够的。 Eclipse 在类型推断方面……非常特别。对于javac,在末尾附加一个.map(x-&gt;x) 也可以解决问题,清楚地表明它从未在这里推断出? extends Object。比较复杂。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-18
    • 2010-09-20
    相关资源
    最近更新 更多