【问题标题】:comparing and thenComparing gives compile error比较然后比较给出编译错误
【发布时间】:2016-11-09 04:43:27
【问题描述】:

我正在尝试使用 Java8 Comparator 按名称对员工的 List 进行排序,然后在 Comparator 下方创建,但它给了我一个编译器错误

Type mismatch: cannot convert from Comparator<Object> to <unknown>

    Comparator<String> c = Comparator.comparing(s -> s.split("\\s+")[0])
            .thenComparingInt(s -> Integer.parseInt(s.split("\\s+")[1])); //compile error

但如果我明确指定类型,它会起作用

    Comparator<String> c = Comparator.<String, String> comparing(s -> s.split("\\s+")[0])
            .thenComparingInt(s -> Integer.parseInt(s.split("\\s+")[1])); //works

或者通过创建两个Compartors 和链

    Comparator<String> name = Comparator.comparing(s -> s.split("\\s+")[0]);
    Comparator<String> age = Comparator.comparingInt(s -> Integer.parseInt(s.split("\\s+")[1]));
    Comparator<String> cc = name.thenComparing(age); //works

我已经在左侧指定了类型Comparator&lt;String&gt;,但是为什么自动类型推断没有找到正确的类型并期望明确指定。

有人可以澄清一下吗?

这里是代码

    String[] arr = { "alan 25", "mario 30", "alan 19", "mario 25" };
    Comparator<String> c = Comparator.<String, String> comparing(s -> s.split("\\s+")[0])
            .thenComparingInt(s -> Integer.parseInt(s.split("\\s+")[1]));
    List<String> sorted = Arrays.stream(arr).sorted(c).collect(Collectors.toList());
    System.out.println(sorted);

输出

[alan 19, alan 25, mario 25, mario 30]

【问题讨论】:

  • 我不是泛型类型推断方面的专家,但我猜这只是“过多的自动推断”。可能它无法确定comparing() 方法的类型,因为它没有任何类型可以“锚定”它,这与链接方法不同,在链接方法中您显式地为comparing() 的结果赋予类型。无论如何,我认为编写自己的Comparator 并只调用一次split 会更具可读性。将代码压缩到尽可能少的行中并没有什么好处。
  • 目标类型不能通过链式方法调用工作,参见hereherehere。但是,您可以简单地使用Comparator.comparing(s -&gt; s.replaceFirst("\\s+", " ")) 而不是您的两级比较器,结果将是相同的......
  • Comparator.comparing((String s) -&gt; s.split("\\s+")[0]).thenComparingInt(s -&gt; Integer.parseInt(s.split("\\s+")[1])) 有效,但为什么需要这个String,它可以从Comparator&lt;String&gt; 推断,这是类型推断的限制吗?顺便说一句,使用java version "1.8.0_60"
  • @Holger,我不确定我明白了。如果所有年龄都保证为 2 位数,您的单级比较器会很好地工作,但我相信它会将 [alan 102, alan 25, alan 8] 排序为这个顺序,这与期望的相反——我说的对吗?
  • @Ole VV:确实,这是所选示例的缺陷,它没有显示没有两位数的数字,但我仍然建议避免重复的 split 操作并将其实现为单个比较器,即使它由于数字解析而变得更加复杂。对于较大的列表,首先将所有元素转换为 (String,int) 类型,对它们进行排序,然后将它们转换回 String 形式可能会更有效......

标签: java java-8 comparator comparable


【解决方案1】:

Java 需要知道所有变量的类型。在许多 lambda 中,它可以推断出类型,但在您的第一个代码 sn-p 中,它无法猜测 s 的类型。我认为解决该问题的标准方法是明确声明它:

    Comparator<String> c = Comparator.comparing((String s) -> s.split("\\s+")[0])
            .thenComparingInt(s -> Integer.parseInt(s.split("\\s+")[1]));

如果您查看this answer,它在参数中具有与Comparator.comparing() 相似的类型声明。

你的方法,明确给出comparing()的类型参数,显然也有效。

对于您的另一种方法,声明两个比较器,我非常有信心在这种情况下,Java 可以从赋值左侧的String 推断,就像在传统的List &lt;String&gt; = new ArrayList&lt;&gt;(); 中一样。当您继续在同一个表达式中调用thenComparing() 时,Java 不再能看到左侧的类型是相关的。有点像int size = new ArrayList&lt;&gt;().size(); 这也可以:

    Comparator<String> name = Comparator.comparing(s -> s.split("\\s+")[0]);
    Comparator<String> c = name.thenComparingInt(s -> Integer.parseInt(s.split("\\s+")[1]));

【讨论】:

  • @ole-vv Comparator.comparing((String s) -> s.split("\\s+")[0]).thenComparingInt(s -> Integer.parseInt(s.split( "\\s+")[1])) 有效,但为什么需要这个字符串,它可以从 Comparator 推断,它是类型推断的限制吗?顺便说一句,使用 java 版本“1.8.0_60”
  • 你是怎么推断出来的?你知道thenComparing() 应该返回一个Comparator&lt;String&gt;(以适合赋值的左侧),因此comparing() 需要返回Comparator&lt;String&gt;,因此s 需要是一个String。编译器不会经过这么多推理步骤。是的,这是类型推断的限制,但我认为这是故意引入的限制,否则规则会变得过于复杂,程序员不会理解它们。这是我最好的猜测。
  • 最大的问题是,通过链式调用进行目标类型化可能会改变后续调用的可用方法,这实际上是用于确定前一个目标类型,即foo(x).bar(y),返回类型的foo(x) 确定是否以及哪些bar(…) 方法可用,但将目标类型应用于foo(x) 取决于实际选择的bar(…) 方法。
  • 我很确定有一天这个限制会被解除,但可能只有当基本类型没有改变时,即Comparator.comparing总是返回一个Comparator,目标类型只会影响它的类型参数,并且随后链接的方法没有重载,thenComparingInt就是这种情况。
猜你喜欢
  • 1970-01-01
  • 2014-09-07
  • 2016-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多