问题
I have been checking the upcoming Java update
, namely: Java 8 or JDK 8
. Yes, I am impatient, there\'s a lot of new stuff, but, there is something I don\'t understand, some simple code:
final Stream<Integer>stream = Stream.of(1,2,3,4,5,6,7,8,9,10);
stream.flatMap();
the javadocs are
public <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
Returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element. Each mapped stream is closed after its contents have been placed into this stream. (If a mapped stream is null an empty stream is used, instead.) This is an intermediate operation.
I would appreciate if somebody created some simple real-life examples about flatMap
, how you could code it in previous java versions Java[6,7]
and how you can code the same routines using Java 8
.
回答1:
It doesn't make sense to flatMap
a Stream that's already flat, like the Stream<Integer>
you've shown in your question.
However, if you had a Stream<List<Integer>>
then it would make sense and you could do this:
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);
Which would print:
1
2
3
4
5
To do this pre-Java 8 you just need a 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);
}
回答2:
Made up example
Imagine that you want to create the following sequence: 1, 2, 2, 3, 3, 3, 4, 4, 4, 4 etc. (in other words: 1x1, 2x2, 3x3 etc.)
With flatMap
it could look like:
IntStream sequence = IntStream.rangeClosed(1, 4)
.flatMap(i -> IntStream.iterate(i, identity()).limit(i));
sequence.forEach(System.out::println);
where:
IntStream.rangeClosed(1, 4)
creates a stream ofint
from 1 to 4, inclusiveIntStream.iterate(i, identity()).limit(i)
creates a stream of length i ofint
i - so applied toi = 4
it creates a stream:4, 4, 4, 4
flatMap
"flattens" the stream and "concatenates" it to the original stream
With Java < 8 you would need two nested loops:
List<Integer> list = new ArrayList<>();
for (int i = 1; i <= 4; i++) {
for (int j = 0; j < i; j++) {
list.add(i);
}
}
Real world example
Let's say I have a List<TimeSeries>
where each TimeSeries
is essentially a Map<LocalDate, Double>
. I want to get a list of all dates for which at least one of the time series has a value. flatMap
to the rescue:
list.stream().parallel()
.flatMap(ts -> ts.dates().stream()) // for each TS, stream dates and flatmap
.distinct() // remove duplicates
.sorted() // sort ascending
.collect(toList());
Not only is it readable, but if you suddenly need to process 100k elements, simply adding parallel()
will improve performance without you writing any concurrent code.
回答3:
Extract unique words sorted ASC from a list of phrases:
List<String> phrases = Arrays.asList(
"sporadic perjury",
"confounded skimming",
"incumbent jailer",
"confounded jailer");
List<String> uniqueWords = phrases
.stream()
.flatMap(phrase -> Stream.of(phrase.split(" +")))
.distinct()
.sorted()
.collect(Collectors.toList());
System.out.println("Unique words: " + uniqueWords);
... and the output:
Unique words: [confounded, incumbent, jailer, perjury, skimming, sporadic]
回答4:
Am I the only one who finds unwinding lists boring? ;-)
Let's try with objects. Real world example by the way.
Given: Object representing repetitive task. About important task fields: reminders are starting to ring at start
and repeat every repeatPeriod
repeatUnit
(e.g. 5 HOURS) and there will be repeatCount
reminders in total(including starting one).
Goal: achieve a list of task copies, one for each task reminder invocation.
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);
Output:
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}
P.S.: I would appreciate if someone suggested a simpler solution, I'm not a pro after all.
UPDATE:
@RBz asked for detailed explanation so here it is.
Basically flatMap puts all elements from streams inside another stream into output stream. A lot of streams here :). So, for each Task in initial stream lambda expression x -> LongStream.iterate...
creates a stream of long values that represent task start moments. This stream is limited to x.getRepeatCount()
instances. It's values start from x.getStart().toEpochSecond(ZoneOffset.UTC)
and each next value is calculated using lambda p -> (p + x.getRepeatPeriod()*x.getRepeatUnit().getDuration().getSeconds()
. boxed()
returns the stream with each long value as a Long wrapper instance. Then each Long in that stream is mapped to new Task instance that is not repetitive anymore and contains exact execution time. This sample contains only one Task in input list. But imagine that you have a thousand. You will have then a stream of 1000 streams of Task objects. And what flatMap
does here is putting all Tasks from all streams onto the same output stream. That's all as I understand it. Thank you for your question!
回答5:
A very simple example: Split a list of full names to get a list of names, regardless of first or last
List<String> fullNames = Arrays.asList("Barry Allen", "Bruce Wayne", "Clark Kent");
fullNames.stream()
.flatMap(fullName -> Pattern.compile(" ").splitAsStream(fullName))
.forEach(System.out::println);
This prints out:
Barry
Allen
Bruce
Wayne
Clark
Kent
回答6:
Given this:
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 + '}';
}
}
and this:
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()
{
}
}
We can then do this:
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 );
回答7:
This method takes one Function as an argument, this function accepts one parameter T as an input argument and return one stream of parameter R as a return value. When this function is applied on each element of this stream, it produces a stream of new values. All the elements of these new streams generated by each element are then copied to a new stream, which will be a return value of this method.
http://codedestine.com/java-8-stream-flatmap-method/
来源:https://stackoverflow.com/questions/22382453/java-8-streams-flatmap-method-example