Java 8 Streams FlatMap方法示例

Java 8 Streams FlatMap method example

我一直在检查即将到来的Java update,即:Java 8 or JDK 8。 是的,我不耐烦,有很多新东西,但是,有些我不理解的东西,一些简单的代码:

1
2
final Stream<Integer>stream = Stream.of(1,2,3,4,5,6,7,8,9,10);
stream.flatMap();

javadocs是

public Stream flatMap(Function> 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.

如果有人创建了一些关于flatMap的简单真实的示例,如何在以前的Java版本Java[6,7]中进行编码,以及如何使用Java 8对相同的例程进行编码,我将不胜感激。


flatMap已经很平坦的流(如您在问题中显示的Stream)没有任何意义。

但是,如果您有一个Stream>,这将很有意义,您可以这样做:

1
2
3
4
5
6
7
8
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);

哪个会打印:

1
2
3
4
5
1
2
3
4
5

要在Java 8之前的版本中执行此操作,您只需要一个循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}

编造的例子

假设您要创建以下序列:1、2、2、3、3、3、4、4、4、4等(换句话说:1x1、2x2、3x3等)

使用flatMap可能看起来像:

1
2
3
IntStream sequence = IntStream.rangeClosed(1, 4)
                          .flatMap(i -> IntStream.iterate(i, identity()).limit(i));
sequence.forEach(System.out::println);

哪里:

  • IntStream.rangeClosed(1, 4)创建1到4之间的int
  • IntStream.iterate(i, identity()).limit(i)创建一个长度为int i的流-应用于i = 4它将创建一个流:4, 4, 4, 4
  • flatMap"拉平"流并将其"连接"到原始流

使用Java <8,您将需要两个嵌套循环:

1
2
3
4
5
6
List<Integer> list = new ArrayList<>();
for (int i = 1; i <= 4; i++) {
    for (int j = 0; j < i; j++) {
        list.add(i);
    }
}

现实世界的例子

假设我有一个List,其中每个TimeSeries本质上都是一个Map。我想获取所有时间序列中至少一个具有值的所有日期的列表。 flatMap进行救援:

1
2
3
4
5
list.stream().parallel()
    .flatMap(ts -> ts.dates().stream()) // for each TS, stream dates and flatmap
    .distinct()                         // remove duplicates
    .sorted()                           // sort ascending
    .collect(toList());

它不仅可读性强,而且如果您突然需要处理100k元素,只需添加parallel()即可提高性能,而无需编写任何并发代码。


从短语列表中提取按ASC排序的唯一单词:

1
2
3
4
5
6
7
8
9
10
11
12
13
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);

...以及输出:

1
Unique words: [confounded, incumbent, jailer, perjury, skimming, sporadic]

我是唯一发现解散列表很无聊的人吗? ;-)

让我们尝试对象。现实世界中的例子。

给定:表示重复任务的对象。关于重要的任务字段:提醒从start开始响起,并每隔repeatPeriod repeatUnit重复一次(例如5小时),总共将有repeatCount提醒(包括开始的提醒)。

目标:实现任务副本列表,每个任务提醒调用一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);

输出:

1
2
3
4
5
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}

附注:如果有人提出了更简单的解决方案,我将不胜感激。

更新:
@RBz要求提供详细的解释,所以就在这里。
基本上,flatMap将来自另一个流中的流的所有元素放入输出流。这里有很多流:)。因此,对于初始流中的每个任务,lambda表达式x -> LongStream.iterate...都会创建一个代表任务开始时刻的长值流。此流仅限于x.getRepeatCount()实例。它的值从x.getStart().toEpochSecond(ZoneOffset.UTC)开始,并且每个下一个值都是使用lambda p -> (p + x.getRepeatPeriod()*x.getRepeatUnit().getDuration().getSeconds()计算的。 boxed()返回带有每个long值的流作为Long包装实例。然后,该流中的每个Long都映射到新的Task实例,该实例不再重复并且包含确切的执行时间。此示例在输入列表中仅包含一个任务。但是想象一下,你有一千。然后,您将具有1000个Task对象流。 flatMap在这里所做的就是将所有流中的所有任务都放到同一输出流中。就我所知就这些。谢谢你的问题!


一个非常简单的示例:拆分全名列表以获取名称列表,而不考虑名字或姓氏

1
2
3
4
5
 List<String> fullNames = Arrays.asList("Barry Allen","Bruce Wayne","Clark Kent");

 fullNames.stream()
            .flatMap(fullName -> Pattern.compile("").splitAsStream(fullName))
            .forEach(System.out::println);

打印输出:

1
2
3
4
5
6
Barry
Allen
Bruce
Wayne
Clark
Kent

此方法以一个Function作为参数,此函数接受一个参数T作为输入参数,并返回一个参数R流作为返回值。当将此函数应用于此流的每个元素时,它将产生一个新值流。然后,将每个元素生成的这些新流的所有元素复制到新流,该新流将是此方法的返回值。

Java 8 Stream flatMap method Example


鉴于这种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
  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 + '}';
        }

    }

和这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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()
    {
    }

}

然后我们可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
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 );