web-dev-qa-db-fra.com

Comment cela se compile-t-il?

J'écris une fonction qui prend une liste de fonctions keyExtractor pour produire un comparateur (imaginez que nous avions un objet avec de nombreuses propriétés et que nous voulions pouvoir comparer arbitrairement un grand nombre de propriétés dans n'importe quel ordre).

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

Il y a trois principaux points de confusion pour moi:

1). Si je ne définis pas la classe Extractor, cela ne se compilera pas. Je ne peux pas avoir directement de fonctions ou une sorte d'interface fonctionnelle.

2). Si je supprime la ligne de mappage de la fonction d'identité ".map (e -> e)", cela ne tapera pas check.

3). My IDE indique que ma fonction accepte une liste de fonctions de type Data ->? Qui ne respecte pas les limites de la fonction parseKeysAscending.

19
Billy the Kid

Cela fonctionne pour moi sans la classe Extractor et aussi sans appeler map(e -> e) dans le pipeline de flux. En fait, la diffusion de la liste des extracteurs n'est pas nécessaire du tout si vous utilisez les types génériques appropriés.

Quant à savoir pourquoi votre code ne fonctionne pas, je ne suis pas complètement sûr. Les génériques sont un aspect difficile et instable de Java ... Tout ce que j'ai fait a été d'ajuster la signature de la méthode parseKeysAscending, afin qu'elle soit conforme à ce que Comparator.comparing attend réellement.

Voici la méthode 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));
    }
}

Et voici une démo avec l'appel:

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']]

La seule façon de compiler le code sans avertissement était de déclarer la liste des fonctions comme List<Function<Data, Integer>>. Mais cela ne fonctionne qu'avec les getters qui retournent Integer. Je suppose que vous voudrez peut-être comparer n'importe quel mélange de Comparables, c'est-à-dire que le code ci-dessus fonctionne avec la classe Data suivante:

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 + "']";
    }
}

Voici la démo .

EDIT: Notez qu'avec Java 8, la dernière ligne de la méthode parseKeysAscending peut être:

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

Alors que pour les versions plus récentes de Java, vous devez fournir des types génériques explicites:

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

Après que Federico m'ait corrigé (merci!), C'est une seule méthode avec laquelle vous pouvez le faire:

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

Et l'utilisation serait:

// 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. Si je ne définis pas la classe Extractor, cela ne se compilera pas. Je ne peux pas avoir directement Function ou une sorte d'interface fonctionnelle.

Non tu peux. Vous pouvez définir n'importe quel Function<X, Y> Par un lambda, une référence de méthode ou une classe anonyme.

List<Function<Data, Integer>> extractors = List.of(Data::getA, Data::getB);
  1. Si je supprime la ligne de mappage de la fonction d'identité .map(e -> e), cela ne tapera pas check.

Ce sera toujours le cas, mais le résultat peut ne pas convenir à la méthode. Vous pouvez toujours définir explicitement des paramètres génériques pour vous assurer que tout se passe comme prévu.

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

Mais cela n'est pas nécessaire ici:

Comparator<Data> test = parseKeysAscending(extractors);
  1. Mon IDE indique que ma fonction accepte un List de Function s du type Data, ? Qui ne respecte pas les limites de la Fonction parseKeysAscending.

Oui, ça devrait. Vous passez un List<Extractor<Data, ?>> Et le compilateur ne peut pas comprendre la partie ?. Il peut ou non s'agir d'un Comparable alors que votre méthode nécessite clairement S extends Comparable<S>.

2
Andrew Tobilko

Concernant le code d'origine dans la question: vous pouvez supprimer Extractor et utiliser un Comparable brut:

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

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

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

P.S. Pourtant, je ne vois pas comment me débarrasser du type brut ici ...

1