这是如何编译的?

Bil*_*Kid 19 java generics functional-programming comparator java-8

我正在编写一个函数,它接受一个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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

1).如果我没有定义Extractor类,则不会编译.我不能直接拥有函数或某种功能界面.

2).如果我删除标识函数映射行".map(e - > e)",则不会进行类型检查.

3).我的IDE说我的函数是接受数据类型的函数列表 - >?它不符合parseKeysAscending函数的范围.

Fed*_*ner 8

它适用于没有Extractor类的我,也没有调用map(e -> 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));
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个带有调用的演示:

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']]
Run Code Online (Sandbox Code Playgroud)

使代码编译而没有警告的唯一方法是将函数列表声明为List<Function<Data, Integer>>.但这只适用于返回的吸气剂Integer.我假设您可能想要比较任何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 + "']";
    }
}
Run Code Online (Sandbox Code Playgroud)

这是演示.

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

return Comparator.comparing(firstSortKey)
        .thenComparing(parseKeysAscending(restOfSortKeys));
Run Code Online (Sandbox Code Playgroud)

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

return Comparator.<T, S>comparing(firstSortKey)
        .thenComparing(parseKeysAscending(restOfSortKeys));
Run Code Online (Sandbox Code Playgroud)

  • 我仍然不明白问题出在哪里(它是用java-11编译的,我有并使用),但作为旁注,我认为该方法可以缩短为`public static <T,S extends Comparable <S >> Comparator <T> parseKeysAscending(List <Function <T,S >> keyExtractors){return keyExtractors.stream().collect(Collector.of(() - >(Comparator <T>)(x,y) - > 0,Comparator :: thenComparing,Comparator :: thenComparing)); }` (3认同)
  • @BillytheKid老实说,我没有得到异常/编译错误,所以我不知道.通常,已知Eclipse编译器存在类型推断的问题(即协方差/逆变,泛型类型,lambdas和方法引用都混合在一起).至于未经检查的警告,我认为没有办法摆脱它. (3认同)

Eug*_*ene 8

在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);
}
Run Code Online (Sandbox Code Playgroud)

用法是:

// 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));
Run Code Online (Sandbox Code Playgroud)

  • 非常好,这确实有意义,因为原始的递归函数遵循非常标准的折叠式模式. (3认同)
  • 我仍然喜欢`list.stream().map(Comparator ::比较).reduce(Comparator :: thenComparing).orElse((Comparator <T>)(a,b) - > 0)`比这更多,但+ 1保持所有这些泛型梦魇简单. (2认同)