Ví dụ về phương pháp FlatMap Java 8 Streams


85

Tôi đã được kiểm tra sắp tới Java update, cụ thể là: Java 8 or JDK 8. Vâng, tôi đang thiếu kiên nhẫn, có rất nhiều thứ mới, nhưng, có điều gì đó tôi không hiểu, một số mã đơn giản:

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

javadocs là

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

Trả về một luồng bao gồm kết quả của việc thay thế từng phần tử của luồng này bằng nội dung của luồng được ánh xạ được tạo ra bằng cách áp dụng chức năng ánh xạ đã cung cấp cho từng phần tử. Mỗi luồng được ánh xạ sẽ bị đóng sau khi nội dung của nó đã được đặt vào luồng này. (Nếu một luồng được ánh xạ là rỗng, thì một luồng trống sẽ được sử dụng.) Đây là một thao tác trung gian.

Tôi sẽ đánh giá cao nếu ai đó tạo ra một số ví dụ thực tế đơn giản về flatMapcách bạn có thể viết mã nó trong các phiên bản java trước đó Java[6,7]và cách bạn có thể viết mã các quy trình tương tự bằng cách sử dụng Java 8.


2
Có khoảng một triệu ví dụ về việc sử dụng flatMap (ít nhất là cho Scala, và về cơ bản chúng giống nhau :))) trên internet, bạn đã thử tìm kiếm chưa? Đây là một bắt đầu với: brunton-spall.co.uk/post/2011/12/02/…
Peter Svensson

3
tôi không hiểu Scala tôi chưa bao giờ đã làm việc với scala
chiperortiz

Ý tôi là flatMap là một khái niệm chung hiện đang tồn tại trong Java cũng như Scala.
Peter Svensson

ok tôi sẽ đọc thêm về nó cảm ơn người đàn ông.
chiperortiz

10
flatMap trong Java là cùng một ý tưởng nhưng trông khá khác với các luồng. Đừng chỉ mọi người vào Scala!
orbfish

Câu trả lời:


158

Điều đó không có ý nghĩa gì đối với flatMapmột Luồng đã phẳng, như luồngStream<Integer> bạn đã trình bày trong câu hỏi của mình.

Tuy nhiên, nếu bạn có Stream<List<Integer>>thì nó sẽ có ý nghĩa và bạn có thể làm điều này:

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

Cái nào sẽ in:

1
2
3
4
5

Để làm điều này trước Java 8, bạn chỉ cần một vòng lặp:

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

Tạo ví dụ

Hãy tưởng tượng rằng bạn muốn tạo chuỗi sau: 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, v.v. (nói cách khác: 1x1, 2x2, 3x3, v.v.)

Với flatMapnó có thể trông giống như:

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

Ở đâu:

  • IntStream.rangeClosed(1, 4)tạo luồng inttừ 1 đến 4, bao gồm
  • IntStream.iterate(i, identity()).limit(i)tạo một luồng có độ dài i của inti - vì vậy áp dụng cho i = 4nó sẽ tạo một luồng:4, 4, 4, 4
  • flatMap "làm phẳng" luồng và "nối" luồng đó với luồng ban đầu

Với Java <8, bạn sẽ cần hai vòng lặp lồng nhau:

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

Ví dụ thế giới thực

Giả sử tôi có một List<TimeSeries>trong đó mỗi TimeSeriesvề cơ bản là a Map<LocalDate, Double>. Tôi muốn nhận danh sách tất cả các ngày mà ít nhất một trong các chuỗi thời gian có giá trị. flatMapđể giải cứu:

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

Nó không chỉ có thể đọc được, mà nếu bạn đột nhiên cần xử lý 100k phần tử, chỉ cần thêm vào parallel()sẽ cải thiện hiệu suất mà bạn không cần viết bất kỳ mã đồng thời nào.


14
Cả hai ví dụ đều tốt hơn nhiều so với câu trả lời được chấp nhận.
Sebastian Graf

biên dịch phàn nàn về bản sắc ()không xác định
Nirmal

2
@ user3320018 bạn cần nhập tĩnh Function.identity.
assylias

@assylias Tôi đã thử nhập java.util. Chức năng.Function nhưng không hoạt động, tôi mới sử dụng java 8 và điều này có thể có hoặc có thể không phải là java 8 cụ thể nhưng bạn có thể cho tôi biết chính xác cách xóa lỗi đó không.
Nirmal

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

18

Trích xuất các từ duy nhất được sắp xếp theo ASC từ danh sách các cụm từ:

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

... và đầu ra:

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

11

Tôi có phải là người duy nhất thấy các danh sách đang xem nhàm chán không? ;-)

Hãy thử với các đối tượng. Nhân tiện, ví dụ thế giới thực.

Given: Đối tượng đại diện cho nhiệm vụ lặp đi lặp lại. Về các trường nhiệm vụ quan trọng: lời nhắc sẽ bắt đầu đổ chuông startvà lặp lại sau mỗi repeatPeriod repeatUnit(ví dụ: 5 GIỜ) và sẽ có repeatCounttổng số lời nhắc (bao gồm cả lời nhắc bắt đầu).

Mục tiêu: đạt được danh sách các bản sao nhiệm vụ, một bản cho mỗi lần gọi nhắc nhở nhiệm vụ.

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

Đầu ra:

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}

Tái bút: Tôi sẽ đánh giá cao nếu ai đó đề xuất một giải pháp đơn giản hơn, tôi không phải dân chuyên nghiệp.

CẬP NHẬT: @RBz đã yêu cầu giải thích chi tiết nên đây là nó. Về cơ bản, flatMap đặt tất cả các phần tử từ các luồng bên trong một luồng khác vào luồng đầu ra. Rất nhiều luồng ở đây :). Vì vậy, đối với mỗi Tác vụ trong biểu thức lambda dòng ban đầu x -> LongStream.iterate...tạo ra một dòng các giá trị dài đại diện cho thời điểm bắt đầu tác vụ. Luồng này được giới hạn cho các x.getRepeatCount()phiên bản. Giá trị của nó bắt đầu từ x.getStart().toEpochSecond(ZoneOffset.UTC)và mỗi giá trị tiếp theo được tính bằng lambda p -> (p + x.getRepeatPeriod()*x.getRepeatUnit().getDuration().getSeconds(). boxed()trả về luồng với mỗi giá trị dài dưới dạng một trường hợp Trình bao bọc dài. Sau đó, mỗi Long trong luồng đó được ánh xạ tới cá thể Tác vụ mới không lặp lại nữa và chứa thời gian thực thi chính xác. Mẫu này chỉ chứa một Nhiệm vụ trong danh sách đầu vào. Nhưng hãy tưởng tượng rằng bạn có một nghìn. Sau đó, bạn sẽ có một luồng gồm 1000 luồng đối tượng Tác vụ. Vậy thì saoflatMapở đây là đặt tất cả Nhiệm vụ từ tất cả các luồng vào cùng một luồng đầu ra. Đó là tất cả những gì tôi hiểu. Cảm ơn câu hỏi của bạn!


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

3
Tôi thấy nó thực sự khó hiểu ví dụ này. :(
RBz

@RBz Các hoạt động của luồng đôi khi không dễ hiểu, đặc biệt nếu có nhiều hoạt động liên quan. Đó là một vấn đề thực hành mặc dù. Tốt nhất bạn có thể làm là google mọi từ không rõ ràng từ mẫu và cố gắng tự sử dụng nó. Trên thực tế, mẫu kiểu mệnh lệnh thông thường sẽ dễ hiểu hơn nhiều (và đôi khi nhanh hơn). Vì vậy, chỉ cần suy nghĩ nếu bạn thực sự cần sử dụng luồng.
Aleksandr Kravets

Cảm ơn người đàn ông trả lời. Tuy nhiên tôi khá ổn với các khái niệm luồng. Những gì tôi đang gặp khó khăn ở đây là ví dụ cụ thể. Tôi không giỏi lắm với Time api, nhưng ngay cả việc đọc qua nó cũng không giúp tôi hiểu được điều gì đang xảy ra ở đây. Có thể tôi đang ngây thơ, nhưng sẽ rất tuyệt nếu có thêm một chút giải thích cho câu trả lời của bạn. Nó thực sự sẽ giúp tôi hiểu được ví dụ của bạn. Tôi biết, tôi chỉ bị nhốt vào nó vì tò mò! :)
RBz

Ví dụ tuyệt vời ... hơi khó hiểu lúc đầu, nhưng một khi tôi chạy nó trong IDE của mình ... thay thế rất mạnh mẽ !! cảm ơn rất nhiều !
Cristiano

2

Phương thức này nhận một Hàm làm đối số, hàm này nhận một tham số T làm đối số đầu vào và trả về một luồng tham số R làm giá trị trả về. Khi hàm này được áp dụng trên mỗi phần tử của luồng này, nó sẽ tạo ra một luồng giá trị mới. Tất cả các phần tử của các luồng mới này được tạo bởi mỗi phần tử sau đó được sao chép sang một luồng mới, sẽ là giá trị trả về của phương thức này.

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


2

Một ví dụ rất đơn giản: Tách danh sách tên đầy đủ để có danh sách tên, bất kể họ hay tên

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

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

Điều này in ra:

Barry
Allen
Bruce
Wayne
Clark
Kent

1

Đưa ra điều này:

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

    }

và điều này:

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

}

Sau đó, chúng tôi có thể làm điều này:

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 );
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.