Qual é a diferença entre os métodos map() e flatMap() em Java 8?
em Java 8, Qual é a diferença entre Stream.map()
e Stream.flatMap()
métodos?
19 answers
Ambos map
e flatMap
podem ser aplicados a Stream<T>
e ambos devolvem a Stream<R>
. A diferença é que a operação map
produz um valor de saída para cada valor de entrada, enquanto a operação flatMap
produz um número arbitrário (zero ou mais) para cada valor de entrada.
Isto reflecte-se nos argumentos de cada operação.
A operação map
toma um Function
, que é chamado para cada valor no fluxo de entrada e produz um valor de resultado, que é enviado para o fluxo de saída.
A operação flatMap
tem uma função que conceptualmente quer consumir um valor e produzir um número arbitrário de valores. No entanto, em Java, é complicado para um método retornar um número arbitrário de Valores, uma vez que os métodos podem retornar apenas zero ou um valor. Pode-se imaginar uma API onde a função mapper para flatMap
toma um valor e devolve um array ou um List
de valores, que são então enviados para a saída. Dado que esta é a biblioteca streams, um particularmente apt maneira de representar um número arbitrário de valores de retorno é para a função mapper em si para retornar um fluxo! Os valores do fluxo retornado pelo mapper são drenados do fluxo e são passados ao fluxo de saída. Os " clumps "de valores retornados por cada chamada para a função mapper não são distinguidos em tudo na corrente de saída, assim a saída é dita ter sido" achatada."
O uso típico é para a função mapper de flatMap
para retornar Stream.empty()
Se quiser para enviar valores zero, ou algo como Stream.of(a, b, c)
Se quiser devolver vários valores. Mas é claro que qualquer Riacho pode ser devolvido.
Stream.flatMap
, como pode ser adivinhado pelo seu nome, é a combinação de uma operação map
e uma operação flat
. Isso significa que você primeiro aplica uma função aos seus elementos, e então achatá-la. {[3] } apenas aplica uma função à corrente sem nivelar a corrente.
Para compreender em que consiste um fluxo, considere uma estrutura como {[4] } que tem "dois níveis". Achatar isto significa transformá-lo numa estrutura de "um nível": [ 1,2,3,4,5,6,7,8,9 ]
.
Eu gostaria de dar 2 Exemplos para obter um mais ponto de vista prático:
primeiro exemplo de utilização de map
:
@Test
public void convertStringToUpperCaseStreams() {
List<String> collected = Stream.of("a", "b", "hello") // Stream of String
.map(String::toUpperCase) // Returns a stream consisting of the results of applying the given function to the elements of this stream.
.collect(Collectors.toList());
assertEquals(asList("A", "B", "HELLO"), collected);
}
Nada de especial no primeiro exemplo, um Function
é aplicado para devolver o {[4] } em maiúsculas.
Segundo exemplo de utilização de flatMap
:
@Test
public void testflatMap() throws Exception {
List<Integer> together = Stream.of(asList(1, 2), asList(3, 4)) // Stream of List<Integer>
.flatMap(List::stream)
.map(integer -> integer + 1)
.collect(Collectors.toList());
assertEquals(asList(2, 3, 4, 5), together);
}
No segundo exemplo, um fluxo de lista é passado. Não é um fluxo de inteiros!
se uma função de transformação tem que ser usada (através do mapa), então primeiro o Stream tem que ser achatado para algo mais (um fluxo de inteiro).
se flatMap
for removido então o seguinte erro é devolvido: o operador + está indefinido para a lista de tipos de argumentos, int.
não é possível aplicar + 1 num List
de inteiros!
por favor, atravesse o post completamente para ter uma ideia clara,
Mapa vs mapa plano:
Para devolver um comprimento de cada palavra de uma lista, nós faríamos algo como abaixo..
Versão resumida abaixo
Quando coletamos duas listas, dadas abaixo
Sem mapa plano => [1,2],[1,1] => [[1,2],[1,1]] Aqui duas listas são colocados dentro de uma lista, então a saída será da lista que contém listas
Com mapa plano => [1,2],[1,1] => [1,2,1,1] Aqui duas listas são achatadas e apenas os valores são colocados na lista, então, a saída será de lista contendo apenas os elementos
Basicamente, junta todos os objectos a um.
# # a versão detalhada foi dada abaixo: -
Por exemplo:-
Considere uma lista ["PILHA", "OOOVVVER"] e estamos tentando retornar uma lista como ["STACKOVER"] (devolvendo apenas letras únicas daquela lista)
Inicialmente, faríamos algo como abaixo para devolver uma lista ["STACKOVER"] de ["STACK", "OOOVVVER"]
public class WordMap {
public static void main(String[] args) {
List<String> lst = Arrays.asList("STACK","OOOVER");
lst.stream().map(w->w.split("")).distinct().collect(Collectors.toList());
}
}
Aqui o problema é que o Lambda passado para o método do mapa devolve um array de texto para cada palavra, por isso o fluxo devolvido pelo método do mapa é na verdade de fluxo de tipo, mas o que precisamos é de fluxo para representar um fluxo de caracteres, abaixo a imagem ilustra o problema.
Figura A:
Você pode pensar que, nós podemos resolver este problema usando flatmap,
OK, vamos ver como resolver isso usando Map e Arrays.stream
Primeiro, vais precisar de um fluxo de personagens em vez de um fluxo de arrays. Há um método chamado Arrays.stream () que pegaria um array e produziria um stream, por exemplo:
String[] arrayOfWords = {"STACK", "OOOVVVER"};
Stream<String> streamOfWords = Arrays.stream(arrayOfWords);
streamOfWords.map(s->s.split("")) //Converting word in to array of letters
.map(Arrays::stream).distinct() //Make array in to separate stream
.collect(Collectors.toList());
O acima ainda não funciona, porque agora acabamos com uma lista de córregos (mais precisamente, Stream>), em vez disso, devemos primeiro converter cada palavra em um conjunto de letras individuais e, em seguida, fazer cada array em um fluxo separado
Usando flatMap devemos ser capazes de corrigir este problema como abaixo:
String[] arrayOfWords = {"STACK", "OOOVVVER"};
Stream<String> streamOfWords = Arrays.stream(arrayOfWords);
streamOfWords.map(s->s.split("")) //Converting word in to array of letters
.flatMap(Arrays::stream).distinct() //flattens each generated stream in to a single stream
.collect(Collectors.toList());
FlatMap executaria mapeamento de cada matriz não com fluxo, mas com o conteúdo desse fluxo. Todos os fluxos individuais que seriam gerados ao usar o mapa (Arrays:: stream) se fundem em um único fluxo. A figura B ilustra o efeito da utilização do método flatMap. Compare-o com o que o mapa faz na Figura A. Figura B
O método flatMap permite substituir cada valor de um fluxo por outro fluxo e, em seguida, junta todos os fluxos gerados em um único fluxo.
Uma Resposta de linha: flatMap
ajuda a achatar um Collection<Collection<T>>
em um Collection<T>
. Da mesma forma, também achatará um Optional<Optional<T>>
em Optional<T>
.
Como podes ver, com map()
apenas:
- o tipo intermédio é
Stream<List<Item>>
- o tipo de retorno é
List<List<Item>>
E com flatMap()
:
- o tipo intermédio é
Stream<Item>
- o tipo de retorno é
List<Item>
Este é o resultado do teste do Código utilizado abaixo:
-------- Without flatMap() -------------------------------
collect() returns: [[Laptop, Phone], [Mouse, Keyboard]]
-------- With flatMap() ----------------------------------
collect() returns: [Laptop, Phone, Mouse, Keyboard]
Código utilizado:
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public class Parcel {
String name;
List<String> items;
public Parcel(String name, String... items) {
this.name = name;
this.items = Arrays.asList(items);
}
public List<String> getItems() {
return items;
}
public static void main(String[] args) {
Parcel amazon = new Parcel("amazon", "Laptop", "Phone");
Parcel ebay = new Parcel("ebay", "Mouse", "Keyboard");
List<Parcel> parcels = Arrays.asList(amazon, ebay);
System.out.println("-------- Without flatMap() ---------------------------");
List<List<String>> mapReturn = parcels.stream()
.map(Parcel::getItems)
.collect(Collectors.toList());
System.out.println("\t collect() returns: " + mapReturn);
System.out.println("\n-------- With flatMap() ------------------------------");
List<String> flatMapReturn = parcels.stream()
.map(Parcel::getItems)
.flatMap(Collection::stream)
.collect(Collectors.toList());
System.out.println("\t collect() returns: " + flatMapReturn);
}
}
A função que passa para stream.map
Tem de devolver um objecto. Isso significa que cada objeto no fluxo de entrada resulta em exatamente um objeto no fluxo de saída.
A função que passa para stream.flatMap
devolve um fluxo para cada objecto. Isso significa que a função pode retornar qualquer número de objetos para cada objeto de entrada (incluindo nenhum). Os fluxos resultantes são então concatenados a um fluxo de saída.
Para um mapa temos uma lista de elementos e uma (função, acção) F so:
[a,b,c] f(x) => [f(a),f(b),f(c)]
E para o mapa plano temos uma lista de elementos e temos uma (função,acção) f e queremos que o resultado seja achatado :
[[a,b],[c,d,e]] f(x) =>[f(a),f(b),f(c),f(d),f(e)]
map
funciona que deve ser bastante fácil de entender.
Há casos em que podemos acabar com estruturas aninhadas indesejadas quando usamos map()
, o método flatMap()
foi concebido para ultrapassar isto evitando envolver.
Exemplos:
1
List<List<Integer>> result = Stream.of(Arrays.asList(1), Arrays.asList(2, 3))
.collect(Collectors.toList());
Podemos evitar ter listas aninhadas usando flatMap
:
List<Integer> result = Stream.of(Arrays.asList(1), Arrays.asList(2, 3))
.flatMap(i -> i.stream())
.collect(Collectors.toList());
2
Optional<Optional<String>> result = Optional.of(42)
.map(id -> findById(id));
Optional<String> result = Optional.of(42)
.flatMap(id -> findById(id));
Em que:
private Optional<String> findById(Integer id)
.mapa é para A -> B
mapeamento
Stream.of("dog", "cat") // stream of 2 Strings
.map(s -> s.length()) // stream of 2 Integers: [3, 3]
converte qualquer item A
em qualquer item B
. Javadoc
.flatMap é para A -> Stream< B>
concatinating
Stream.of("dog", "cat") // stream of 2 Strings
.flatMapToInt(s -> s.chars()) // stream of 6 ints: [d, o, g, c, a, t]
Ele --1 converte qualquer item A
em Stream< B>
, Então -- 2 concatenaliza todos os fluxos em um único fluxo (plano). Javadoc
Nota 1: Embora este último exemplo flutue num fluxo de primitivos (IntStream) em vez de um fluxo de objetos (Stream), ele ainda ilustra a idéia do .flatMap
.
Nota 2: Apesar do nome, String.o método chars () devolve ints. Então, a coleção real será: [100, 111, 103, 99, 97, 116]
, em que {[10] } é o código de 'd'
, 111
é o código de 'o'
etc. Mais uma vez, para fins ilustrativos, é apresentado como [d, o, g, c, A, t].
String version = computer.map(Computer::getSoundcard)
.map(Soundcard::getUSB)
.map(USB::getVersion)
.orElse("UNKNOWN");
Infelizmente, este código não compila. Por quê? O computador variável é do tipoOptional<Computer>
, por isso é perfeitamente correcto chamar o método do mapa. Contudo, o getSoundcard () devolve um objecto do tipo Opcional. Isto significa que o resultado da operação map é um objecto do tipoOptional<Optional<Soundcard>>
. Como resultado, o convite para o getUSB () é inválido porque o opcional mais externo contém como seu value outro opcional, que, claro, não suporta o getUSB () metodo.Com as transmissões, O método flatMap assume uma função como argumento, que devolve outro Riacho. Esta função é aplicada a cada elemento de um riacho, o que resultaria num riacho de riachos. Entanto, flatMap tem o efeito de substituir cada fluxo gerado pelo o conteúdo daquele Riacho. Em outras palavras, todos os fluxos separados que são gerados pela função ser amalgamado ou "achatado" em um um único Riacho. O que queremos aqui é algo semelhante, mas queremos "nivelar" uma opção de dois níveis em um.
Opcional também suporta um método flatMap. O seu objectivo é aplicar a função de transformação no valor de um opcional (tal como o mapa operação faz) e então nivelar o resultado de dois níveis opcional em um único.
Então, para fazer nosso código correto, precisamos reescrevê-lo como segue usando mapa plano:
String version = computer.flatMap(Computer::getSoundcard)
.flatMap(Soundcard::getUSB)
.map(USB::getVersion)
.orElse("UNKNOWN");
O primeiro mapa plano garante que um
Optional<Soundcard>
é devolvido em vez de umOptional<Optional<Soundcard>>
, e o segundo plano plano atinge o mesmo propósito de devolver umOptional<USB>
. Note - se que o a terceira chamada só precisa de ser um mapa () porque o getVersion () devolve um String em vez de um objeto opcional.
Http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html
Mapa () e plano()
map()
Basta tomar uma função um lambda param onde T é elemento e r o elemento de retorno construído usando T. No final teremos um fluxo com objetos do tipo R. um exemplo simples pode ser:
Stream
.of(1,2,3,4,5)
.map(myInt -> "preFix_"+myInt)
.forEach(System.out::println);
Ele simplesmente pega os elementos 1 a 5 do tipo Integer
, usa cada elemento para construir um novo elemento do tipo String
com o valor "prefix_"+integer_value
e imprime-o.
flatMap()
F<T, R>
onde
T é um tipo de que um riacho pode ser construído a partir de / com . Pode ser uma lista (T. stream ()), uma array (Arrays.stream (somarray), etc.. qualquer coisa a partir da qual um riacho pode estar com/ou forma. no exemplo abaixo cada dev tem muitas línguas, assim dev. Languages é uma lista e vai usar um parâmetro lambda.
R é o fluxo resultante que será construído usando T. sabendo que temos muitos exemplos de T, teremos naturalmente muitos córregos de R. todos esses córregos do tipo R serão agora combinados em um único fluxo 'plano' do tipo R.
Exemplo
Os exemplos de Bachiri Taoufiq veja a sua resposta aqui são simples e fáceis de entender. Só para esclarecer, digamos que temos uma equipa de desenvolvedores:dev_team = {dev_1,dev_2,dev_3}
, com cada desenvolvedor a conhecer muitas línguas:
dev_1 = {lang_a,lang_b,lang_c},
dev_2 = {lang_d},
dev_2 = {lang_e,lang_f}
Aplicação Transmissão.mapa () on dev_team to get the languages of each dev:
dev_team.map(dev -> dev.getLanguages())
Dar-lhe-á esta estrutura:
{
{lang_a,lang_b,lang_c},
{lang_d},
{lang_e,lang_f}
}
O que é basicamente um List<List<Languages>> /Object[Languages[]]
. Nem tão bonito, nem tão parecido com o Java8!!
Com Stream.flatMap()
pode-se 'achatar' as coisas à medida que se retira a estrutura acima
e transforma-o em {lang_a, lang_b, lang_c, lang_d, lang_e, lang_f}
, que pode basicamente ser usado como List<Languages>/Language[]/etc
...
dev_team
.stream() /* {dev_1,dev_2,dev_3} */
.map(dev -> dev.getLanguages()) /* {{lang_a,...,lang_c},{lang_d}{lang_e,lang_f}}} */
.flatMap(languages -> languages.stream()) /* {lang_a,...,lang_d, lang_e, lang_f} */
.doWhateverWithYourNewStreamHere();
Ou simplesmente:
dev_team
.stream() /* {dev_1,dev_2,dev_3} */
.flatMap(dev -> dev.getLanguages().stream()) /* {lang_a,...,lang_d, lang_e, lang_f} */
.doWhateverWithYourNewStreamHere();
Quando usar o map () e usar mapa plano():
Use
map()
quando cada elemento do tipo T, a partir de seu fluxo é suposto ser mapeada/transformada única elemento do tipo R. O resultado é um mapeamento do tipo (1 elemento de início -> 1 elemento) e o novo fluxo de elementos do tipo R é retornado.Use
flatMap()
quando cada elemento do tipo T do seu fluxo é suposto mapear/transformar para uma Colecções de elementos do tipo R. O resultado é um mapeamento do tipo (1 elemento inicial -> n elementos finais) . Essas Coleções são, em seguida, mesclado (ou achatada) para um novo fluxo de elementos do tipo R. Isso é útil, por exemplo, para representar loops aninhados.
Pre Java 8:
List<Foo> myFoos = new ArrayList<Foo>();
for(Foo foo: myFoos){
for(Bar bar: foo.getMyBars()){
System.out.println(bar.getMyName());
}
}
Depois De Java 8
myFoos
.stream()
.flatMap(foo -> foo.getMyBars().stream())
.forEach(bar -> System.out.println(bar.getMyName()));
map
está a transformar essa maçã em apple-juice
por exemplo, ou um mapeamento de um para um .
Pega nessa mesma maçã e tira apenas as sementes dela, é isso que flatMap
faz, ou um um a muitos , uma maçã como entrada, muitas sementes como saída.
Mapa:- Este método leva uma função como um argumento e retorna um novo fluxo consistindo dos resultados gerados pela aplicação da função passada a todos os elementos do fluxo.
Vamos imaginar, eu tenho uma lista de valores inteiros ( 1,2,3,4,5 ) e uma interface de função cuja lógica é quadrada do inteiro passado. (e - > E * e ).
List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> newList = intList.stream().map( e -> e * e ).collect(Collectors.toList());
System.out.println(newList);
Resultado: -
[1, 4, 9, 16, 25]
Como pode ver, uma saída é uma nova corrente cujos valores são quadrados dos valores da entrada fluxo.
[1, 2, 3, 4, 5] -> apply e -> e * e -> [ 1*1, 2*2, 3*3, 4*4, 5*5 ] -> [1, 4, 9, 16, 25 ]
Http://codedestine.com/java-8-stream-map-method/
Plano :- Este método leva uma função como um 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 a cada elemento desta corrente, ela produz uma corrente de novos valores. Todos os elementos destes novos fluxos gerados por cada elemento são então copiados para um novo fluxo, que será um valor de retorno deste método.
Vamos ver, eu tenho uma lista de objetos estudantis, onde cada estudante pode optar por várias disciplinas.List<Student> studentList = new ArrayList<Student>();
studentList.add(new Student("Robert","5st grade", Arrays.asList(new String[]{"history","math","geography"})));
studentList.add(new Student("Martin","8st grade", Arrays.asList(new String[]{"economics","biology"})));
studentList.add(new Student("Robert","9st grade", Arrays.asList(new String[]{"science","math"})));
Set<Student> courses = studentList.stream().flatMap( e -> e.getCourse().stream()).collect(Collectors.toSet());
System.out.println(courses);
Resultado: -
[economics, biology, geography, science, history, math]
Como você pode ver, uma saída é uma nova corrente cujos valores são uma coleção de todos os elementos dos fluxos retornam por cada elemento da Corrente de entrada.
[ S1 , S2, S3 ] -> [{"history","math","geography"}, {"economics","biology"}, {"science","math"} ] -> take unique subjects - > [economics, biology, geography, Ciência, História, Matemática
Se você pensa map()
como uma iteração (um nível for
loop), flatmap()
é uma iteração de dois níveis(como um loop nested for
). (Digite cada elemento iterado foo
, e faça foo.getBarList()
e itere nisso barList
de novo)
map()
: pega num fluxo, faz algo a cada elemento, recolhe o único resultado de cada processo, emite outro fluxo. A definição de "fazer algo função" está implícita. Se o processo de qualquer elemento resultar em null
, null
é utilizado para compor a fluxo. Assim, o número de elementos no fluxo resultante será igual ao número de fluxo de entrada.
flatmap()
: Pegue um fluxo de elementos / fluxos e uma função (definição explícita), aplique a função a cada elemento de cada fluxo, e colete todo o fluxo intermediário resultante para ser um fluxo maior ("achatamento"). Se o processo de qualquer elemento resultar em null
, o fluxo vazio é fornecido ao passo final de "achatamento". O número de elementos no resultado stream, é o total de todos os elementos participantes em todas as entradas, se a entrada for vários fluxos.
A operação map
pode produzir um Stream
de Stream
.EX Stream<Stream<Integer>>
flatMap
a operação só produzirá algo. EX Stream<Integer>
Também uma boa analogia pode ser com C# Se estiver familiarizado. Basicamente C# Select
semelhante ao java map
e C# SelectMany
java flatMap
. O mesmo se aplica ao Kotlin para colecções.
map
emite um item para cada item da lista e flatMap
é basicamente um map
+ flatten
operação. Para ser mais claro, use flatMap quando necessitar de mais de um valor, por exemplo, quando estiver à espera de um loop para devolver as arrays, flatMap será realmente útil neste caso.
Eu escrevi um blog sobre isso, você pode conferir aqui.
As operações de Fluxo flatMap
e map
aceitam uma função como entrada.
flatMap
espera que a função retorne um novo fluxo para cada elemento do fluxo e retorna um fluxo que combina todos os elementos dos fluxos retornados pela função para cada elemento. Em outras palavras, com flatMap
, para cada elemento da fonte, vários elementos serão criados pela função. http://www.zoftino.com/java-stream-examples#flatmap-operation
map
espera que função para devolver um valor transformado e retorna um novo fluxo contendo os elementos transformados. Em outras palavras, com map
, para cada elemento da fonte, um elemento transformado será criado pela função.
http://www.zoftino.com/java-stream-examples#map-operation
flatMap()
também tira vantagem da avaliação parcial preguiçosa de córregos. Ele vai ler o fluxo de punho e só quando necessário, vai para o próximo fluxo. O comportamento é explicado em detalhe aqui: é garantido flatMap ser preguiçoso?