Tenho vindo a verificar a próxima Java update
, a saber: Java 8 or JDK 8
. Sim, estou impaciente, há muitas coisas novas, mas há algo que não entendo, um código simples:
final Stream<Integer>stream = Stream.of(1,2,3,4,5,6,7,8,9,10);
stream.flatMap();
os javadocs são
public <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
Retorna um fluxo que consiste nos resultados da substituição de cada elemento deste fluxo com o conteúdo de um fluxo mapeado produzido aplicando a função de mapeamento fornecida a cada elemento. Cada fluxo mapeado é fechado após seu conteúdo ter sido colocado neste fluxo. (Se um fluxo mapeado for nulo, um fluxo vazio será usado, em vez disso.) Esta é uma operação intermediária.
Eu apreciaria se alguém criasse alguns exemplos simples da vida real sobre flatMap
como você poderia codificá-lo em versões anteriores do Java Java[6,7]
e como você pode codificar as mesmas rotinas usando Java 8
.
fonte
Respostas:
Não faz sentido para
flatMap
um stream que já está plano, como oStream<Integer>
que você mostrou em sua pergunta.No entanto, se você tivesse um
Stream<List<Integer>>
, faria sentido e você poderia fazer o seguinte: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 imprimiria:
1 2 3 4 5
Para fazer isso antes do Java 8, você só precisa de loops:
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); }
fonte
Exemplo inventado
Imagine que você deseja criar a seguinte sequência: 1, 2, 2, 3, 3, 3, 4, 4, 4, 4 etc. (em outras palavras: 1x1, 2x2, 3x3 etc.)
Com
flatMap
ele pode ser parecido com:IntStream sequence = IntStream.rangeClosed(1, 4) .flatMap(i -> IntStream.iterate(i, identity()).limit(i)); sequence.forEach(System.out::println);
Onde:
IntStream.rangeClosed(1, 4)
cria um fluxo deint
1 a 4, inclusiveIntStream.iterate(i, identity()).limit(i)
cria um fluxo de comprimento i deint
i - então aplicado ai = 4
ele cria um fluxo:4, 4, 4, 4
flatMap
"nivela" o fluxo e "concatena-o" com o fluxo originalCom Java <8, você precisaria de dois loops aninhados:
List<Integer> list = new ArrayList<>(); for (int i = 1; i <= 4; i++) { for (int j = 0; j < i; j++) { list.add(i); } }
Exemplo do mundo real
Digamos que eu tenha um
List<TimeSeries>
onde cadaTimeSeries
é essencialmente umMap<LocalDate, Double>
. Desejo obter uma lista de todas as datas para as quais pelo menos uma das séries temporais tem um valor.flatMap
para o resgate:list.stream().parallel() .flatMap(ts -> ts.dates().stream()) // for each TS, stream dates and flatmap .distinct() // remove duplicates .sorted() // sort ascending .collect(toList());
Não apenas é legível, mas se você de repente precisar processar 100k elementos, simplesmente adicionar
parallel()
irá melhorar o desempenho sem você escrever nenhum código simultâneo.fonte
Function.identity
.import static java.util.function.Function.identity;
Extraia palavras exclusivas classificadas ASC de uma 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);
... e a saída:
fonte
Sou o único que acha aborrecido descomprimir listas? ;-)
Vamos tentar com objetos. A propósito, exemplo do mundo real.
Dado: Objeto que representa tarefa repetitiva. Sobre campos de tarefas importantes: os lembretes começam a tocar
start
e se repetem a cadarepeatPeriod
repeatUnit
(por exemplo, 5 HORAS) e haverárepeatCount
lembretes no total (incluindo o início).Objetivo: obter uma lista de cópias de tarefas, uma para cada chamada de lembrete de tarefa.
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);
Resultado:
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}
PS: Eu agradeceria se alguém sugerisse uma solução mais simples, afinal não sou um profissional.
ATUALIZAÇÃO: @RBz pediu uma explicação detalhada, então aqui está. Basicamente, flatMap coloca todos os elementos de streams dentro de outro stream no stream de saída. Muitos streams aqui :). Portanto, para cada Tarefa no fluxo inicial, a expressão lambda
x -> LongStream.iterate...
cria um fluxo de valores longos que representam os momentos de início da tarefa. Este fluxo é limitado ax.getRepeatCount()
instâncias. Seus valores começam emx.getStart().toEpochSecond(ZoneOffset.UTC)
e cada próximo valor é calculado usando lambdap -> (p + x.getRepeatPeriod()*x.getRepeatUnit().getDuration().getSeconds()
.boxed()
retorna o stream com cada valor longo como uma instância de wrapper Long. Em seguida, cada Long nesse fluxo é mapeado para uma nova instância de Task que não é mais repetitiva e contém o tempo de execução exato. Este exemplo contém apenas uma tarefa na lista de entrada. Mas imagine que você tem mil. Você terá então um fluxo de 1000 fluxos de objetos Task. E o queflatMap
O que faz aqui é colocar todas as tarefas de todos os fluxos no mesmo fluxo de saída. Isso é tudo que eu entendo. Obrigado por sua pergunta!fonte
Am I the only one who finds unwinding lists boring?
+1Este método recebe uma Função como argumento, esta função aceita um parâmetro T como um argumento de entrada e retorna um fluxo do parâmetro R como um valor de retorno. Quando esta função é aplicada em cada elemento deste fluxo, ela produz um fluxo de novos valores. Todos os elementos desses novos fluxos gerados por cada elemento são então copiados para um novo fluxo, que será um valor de retorno desse método.
http://codedestine.com/java-8-stream-flatmap-method/
fonte
Um exemplo muito simples: divida uma lista de nomes completos para obter uma lista de nomes, independentemente do primeiro ou último
List<String> fullNames = Arrays.asList("Barry Allen", "Bruce Wayne", "Clark Kent"); fullNames.stream() .flatMap(fullName -> Pattern.compile(" ").splitAsStream(fullName)) .forEach(System.out::println);
Isso imprime:
fonte
Dado isso:
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 + '}'; } }
e isto:
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() { } }
Podemos então fazer isso:
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 );
fonte