Comprensión de listas tipo Python en Java

Ya que Java no permite pasar métodos como parámetros, ¿qué truco usas para implementar la comprensión de listas tipo Python en Java?

Tengo una lista (ArrayList) de cadenas. Necesito transformar cada elemento usando una función para obtener otra lista. Tengo varias funciones que toman una cadena como entrada y devuelvo otra cadena como salida. ¿Cómo hago un método genérico al que se le puede dar la lista y la función como parámetros para que pueda obtener una lista con cada elemento procesado? No es posible en el sentido literal, pero ¿qué truco debo usar?

La otra opción es escribir una nueva función para cada función de procesamiento de cadenas más pequeña que simplemente recorre toda la lista, lo que no es tan genial.

Básicamente, creas una interfaz de función:

 public interface Func { public Out apply(In in); } 

y luego pase una subclase anónima a su método.

Su método podría aplicar la función a cada elemento en el lugar:

 public static  void applyToListInPlace(List list, Func f) { ListIterator itr = list.listIterator(); while (itr.hasNext()) { T output = f.apply(itr.next()); itr.set(output); } } // ... List myList = ...; applyToListInPlace(myList, new Func() { public String apply(String in) { return in.toLowerCase(); } }); 

o cree una nueva List (básicamente creando una asignación de la lista de entrada a la lista de salida):

 public static  List map(List in, Func f) { List out = new ArrayList(in.size()); for (In inObj : in) { out.add(f.apply(inObj)); } return out; } // ... List myList = ...; List lowerCased = map(myList, new Func() { public String apply(String in) { return in.toLowerCase(); } }); 

Cuál es preferible depende de su caso de uso. Si su lista es extremadamente grande, la solución local puede ser la única viable; Si desea aplicar muchas funciones diferentes a la misma lista original para hacer muchas listas derivadas, querrá la versión del map .

En Java 8 puedes usar referencias de métodos:

 List list = ...; list.replaceAll(String::toUpperCase); 

O, si desea crear una nueva instancia de lista:

 List upper = list.stream().map(String::toUpperCase).collect(Collectors.toList()); 

La biblioteca de Google Collections tiene muchas clases para trabajar con colecciones e iteradores a un nivel mucho más alto que el de los soportes de Java, y de manera funcional (filtro, mapa, pliegue, etc.). Define las interfaces y los métodos de función y predicado que los utilizan para procesar colecciones para que no tenga que hacerlo. También tiene funciones de conveniencia que hacen que lidiar con los generics de Java sea menos arduo.

También uso Hamcrest ** para filtrar colecciones.

Las dos bibliotecas son fáciles de combinar con clases de adaptadores.


** Declaración de interés: co-escribí Hamcrest

Apache Commons CollectionsUtil.transform (Collection, Transformer) es otra opción.

Estoy desarrollando este proyecto para escribir la comprensión de listas en Java, ahora es una prueba de concepto en https://github.com/farolfo/list-comprehension-in-java

Ejemplos

 // { x | x E {1,2,3,4} ^ x is even } // gives {2,4} Predicate even = x -> x % 2 == 0; List evens = new ListComprehension() .suchThat(x -> { x.belongsTo(Arrays.asList(1, 2, 3, 4)); x.is(even); }); // evens = {2,4}; 

Y si queremos transformar la expresión de salida de alguna manera como

 // { x * 2 | x E {1,2,3,4} ^ x is even } // gives {4,8} List duplicated = new ListComprehension() .giveMeAll((Integer x) -> x * 2) .suchThat(x -> { x.belongsTo(Arrays.asList(1, 2, 3, 4)); x.is(even); }); // duplicated = {4,8} 

Puedes usar lambdas para la función, así:

 class Comprehension { /** *in: List int *func: Function to do to each entry */ public List comp(List in, Function func) { List out = new ArrayList(); for(T o: in) { out.add(func.apply(o)); } return out; } } 

el uso:

 List stuff = new ArrayList(); stuff.add("a"); stuff.add("b"); stuff.add("c"); stuff.add("d"); stuff.add("cheese"); List newStuff = new Comprehension().comp(stuff, (a) -> { //The  tells the comprehension to return an ArrayList a.equals("a")? "1": (a.equals("b")? "2": (a.equals("c")? "3": (a.equals("d")? "4": a ))) }); 

volverá:

 ["1", "2", "3", "4", "cheese"]