una que para paquetes mac ejecutar desde con compilar como comando clase archivo java generics java-8 functional-programming comparator

java - que - ¿Cómo se compila esto?



compilar un archivo java desde cmd (4)

Estoy escribiendo una función que toma una lista de funciones keyExtractor para producir un Comparador (imagina que tenemos un objeto con muchas propiedades y queremos poder comparar arbitrariamente una gran cantidad de propiedades en cualquier orden).

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

Hay tres puntos principales de confusión para mí:

1). Si no defino la clase Extractor, esto no se compilará. No puedo tener directamente funciones o algún tipo de interfaz funcional.

2). Si elimino la línea de asignación de la función de identidad ".map (e -> e)", esto no escribirá "check".

3). Mi IDE dice que mi función está aceptando una lista de funciones del tipo Datos ->? que no cumple con los límites de la función parseKeysAscending.


  1. Si no defino la clase Extractor , esto no se compilará. No puedo tener directamente Function o algún tipo de interfaz funcional.

No, tu puedes. Puede definir cualquier Function<X, Y> mediante una lambda, una referencia de método o una clase anónima.

List<Function<Data, Integer>> extractors = List.of(Data::getA, Data::getB);

  1. Si .map(e -> e) línea de mapeo de la función de identidad .map(e -> e) , esto no escribirá check.

Todavía lo hará, pero el resultado puede no ser adecuado para el método. Siempre puede definir parámetros genéricos explícitamente para asegurarse de que todo salga como espera.

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

Pero no hay necesidad de eso aquí:

Comparator<Data> test = parseKeysAscending(extractors);

  1. Mi IDE dice que mi función acepta una List de Function del tipo Data, ? que no cumple con los límites de la función parseKeysAscending .

Sí, debería. Está pasando una List<Extractor<Data, ?>> y el compilador no puede entender el ? parte. Puede o no ser un Comparable mientras que su método claramente requiere S extends Comparable<S> .


Con respecto al código original en la pregunta: puede eliminar Extractor y usar un Comparable procesar:

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

Sin embargo, no veo cómo deshacerme del tipo crudo aquí ...


Después de que Federico me corrigió (¡gracias!), Este es un método único con el que podría hacerlo:

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

Y su uso sería:

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


Funciona para mí sin la clase Extractor y también sin llamar al map(e -> e) en el canal de flujo. En realidad, la transmisión de la lista de extractores no es necesaria en absoluto si utiliza los tipos genéricos correctos.

En cuanto a por qué su código no funciona, no estoy completamente seguro. Los genéricos son un aspecto difícil y escamoso de Java ... Todo lo que hice fue ajustar la firma del método parseKeysAscending , para que se ajuste a lo que Comparator.comparing realmente espera.

Aquí está el método 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)); } }

Y aquí hay una demo con la llamada:

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 única forma de hacer que el código se compile sin advertencias era declarar la lista de funciones como List<Function<Data, Integer>> . Pero esto solo funciona con getters que devuelven Integer . Supongo que es posible que desee comparar cualquier combinación de s Comparable , es decir, el código anterior funciona con la siguiente clase de 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 + "'']"; } }

Aquí está la demo .

EDITAR: tenga en cuenta que con Java 8, la última línea del método parseKeysAscending puede ser:

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

Mientras que para las versiones más nuevas de Java, debe proporcionar tipos genéricos explícitos:

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