【问题标题】:How does this compile?这是如何编译的?
【发布时间】:2018-10-01 17:36:24
【问题描述】:

我正在编写一个函数,它采用一系列 keyExtractor 函数来生成一个 Comparator(假设我们有一个具有许多属性的对象,并且希望能够以任意顺序任意比较大量属性)。

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

class Test {
    public static <T, S extends Comparable<S>> Comparator<T> parseKeysAscending(List<Function<T, S>> keyExtractors) {
        if (keyExtractors.isEmpty()) {
            return (a, b) -> 0;
        } else {
            Function<T, S> firstSortKey = keyExtractors.get(0);
            List<Function<T, S>> restOfSortKeys = keyExtractors.subList(1, keyExtractors.size());
            return Comparator.comparing(firstSortKey).thenComparing(parseKeysAscending(restOfSortKeys));
        }
    }

    public static void main(String[] args) {
        List<Extractor<Data, ?>> extractors = new ArrayList<>();
        extractors.add(new Extractor<>(Data::getA));
        extractors.add(new Extractor<>(Data::getB));

        Comparator<Data> test = parseKeysAscending(
                extractors.stream()
                        .map(e -> e)
                        .collect(Collectors.toList()));
    }

}


class Extractor<T, S extends Comparable<S>> implements Function<T, S> {
    private final Function<T, S> extractor;

    Extractor(Function<T, S> extractor) {
        this.extractor = extractor;
    }

    @Override
    public S apply(T t) {
        return extractor.apply(t);
    }
}

class Data {
    private final Integer a;
    private final Integer b;

    private Data(int a, int b) {
        this.a = a;
        this.b = b;
    }

    public Integer getA() {
        return a;
    }

    public Integer getB() {
        return b;
    }
}

对我来说主要有三个困惑:

1)。如果我不定义 Extractor 类,这将无法编译。我不能直接拥有 Functions 或某种功能接口。

2)。如果我删除恒等函数映射行“.map(e -> e)”,则不会进行类型检查。

3)。我的 IDE 说我的函数正在接受 Data -> 类型的函数列表?不符合 parseKeysAscending 函数的边界。

【问题讨论】:

  • 这里确实似乎发生了一些奇怪的事情。值得问我的问题,因为我的 IDE 也优化了映射但随后立即拒绝编译。您使用的是什么版本的 Java?
  • 这个问题正在meta讨论

标签: java generics java-8 functional-programming comparator


【解决方案1】:

它适用于我没有Extractor 类,也没有在流管道中调用map(e -&gt; e)。实际上,如果您使用正确的泛型类型,则根本不需要流式传输提取器列表。

至于为什么您的代码不起作用,我不完全确定。泛型是 Java 的一个棘手而脆弱的方面......我所做的只是调整 parseKeysAscending 方法的签名,使其符合 Comparator.comparing 的实际期望。

这是parseKeysAscending 方法:

public static <T, S extends Comparable<? super S>> Comparator<T> parseKeysAscending(
        List<Function<? super T, ? extends S>> keyExtractors) {

    if (keyExtractors.isEmpty()) {
        return (a, b) -> 0;
    } else {

        Function<? super T, ? extends S> firstSortKey = keyExtractors.get(0);
        List<Function<? super T, ? extends S>> restOfSortKeys = 
            keyExtractors.subList(1, keyExtractors.size());

        return Comparator.<T, S>comparing(firstSortKey)
            .thenComparing(parseKeysAscending(restOfSortKeys));
    }
}

这是一个调用演示:

List<Function<? super Data, ? extends Comparable>> extractors = new ArrayList<>();
extractors.add(Data::getA);
extractors.add(Data::getB);

Comparator<Data> test = parseKeysAscending(extractors);

List<Data> data = new ArrayList<>(Arrays.asList(
    new Data(1, "z"),
    new Data(2, "b"),
    new Data(1, "a")));

System.out.println(data); // [[1, 'z'], [2, 'b'], [1, 'a']]

data.sort(test);

System.out.println(data); // [[1, 'a'], [1, 'z'], [2, 'b']]

使代码在没有警告的情况下编译的唯一方法是将函数列表声明为List&lt;Function&lt;Data, Integer&gt;&gt;。但这仅适用于返回 Integer 的 getter。我假设您可能想要比较 Comparables 的任何组合,即上面的代码适用于以下 Data 类:

public class Data {
    private final Integer a;
    private final String b;

    private Data(int a, String b) {
        this.a = a;
        this.b = b;
    }

    public Integer getA() {
        return a;
    }

    public String getB() {
        return b;
    }

    @Override
    public String toString() {
        return "[" + a + ", '" + b + "']";
    }
}

这是demo

编辑:请注意,在 Java 8 中,parseKeysAscending 方法的最后一行可以是:

return Comparator.comparing(firstSortKey)
        .thenComparing(parseKeysAscending(restOfSortKeys));

对于较新版本的 Java,您必须提供明确的泛型类型:

return Comparator.<T, S>comparing(firstSortKey)
        .thenComparing(parseKeysAscending(restOfSortKeys));

【讨论】:

  • 我仍然不明白问题出在哪里(它是用我拥有和使用的 java-11 编译的),但作为旁注,我认为该方法可以缩短为 public static &lt;T, S extends Comparable&lt;S&gt;&gt; Comparator&lt;T&gt; parseKeysAscending(List&lt;Function&lt;T, S&gt;&gt; keyExtractors) { return keyExtractors.stream() .collect(Collector.of( () -&gt; (Comparator&lt;T&gt;) (x, y) -&gt; 0, Comparator::thenComparing, Comparator::thenComparing)); }
  • 您的代码 Federico 有两个有趣的观察结果:1)。如果我删除呼叫 2) 中的流,这对我不起作用。我在递归调用中收到“无法解析方法”,但它仍然成功运行。
  • @BillytheKid 老实说,我没有收到那个异常/编译错误,所以我无法确定。通常,众所周知,Eclipse 编译器存在类型推断问题(即协变/逆变,泛型类型。lambdas 和方法引用都混合在一起)。至于未经检查的警告,我认为没有办法摆脱它。
  • @BillytheKid 我在 ideone.com 中尝试过,但它没有编译,因为 Comparator.thenComparing 需要 Function&lt;T, S extends Comparable&lt;? super S&gt;&gt; 类型的参数,它与 Function&lt;T, ? extends Comparable&lt;?&gt;&gt; 不匹配。
  • 在尝试各种版本的编译器时,javac 8 是唯一接受此变体的版本。较新的版本拒绝该程序。自 Java 8 发布以来,Eclipse (ecj) 一直拒绝所有版本的程序。因此,一些混乱似乎是由 javac 中的一个错误引起的,该错误已在 9 中修复。
【解决方案2】:

在 Federico 纠正我之后(谢谢!)这是您可以使用的单一方法:

public static <T, S extends Comparable<? super S>> Comparator<T> test(List<Function<T, S>> list) {
    return list.stream()
            .reduce((x, y) -> 0,
                    Comparator::thenComparing,
                    Comparator::thenComparing);
}

用法如下:

// I still don't know how to avoid this raw type here
List<Function<Data, Comparable>> extractors = new ArrayList<>();
extractors.add(Data::getA); // getA returns an Integer
extractors.add(Data::getB); // getB returns a String

listOfSomeDatas.sort(test(extractors));

【讨论】:

  • 非常好,这确实有意义,因为原始递归函数遵循非常标准的折叠模式。
  • 我仍然更喜欢 list.stream().map(Comparator::comparing).reduce(Comparator::thenComparing).orElse((Comparator&lt;T&gt;) (a, b) -&gt; 0),但 +1 让所有这些泛型噩梦变得简单。
【解决方案3】:
  1. 如果我没有定义Extractor 类,这将无法编译。我不能直接拥有Functions 或某种功能接口。

不,你可以。您可以通过 lambda、方法引用或匿名类定义任何 Function&lt;X, Y&gt;

List<Function<Data, Integer>> extractors = List.of(Data::getA, Data::getB);
  1. 如果我删除标识函数映射行.map(e -&gt; e),这将不会进行类型检查。

它仍然会,但结果可能不适合该方法。您始终可以显式定义泛型参数,以确保一切按您的预期进行。

extractors.<Function<Data, Integer>>stream().collect(Collectors.toList())

但这里没有必要:

Comparator<Data> test = parseKeysAscending(extractors);
  1. 我的 IDE 说我的函数正在接受 Functions 的 List 类型 Data, ? 不符合 parseKeysAscending 函数的边界。

是的,应该。您正在传递 List&lt;Extractor&lt;Data, ?&gt;&gt; 并且编译器无法理解 ? 部分。它可能是也可能不是Comparable,而您的方法显然需要S extends Comparable&lt;S&gt;

【讨论】:

    【解决方案4】:

    关于问题中的原始代码:您可以删除Extractor 并使用原始Comparable

    List<Function<Data, Comparable>> extractors = new ArrayList<>();
    
    extractors.add(Data::getA);
    extractors.add(Data::getB);
    
    @SuppressWarnings("unchecked")
    Comparator<Data> test = parseKeysAscending(extractors);
    

    附:但是我看不到如何在这里摆脱原始类型...

    【讨论】:

      猜你喜欢
      • 2014-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-12
      • 2016-10-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多