如何编译?

时间:2018-10-01 17:36:25

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

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

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类,则不会编译。我不能直接拥有“功能”或某种功能接口。

2)。如果删除身份函数映射行“ .map(e-> e)”,则不会键入check。

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

4 个答案:

答案 0 :(得分: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));
    }
}

这是一个带有呼叫的演示:

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<Function<Data, Integer>>。但这仅适用于返回Integer的吸气剂。我假设您可能想比较Comparable的任何组合,即上面的代码可与以下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));

答案 1 :(得分: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);
}

用法为:

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

答案 2 :(得分:2)

  
      
  1. 如果我没有定义Extractor类,则不会编译。我不能直接拥有Function或某种功能接口。
  2.   

不,可以。您可以通过lambda,方法引用或匿名类定义任何Function<X, Y>

List<Function<Data, Integer>> extractors = List.of(Data::getA, Data::getB);
  
      
  1. 如果我删除身份功能映射行.map(e -> e),则不会键入check。
  2.   

它仍然可以,但是结果可能不适合该方法。您始终可以明确定义通用参数,以确保一切按预期进行。

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

但是这里不需要这样做:

Comparator<Data> test = parseKeysAscending(extractors);
  
      
  1. 我的IDE表示我的函数正在接受List类型的Function中的Data, ?个,这不符合parseKeysAscending函数的范围。
  2.   

是的,应该。您传递了List<Extractor<Data, ?>>,编译器无法理解?部分。当您的方法明确要求使用Comparable时,它可能不是S extends Comparable<S>

答案 3 :(得分:1)

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

List<Function<Data, Comparable>> extractors = new ArrayList<>();

extractors.add(Data::getA);
extractors.add(Data::getB);

@SuppressWarnings("unchecked")
Comparator<Data> test = parseKeysAscending(extractors);

P.S。但是我看不出如何摆脱原始类型...