Java 8 Streams FlatMap方法示例


85

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

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

javadocs是

public <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)

返回一个流,该流包括将流中的每个元素替换为通过将提供的映射函数应用于每个元素而生成的映射流的内容而得到的结果。将每个映射流的内容放入此流后,将其关闭。(如果映射的流为null,则使用空流。)这是一个中间操作。

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


2
互联网上有大约一百万个使用flatMap的示例(至少对于Scala,它们基本上是相同的:)),您是否尝试过搜索?首先从这里开始:brunton-spall.co.uk/post/2011/12/02/…–
彼得·斯文森

3
我不了解Scala,我从未与Scala合作过
Chiperortiz 2014年

我的意思是,flatMap是一个通用概念,现在Java和Scala中都存在。
彼得·史文森

好吧,我会读到更多关于这个的人。
chiperortiz 2014年

10
Java中的flatMap是相同的想法,但是在流中看起来完全不同。不要将人们指向Scala!
orbfish

Answers:


158

对于已经很平坦flatMapStream(例如Stream<Integer>问题中已经显示的。

但是,如果您有a,Stream<List<Integer>>那么这将很有意义,您可以这样做:

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

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

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);
}

113

编造的例子

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

有了flatMap它可能看起来像:

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

哪里:

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

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

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

现实世界的例子

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

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()提高性能,而无需编写任何并发代码。


14
这两个例子都比公认的答案要好得多。
塞巴斯蒂安·格拉夫

编译器抱怨标识()未定义
尼尔默尔

2
@ user3320018您需要静态导入Function.identity
assylias 2015年

@assylias我尝试导入java.util.function.Function,但是没有用,我是Java 8的新手,这可能是Java 8特定的,也可能不是Java 8的,但是您能确切告诉我如何删除该错误。
Nirmal

4
import static java.util.function.Function.identity;
assylias 2015年

18

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

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);

...和输出:

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

11

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

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

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

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

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);

输出:

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:如果有人提出一个更简单的解决方案,我将不胜感激,毕竟我不是专业人士。

更新: @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这里要做的是将所有流中的所有任务都放到同一输出流中。就我所知。谢谢你的问题!


8
Am I the only one who finds unwinding lists boring?+1
whitfin

3
我觉得很难理解这个例子。:(
RBz

@RBz Stream操作有时不容易理解,尤其是涉及多个操作时。不过这是一个实践问题。最好的办法是将示例中每个不清楚的单词用Google搜索,然后尝试自己使用。实际上,通常的命令式样例将更容易理解(有时更快)。因此,请考虑是否确实需要使用流。
Aleksandr Kravets

感谢您的回复。但是我对流的概念很满意。我在这里遇到的问题是特定于示例的。我对Time api不太满意,但是即使阅读它也无法帮助我理解这里发生的事情。也许我太天真了,但是为您的答案多一点解释将是很棒的。这确实有助于我理解您的示例。我知道,我只是出于好奇而被锁定!:)
RBz

很棒的示例...一开始有点难以理解,但是一旦我在IDE中运行它,它就会如此强大!非常感谢 !
克里斯蒂亚诺

2

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

http://codedestine.com/java-8-stream-flatmap-method/


2

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

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

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

打印输出:

Barry
Allen
Bruce
Wayne
Clark
Kent

1

鉴于这种:

  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 + '}';
        }

    }

还有这个:

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()
    {
    }

}

然后我们可以这样做:

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 );
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.