He estado revisando la próxima Java update
, a saber: Java 8 or JDK 8
. Sí, estoy impaciente, hay muchas cosas nuevas, pero hay algo que no entiendo, un código simple:
final Stream<Integer>stream = Stream.of(1,2,3,4,5,6,7,8,9,10);
stream.flatMap();
los javadocs son
public <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
Devuelve un flujo que consta de los resultados de reemplazar cada elemento de este flujo con el contenido de un flujo mapeado producido al aplicar la función de mapeo proporcionada a cada elemento. Cada flujo mapeado se cierra después de que su contenido se haya colocado en este flujo. (Si un flujo mapeado es nulo, en su lugar se usa un flujo vacío). Esta es una operación intermedia.
Agradecería que alguien creara algunos ejemplos simples de la vida real sobre flatMap
cómo podría codificarlo en versiones anteriores de Java Java[6,7]
y cómo puede codificar las mismas rutinas usando Java 8
.
fuente
Respuestas:
No tiene sentido para
flatMap
una transmisión que ya es plana, como laStream<Integer>
que mostró en su pregunta.Sin embargo, si tuvieras un,
Stream<List<Integer>>
entonces tendría sentido y podrías hacer esto:Stream<List<Integer>> integerListStream = Stream.of( Arrays.asList(1, 2), Arrays.asList(3, 4), Arrays.asList(5) ); Stream<Integer> integerStream = integerListStream .flatMap(Collection::stream); integerStream.forEach(System.out::println);
Que imprimiría:
1 2 3 4 5
Para hacer esto antes de Java 8, solo necesita un bucle:
List<List<Integer>> integerLists = Arrays.asList( Arrays.asList(1, 2), Arrays.asList(3, 4), Arrays.asList(5) ) List<Integer> flattened = new ArrayList<>(); for (List<Integer> integerList : integerLists) { flattened.addAll(integerList); } for (Integer i : flattened) { System.out.println(i); }
fuente
Ejemplo inventado
Imagina que quieres crear la siguiente secuencia: 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, etc. (en otras palabras: 1x1, 2x2, 3x3, etc.)
Con
flatMap
podría verse así:IntStream sequence = IntStream.rangeClosed(1, 4) .flatMap(i -> IntStream.iterate(i, identity()).limit(i)); sequence.forEach(System.out::println);
dónde:
IntStream.rangeClosed(1, 4)
crea un flujo deint
1 a 4, inclusiveIntStream.iterate(i, identity()).limit(i)
crea una secuencia de longitud i deint
i, por lo que aplicadai = 4
crea una secuencia:4, 4, 4, 4
flatMap
"aplana" la secuencia y la "concatena" con la secuencia originalCon Java <8, necesitaría dos bucles anidados:
List<Integer> list = new ArrayList<>(); for (int i = 1; i <= 4; i++) { for (int j = 0; j < i; j++) { list.add(i); } }
Ejemplo del mundo real
Digamos que tengo un
List<TimeSeries>
donde cada unoTimeSeries
es esencialmente unMap<LocalDate, Double>
. Quiero obtener una lista de todas las fechas para las que al menos una de las series de tiempo tiene un valor.flatMap
al rescate:list.stream().parallel() .flatMap(ts -> ts.dates().stream()) // for each TS, stream dates and flatmap .distinct() // remove duplicates .sorted() // sort ascending .collect(toList());
No solo es legible, sino que si de repente necesita procesar 100k elementos, simplemente agregar
parallel()
mejorará el rendimiento sin que tenga que escribir ningún código simultáneo.fuente
Function.identity
.import static java.util.function.Function.identity;
Extraiga palabras únicas ordenadas ASC de una lista de frases:
List<String> phrases = Arrays.asList( "sporadic perjury", "confounded skimming", "incumbent jailer", "confounded jailer"); List<String> uniqueWords = phrases .stream() .flatMap(phrase -> Stream.of(phrase.split("\\s+"))) .distinct() .sorted() .collect(Collectors.toList()); System.out.println("Unique words: " + uniqueWords);
... y la salida:
fuente
¿Soy el único que encuentra aburrido deshacer las listas? ;-)
Probemos con objetos. Por cierto, ejemplo del mundo real.
Dado: Objeto que representa una tarea repetitiva. Acerca de los campos de tareas importantes: los recordatorios comienzan a sonar
start
y se repiten cadarepeatPeriod
repeatUnit
(por ejemplo, 5 HORAS) y habrárepeatCount
recordatorios en total (incluido el inicio).Objetivo: lograr una lista de copias de tareas, una para cada invocación de recordatorio de tareas.
List<Task> tasks = Arrays.asList( new Task( false,//completed sign "My important task",//task name (text) LocalDateTime.now().plus(2, ChronoUnit.DAYS),//first reminder(start) true,//is task repetitive? 1,//reminder interval ChronoUnit.DAYS,//interval unit 5//total number of reminders ) ); tasks.stream().flatMap( x -> LongStream.iterate( x.getStart().toEpochSecond(ZoneOffset.UTC), p -> (p + x.getRepeatPeriod()*x.getRepeatUnit().getDuration().getSeconds()) ).limit(x.getRepeatCount()).boxed() .map( y -> new Task(x,LocalDateTime.ofEpochSecond(y,0,ZoneOffset.UTC))) ).forEach(System.out::println);
Salida:
Task{completed=false, text='My important task', start=2014-10-01T21:35:24, repeat=false, repeatCount=0, repeatPeriod=0, repeatUnit=null} Task{completed=false, text='My important task', start=2014-10-02T21:35:24, repeat=false, repeatCount=0, repeatPeriod=0, repeatUnit=null} Task{completed=false, text='My important task', start=2014-10-03T21:35:24, repeat=false, repeatCount=0, repeatPeriod=0, repeatUnit=null} Task{completed=false, text='My important task', start=2014-10-04T21:35:24, repeat=false, repeatCount=0, repeatPeriod=0, repeatUnit=null} Task{completed=false, text='My important task', start=2014-10-05T21:35:24, repeat=false, repeatCount=0, repeatPeriod=0, repeatUnit=null}
PD: Agradecería que alguien sugiriera una solución más simple, no soy un profesional después de todo.
ACTUALIZACIÓN: @RBz solicitó una explicación detallada, así que aquí está. Básicamente, flatMap coloca todos los elementos de los flujos dentro de otro flujo en el flujo de salida. Muchas transmisiones aquí :). Por lo tanto, para cada tarea en el flujo inicial, la expresión lambda
x -> LongStream.iterate...
crea un flujo de valores largos que representan los momentos de inicio de la tarea. Esta secuencia está limitada ax.getRepeatCount()
instancias. Sus valores comienzan desdex.getStart().toEpochSecond(ZoneOffset.UTC)
y cada valor siguiente se calcula usando lambdap -> (p + x.getRepeatPeriod()*x.getRepeatUnit().getDuration().getSeconds()
.boxed()
devuelve la secuencia con cada valor largo como una instancia de contenedor Long. Luego, cada Long en esa secuencia se asigna a una nueva instancia de Task que ya no es repetitiva y contiene el tiempo de ejecución exacto. Esta muestra contiene solo una tarea en la lista de entrada. Pero imagina que tienes mil. Entonces tendrá una secuencia de 1000 secuencias de objetos de Tarea. Y quéflatMap
lo que hace aquí es poner todas las tareas de todos los flujos en el mismo flujo de salida. Eso es todo lo que yo entiendo. ¡Gracias por su pregunta!fuente
Am I the only one who finds unwinding lists boring?
+1Este método toma una función como argumento, esta función acepta un parámetro T como argumento de entrada y devuelve un flujo de parámetro R como valor de retorno. Cuando esta función se aplica a cada elemento de esta secuencia, produce una secuencia de nuevos valores. Todos los elementos de estos nuevos flujos generados por cada elemento se copian luego en un nuevo flujo, que será un valor de retorno de este método.
http://codedestine.com/java-8-stream-flatmap-method/
fuente
Un ejemplo muy simple: divida una lista de nombres completos para obtener una lista de nombres, independientemente del nombre o apellido
List<String> fullNames = Arrays.asList("Barry Allen", "Bruce Wayne", "Clark Kent"); fullNames.stream() .flatMap(fullName -> Pattern.compile(" ").splitAsStream(fullName)) .forEach(System.out::println);
Esto imprime:
fuente
Dado este:
public class SalesTerritory { private String territoryName; private Set<String> geographicExtents; public SalesTerritory( String territoryName, Set<String> zipCodes ) { this.territoryName = territoryName; this.geographicExtents = zipCodes; } public String getTerritoryName() { return territoryName; } public void setTerritoryName( String territoryName ) { this.territoryName = territoryName; } public Set<String> getGeographicExtents() { return geographicExtents != null ? Collections.unmodifiableSet( geographicExtents ) : Collections.emptySet(); } public void setGeographicExtents( Set<String> geographicExtents ) { this.geographicExtents = new HashSet<>( geographicExtents ); } @Override public int hashCode() { int hash = 7; hash = 53 * hash + Objects.hashCode( this.territoryName ); return hash; } @Override public boolean equals( Object obj ) { if ( this == obj ) { return true; } if ( obj == null ) { return false; } if ( getClass() != obj.getClass() ) { return false; } final SalesTerritory other = (SalesTerritory) obj; if ( !Objects.equals( this.territoryName, other.territoryName ) ) { return false; } return true; } @Override public String toString() { return "SalesTerritory{" + "territoryName=" + territoryName + ", geographicExtents=" + geographicExtents + '}'; } }
y esto:
public class SalesTerritories { private static final Set<SalesTerritory> territories = new HashSet<>( Arrays.asList( new SalesTerritory[]{ new SalesTerritory( "North-East, USA", new HashSet<>( Arrays.asList( new String[]{ "Maine", "New Hampshire", "Vermont", "Rhode Island", "Massachusetts", "Connecticut", "New York", "New Jersey", "Delaware", "Maryland", "Eastern Pennsylvania", "District of Columbia" } ) ) ), new SalesTerritory( "Appalachia, USA", new HashSet<>( Arrays.asList( new String[]{ "West-Virgina", "Kentucky", "Western Pennsylvania" } ) ) ), new SalesTerritory( "South-East, USA", new HashSet<>( Arrays.asList( new String[]{ "Virginia", "North Carolina", "South Carolina", "Georgia", "Florida", "Alabama", "Tennessee", "Mississippi", "Arkansas", "Louisiana" } ) ) ), new SalesTerritory( "Mid-West, USA", new HashSet<>( Arrays.asList( new String[]{ "Ohio", "Michigan", "Wisconsin", "Minnesota", "Iowa", "Missouri", "Illinois", "Indiana" } ) ) ), new SalesTerritory( "Great Plains, USA", new HashSet<>( Arrays.asList( new String[]{ "Oklahoma", "Kansas", "Nebraska", "South Dakota", "North Dakota", "Eastern Montana", "Wyoming", "Colorada" } ) ) ), new SalesTerritory( "Rocky Mountain, USA", new HashSet<>( Arrays.asList( new String[]{ "Western Montana", "Idaho", "Utah", "Nevada" } ) ) ), new SalesTerritory( "South-West, USA", new HashSet<>( Arrays.asList( new String[]{ "Arizona", "New Mexico", "Texas" } ) ) ), new SalesTerritory( "Pacific North-West, USA", new HashSet<>( Arrays.asList( new String[]{ "Washington", "Oregon", "Alaska" } ) ) ), new SalesTerritory( "Pacific South-West, USA", new HashSet<>( Arrays.asList( new String[]{ "California", "Hawaii" } ) ) ) } ) ); public static Set<SalesTerritory> getAllTerritories() { return Collections.unmodifiableSet( territories ); } private SalesTerritories() { } }
Entonces podemos hacer esto:
System.out.println(); System.out .println( "We can use 'flatMap' in combination with the 'AbstractMap.SimpleEntry' class to flatten a hierarchical data-structure to a set of Key/Value pairs..." ); SalesTerritories.getAllTerritories() .stream() .flatMap( t -> t.getGeographicExtents() .stream() .map( ge -> new SimpleEntry<>( t.getTerritoryName(), ge ) ) ) .map( e -> String.format( "%-30s : %s", e.getKey(), e.getValue() ) ) .forEach( System.out::println );
fuente