ตัวอย่างเมธอด Java 8 Streams FlatMap


86

ฉันได้ตรวจสอบสิ่งที่กำลังจะมาถึง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)

ส่งคืนสตรีมที่ประกอบด้วยผลลัพธ์ของการแทนที่แต่ละองค์ประกอบของสตรีมนี้ด้วยเนื้อหาของสตรีมที่แมปที่สร้างขึ้นโดยใช้ฟังก์ชันการแมปที่ให้มากับแต่ละองค์ประกอบ สตรีมที่แมปแต่ละสตรีมจะถูกปิดหลังจากวางเนื้อหาลงในสตรีมนี้ (หากสตรีมที่แมปเป็นโมฆะจะใช้สตรีมว่างแทน) นี่เป็นการดำเนินการระดับกลาง

ฉันอยากจะขอบคุณถ้าใครสร้างตัวอย่างชีวิตจริงบางอย่างง่ายเกี่ยวกับflatMapวิธีการที่คุณสามารถรหัสมันในรุ่นก่อนหน้า Java และวิธีที่คุณสามารถรหัสกิจวัตรเดียวกันโดยใช้Java[6,7]Java 8


2
มีตัวอย่างการใช้ flatMap ประมาณล้านตัวอย่าง (สำหรับ Scala อย่างน้อยก็เหมือนกัน :)) บนอินเทอร์เน็ตคุณได้ลองค้นหาแล้วหรือยัง? นี่คือหนึ่งที่จะเริ่มต้นด้วย: brunton-spall.co.uk/post/2011/12/02/…
Peter Svensson

3
ฉันไม่เข้าใจ Scala ฉันไม่เคยทำงานกับสกาล่า
chiperortiz

สิ่งที่ฉันหมายถึงคือ flatMap เป็นแนวคิดทั่วไปซึ่งตอนนี้มีอยู่ใน Java เช่นเดียวกับ Scala
Peter Svensson

โอเคฉันจะอ่านเพิ่มเติมเกี่ยวกับเรื่องนี้ขอบคุณ
chiperortiz

10
flatMap ใน Java เป็นแนวคิดเดียวกัน แต่ดูแตกต่างกันมากกับสตรีม อย่าชี้คนไปที่สกาล่า!
orbfish

คำตอบ:


158

มันไม่ได้ทำให้ความรู้สึกที่จะสตรีมที่แบนอยู่แล้วเช่นที่คุณได้แสดงให้เห็นในคำถามของคุณ flatMapStream<Integer>

อย่างไรก็ตามหากคุณมี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

ในการทำ pre-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)สร้างกระแสความยาว 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คือ a 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
ทั้งสองตัวอย่างดีกว่าคำตอบที่ยอมรับมาก
Sebastian Graf

คอมไพเลอร์บ่นเกี่ยวกับidentity () as undefined
Nirmal

2
@ user3320018 Function.identityคุณจำเป็นต้องนำเข้าแบบคงที่
assylias

@assylias ฉันลองนำเข้า java.util.function.Functionแต่ใช้งานไม่ได้ฉันยังใหม่กับ java 8 และนี่อาจเป็นหรือไม่เฉพาะ java 8 แต่คุณช่วยบอกวิธีลบข้อผิดพลาดนั้นให้ฉันได้ไหม
Nirmal

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

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: ฉันจะขอบคุณถ้ามีคนแนะนำวิธีแก้ปัญหาที่ง่ายกว่านี้ฉันไม่ใช่มืออาชีพเลย

UPDATE: @RBz ขอคำอธิบายโดยละเอียดนี่คือ โดยทั่วไปแล้ว flatMap จะทำให้องค์ประกอบทั้งหมดจากสตรีมภายในสตรีมอื่นเป็นสตรีมเอาต์พุต สตรีมมากมายที่นี่ :). ดังนั้นสำหรับแต่ละงานในนิพจน์แลมบ์ดาสตรีมเริ่มต้นx -> LongStream.iterate...จะสร้างสตรีมของค่าแบบยาวที่แสดงถึงช่วงเวลาเริ่มต้นของงาน สตรีมนี้ จำกัด เฉพาะx.getRepeatCount()อินสแตนซ์ ค่ามันเริ่มต้นจากและความคุ้มค่าต่อไปแต่ละคนจะถูกคำนวณโดยใช้แลมบ์ดาx.getStart().toEpochSecond(ZoneOffset.UTC) ส่งคืนสตรีมที่มีค่า long แต่ละค่าเป็นอินสแตนซ์ Long wrapper จากนั้นแต่ละ Long ในสตรีมนั้นจะถูกจับคู่กับอินสแตนซ์งานใหม่ที่ไม่ซ้ำซากอีกต่อไปและมีเวลาดำเนินการที่แน่นอน ตัวอย่างนี้มีเพียงงานเดียวในรายการอินพุต แต่ลองนึกดูว่าคุณมีเงินเป็นพัน จากนั้นคุณจะมีสตรีมของวัตถุงาน 1,000 สตรีม และอะไรp -> (p + x.getRepeatPeriod()*x.getRepeatUnit().getDuration().getSeconds()boxed()flatMapนี่คือการวาง Tasks ทั้งหมดจากสตรีมทั้งหมดลงในสตรีมเอาต์พุตเดียวกัน นั่นคือทั้งหมดที่ฉันเข้าใจ ขอบคุณสำหรับคำถาม!


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

3
ฉันพบว่ามันยากมากที่จะเข้าใจตัวอย่างนี้ :(
RBz

การดำเนินการสตรีม @RBz บางครั้งก็ไม่ง่ายที่จะเข้าใจโดยเฉพาะอย่างยิ่งหากมีการดำเนินการมากกว่าหนึ่งรายการที่เกี่ยวข้อง มันเป็นเรื่องของการปฏิบัติแม้ว่า สิ่งที่ดีที่สุดที่คุณสามารถทำได้คือ google ทุกคำที่ไม่ชัดเจนจากตัวอย่างและลองใช้ด้วยตัวเอง ในความเป็นจริงตัวอย่างรูปแบบที่จำเป็นตามปกติจะเข้าใจได้ง่ายกว่ามาก (และบางครั้งก็เร็วกว่า) ลองคิดดูว่าถ้าคุณจำเป็นต้องใช้สตรีมจริงๆ
Aleksandr Kravets

ขอบคุณสำหรับคนตอบ อย่างไรก็ตามฉันค่อนข้างโอเคกับแนวคิดสตรีม สิ่งที่ฉันมีปัญหาคือตัวอย่างเฉพาะ ฉันไม่ค่อยดีกับ Time api แต่ถึงแม้จะอ่านผ่าน ๆ ก็ไม่ได้ช่วยให้ฉันเข้าใจสิ่งที่เกิดขึ้นที่นี่ ฉันอาจเป็นคนไร้เดียงสา แต่จะดีมากหากมีคำอธิบายเพิ่มเติมสำหรับคำตอบของคุณ มันจะช่วยฉันได้มากในการทำความเข้าใจตัวอย่างของคุณ ฉันรู้ว่าฉันถูกขังไว้ด้วยความอยากรู้อยากเห็น! :)
RBz

ตัวอย่างที่น่าทึ่ง ... ยากที่จะเข้าใจในตอนแรก แต่เมื่อฉันเรียกใช้ใน IDE ของฉัน ... ทางเลือกที่ทรงพลัง !! ขอบคุณมาก !
Cristiano

2

วิธีนี้ใช้หนึ่งฟังก์ชันเป็นอาร์กิวเมนต์ฟังก์ชันนี้ยอมรับหนึ่งพารามิเตอร์ 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 );
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.