Ejemplo de método Java 8 Streams FlatMap

85

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 flatMapcómo podría codificarlo en versiones anteriores de Java Java[6,7]y cómo puede codificar las mismas rutinas usando Java 8.

chiperortiz
fuente
2
Hay alrededor de un millón de ejemplos de uso de flatMap (al menos para Scala, y son básicamente los mismos :)) en Internet, ¿ha intentado buscar? Aquí hay uno para comenzar: brunton-spall.co.uk/post/2011/12/02/…
Peter Svensson
3
no entiendo Scala nunca he trabajado con scala
chiperortiz
Lo que quiero decir es que flatMap es un concepto general que ahora existe tanto en Java como en Scala.
Peter Svensson
ok leeré más sobre esto gracias hombre.
chiperortiz
10
flatMap en Java es la misma idea pero se ve bastante diferente con las transmisiones. ¡No dirija a la gente a Scala!
orbfish

Respuestas:

158

No tiene sentido para flatMapuna transmisión que ya es plana, como la Stream<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);
}
Nick Holt
fuente
113

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 flatMappodrí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 de int1 a 4, inclusive
  • IntStream.iterate(i, identity()).limit(i)crea una secuencia de longitud i de inti, por lo que aplicada i = 4crea una secuencia:4, 4, 4, 4
  • flatMap "aplana" la secuencia y la "concatena" con la secuencia original

Con 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 uno TimeSerieses esencialmente un Map<LocalDate, Double>. Quiero obtener una lista de todas las fechas para las que al menos una de las series de tiempo tiene un valor. flatMapal 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.

Assylias
fuente
14
Ambos ejemplos son mucho mejores que en la respuesta aceptada.
Sebastian Graf
el compilador se queja de la identidad () como indefinida
Nirmal
2
@ user3320018 necesita una importación estática Function.identity.
assylias
@assylias Intenté importar java.util.function.Function pero no funcionó, soy nuevo en Java 8 y esto puede o no ser específico de Java 8, pero ¿puede decirme exactamente cómo eliminar ese error?
Nirmal
4
import static java.util.function.Function.identity;
assylias
18

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:

Unique words: [confounded, incumbent, jailer, perjury, skimming, sporadic]
Igor Baiborodina
fuente
11

¿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 starty se repiten cada repeatPeriod repeatUnit(por ejemplo, 5 HORAS) y habrá repeatCountrecordatorios 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 a x.getRepeatCount()instancias. Sus valores comienzan desde x.getStart().toEpochSecond(ZoneOffset.UTC)y cada valor siguiente se calcula usando lambda p -> (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éflatMaplo 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!

Aleksandr Kravets
fuente
8
Am I the only one who finds unwinding lists boring?+1
whitfin el
3
Me cuesta mucho entender este ejemplo. :(
RBz
Las operaciones de @RBz Stream a veces no son fáciles de entender, especialmente si hay más de una operación involucrada. Sin embargo, es cuestión de práctica. Lo mejor que puede hacer es buscar en Google cada palabra que no esté clara de la muestra e intentar usarla usted mismo. De hecho, la muestra de estilo imperativo habitual habría sido mucho más fácil de entender (y a veces más rápida). Así que piensa si realmente necesitas usar streams.
Aleksandr Kravets
Gracias por la respuesta, hombre. Sin embargo, estoy bastante bien con los conceptos de streams. Lo que tengo problemas aquí es un ejemplo específico. No era tan bueno con la API de Time, pero ni siquiera leerlo no me ayuda a comprender lo que está sucediendo aquí. Puede ser que esté siendo ingenuo, pero será genial tener un poco más de explicación para tu respuesta. Realmente me ayudaría a comprender su ejemplo. Lo sé, ¡solo estoy atrapado por curiosidad! :)
RBz
Increíble ejemplo ... un poco difícil de entender al principio, pero una vez que lo ejecuto en mi IDE ... ¡una alternativa tan poderosa! muchas gracias !
Cristiano
2

Este 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/

lalitbhagtani
fuente
2

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:

Barry
Allen
Bruce
Wayne
Clark
Kent
Somaiah Kumbera
fuente
1

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 );
Mayordomo
fuente