web-dev-qa-db-ja.com

これはどのようにコンパイルされますか?

私は、keyExtractor関数のリストを使用してコンパレータを生成する関数を書いています(多くのプロパティを持つオブジェクトがあり、多数のプロパティを任意の順序で任意に比較できるようにしたいとします)。

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

私にとって混乱の主なポイントは3つあります。

1)。 Extractorクラスを定義しない場合、これはコンパイルされません。関数や何らかの機能的なインターフェースを直接持つことはできません。

2)。識別関数マッピング行 ".map(e-> e)"を削除すると、タイプチェックが行われません。

3)。私のIDEは、私の関数がタイプData->?の関数のリストを受け入れていると言いますが、これはparseKeysAscending関数の境界に準拠していません。

19
Billy the Kid

Extractorクラスがなくても、ストリームパイプラインでmap(e -> e)を呼び出さなくても機能します。実際、正しいジェネリック型を使用する場合、エクストラクターのリストをストリーミングする必要はまったくありません。

コードが機能しない理由については、完全にはわかりません。 Genericsは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を返すゲッターでのみ機能します。 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 です。

EDIT:Java 8の場合、parseKeysAscendingメソッドの最後の行は次のようになります:

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

一方、新しいバージョンのJavaでは、明示的なジェネリック型を提供する必要があります。

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

フェデリコが私を訂正してくれた後(ありがとう!)これはあなたがそれを使ってできる単一の方法です:

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));
8
Eugene
  1. Extractorクラスを定義しない場合、これはコンパイルされません。 Functionsや何らかの機能的なインターフェースを直接持つことはできません。

いいえ、できます。 _Function<X, Y>_は、ラムダ、メソッド参照、または匿名クラスで定義できます。

_List<Function<Data, Integer>> extractors = List.of(Data::getA, Data::getB);
_
  1. 識別関数マッピング行.map(e -> e)を削除すると、タイプチェックが行われません。

それでも可能ですが、結果はメソッドに適さない場合があります。常に明示的にジェネリックパラメーターを定義して、すべてが期待どおりに機能することを確認できます。

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

ただし、ここではその必要はありません。

_Comparator<Data> test = parseKeysAscending(extractors);
_
  1. 私のIDEは、私の関数がList関数の境界に準拠していない_Data, ?_タイプのFunctionsのparseKeysAscendingを受け入れていると言います。

はい、そうです。 _List<Extractor<Data, ?>>_を渡していますが、コンパイラは_?_の部分を理解できません。 Comparableである場合とそうでない場合がありますが、メソッドでは明らかに_S extends Comparable<S>_が必要です。

2
Andrew Tobilko

問題の元のコードについて:Extractorを削除して、生のComparableを使用できます。

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

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

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

追伸しかし、ここではrawタイプを取り除く方法がわかりません...

1