Sprawdzałem nadchodzące Java update
, a mianowicie:Java 8 or JDK 8
. Tak, nie mogę się doczekać, jest dużo nowych rzeczy, ale jest coś, czego nie rozumiem, prosty kod:
final Stream<Integer>stream = Stream.of(1,2,3,4,5,6,7,8,9,10);
stream.flatMap();
javadocs są
public <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
Zwraca strumień składający się z wyników zastąpienia każdego elementu tego strumienia zawartością zmapowanego strumienia utworzonego przez zastosowanie dostarczonej funkcji mapowania do każdego elementu. Każdy zmapowany strumień jest zamykany po umieszczeniu jego zawartości w tym strumieniu. (Jeśli zmapowany strumień ma wartość null, zamiast tego używany jest pusty strumień). Jest to operacja pośrednia.
Byłbym wdzięczny, gdyby ktoś stworzył proste przykłady z życia wzięte flatMap
, jak można to zakodować w poprzednich wersjach Java Java[6,7]
i jak można zakodować te same procedury przy użyciu Java 8
.
źródło
Odpowiedzi:
Nie ma to sensu w
flatMap
przypadku strumienia, który jest już płaski, jak tenStream<Integer>
, który pokazałeś w swoim pytaniu.Jednak gdybyś miał
Stream<List<Integer>>
to miałoby to sens i możesz to zrobić: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);
Który wydrukowałby:
1 2 3 4 5
Aby to zrobić przed Java 8, potrzebujesz tylko pętli:
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); }
źródło
Zmyślony przykład
Wyobraź sobie, że chcesz stworzyć następującą sekwencję: 1, 2, 2, 3, 3, 3, 4, 4, 4, 4 itd. (Innymi słowy: 1x1, 2x2, 3x3 itd.)
Dzięki
flatMap
temu mogłoby wyglądać:IntStream sequence = IntStream.rangeClosed(1, 4) .flatMap(i -> IntStream.iterate(i, identity()).limit(i)); sequence.forEach(System.out::println);
gdzie:
IntStream.rangeClosed(1, 4)
tworzy strumieńint
od 1 do 4 włącznieIntStream.iterate(i, identity()).limit(i)
tworzy strumień o długości i zint
i - zastosowany doi = 4
niego tworzy strumień:4, 4, 4, 4
flatMap
„spłaszcza” strumień i „łączy” go z oryginalnym strumieniemW Javie <8 potrzebujesz dwóch zagnieżdżonych pętli:
List<Integer> list = new ArrayList<>(); for (int i = 1; i <= 4; i++) { for (int j = 0; j < i; j++) { list.add(i); } }
Przykład z prawdziwego świata
Powiedzmy, że mam
List<TimeSeries>
gdzie każdyTimeSeries
jest zasadniczo aMap<LocalDate, Double>
. Chcę uzyskać listę wszystkich dat, dla których co najmniej jeden szereg czasowy ma wartość.flatMap
na pomoc:list.stream().parallel() .flatMap(ts -> ts.dates().stream()) // for each TS, stream dates and flatmap .distinct() // remove duplicates .sorted() // sort ascending .collect(toList());
Jest nie tylko czytelny, ale jeśli nagle potrzebujesz przetworzyć 100k elementów, samo dodanie
parallel()
poprawi wydajność bez pisania współbieżnego kodu.źródło
Function.identity
.import static java.util.function.Function.identity;
Wyodrębnij unikalne słowa posortowane ASC z listy fraz:
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);
... i wyjście:
źródło
Czy tylko ja uważam, że listy rozwijania są nudne? ;-)
Spróbujmy z przedmiotami. Nawiasem mówiąc, przykład z prawdziwego świata.
Dane: Obiekt reprezentujący powtarzalne zadanie. O ważnych polach zadań: przypomnienia zaczynają dzwonić
start
i powtarzać się corepeatPeriod
repeatUnit
(np. 5 GODZIN) i będąrepeatCount
przypomnienia w sumie (w tym jedno początkowe).Cel: uzyskać listę kopii zadań, po jednej na każde wywołanie przypomnienia o zadaniu.
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);
Wynik:
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: Byłbym wdzięczny, gdyby ktoś zaproponował prostsze rozwiązanie, w końcu nie jestem profesjonalistą.
UPDATE: @RBz poprosił o szczegółowe wyjaśnienie, więc oto jest. Zasadniczo flatMap umieszcza wszystkie elementy ze strumieni wewnątrz innego strumienia w strumieniu wyjściowym. Dużo tu streamów :). Tak więc dla każdego zadania w początkowym wyrażeniu lambda strumienia
x -> LongStream.iterate...
tworzy strumień długich wartości, które reprezentują momenty rozpoczęcia zadania. Ten strumień jest ograniczony dox.getRepeatCount()
instancji. Jego wartości zaczynają się od,x.getStart().toEpochSecond(ZoneOffset.UTC)
a każda następna wartość jest obliczana za pomocą lambdap -> (p + x.getRepeatPeriod()*x.getRepeatUnit().getDuration().getSeconds()
.boxed()
zwraca strumień z każdą długą wartością jako instancję opakowania Long. Następnie każdy Long w tym strumieniu jest mapowany na nową instancję Task, która nie jest już powtarzalna i zawiera dokładny czas wykonania. Ten przykład zawiera tylko jedno zadanie na liście wejściowej. Ale wyobraź sobie, że masz tysiąc. Będziesz mieć wtedy strumień 1000 strumieni obiektów Task. I coflatMap
robi to umieszczenie wszystkich zadań ze wszystkich strumieni w tym samym strumieniu wyjściowym. To wszystko, tak jak ja to rozumiem. Dziękuję za Twoje pytanie!źródło
Am I the only one who finds unwinding lists boring?
+1Ta metoda przyjmuje jedną funkcję jako argument, ta funkcja przyjmuje jeden parametr T jako argument wejściowy i zwraca jeden strumień parametru R jako wartość zwracaną. Gdy ta funkcja jest stosowana do każdego elementu tego strumienia, tworzy strumień nowych wartości. Wszystkie elementy tych nowych strumieni generowanych przez każdy element są następnie kopiowane do nowego strumienia, który będzie zwracaną wartością tej metody.
http://codedestine.com/java-8-stream-flatmap-method/
źródło
Bardzo prosty przykład: podziel listę imion i nazwisk, aby uzyskać listę nazwisk, niezależnie od imienia i nazwiska
List<String> fullNames = Arrays.asList("Barry Allen", "Bruce Wayne", "Clark Kent"); fullNames.stream() .flatMap(fullName -> Pattern.compile(" ").splitAsStream(fullName)) .forEach(System.out::println);
To drukuje:
źródło
Biorąc to pod uwagę:
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 + '}'; } }
i to:
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() { } }
Następnie możemy to zrobić:
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 );
źródło