Java 8 รายการ <V> ลงในแผนที่ <K, V>


932

ฉันต้องการแปลรายการวัตถุเป็นแผนที่โดยใช้สตรีมและแลมบ์ดาของ Java 8

นี่คือวิธีที่ฉันจะเขียนใน Java 7 และด้านล่าง

private Map<String, Choice> nameMap(List<Choice> choices) {
        final Map<String, Choice> hashMap = new HashMap<>();
        for (final Choice choice : choices) {
            hashMap.put(choice.getName(), choice);
        }
        return hashMap;
}

ฉันสามารถทำได้โดยใช้ Java 8 และ Guava แต่ฉันอยากรู้วิธีการทำสิ่งนี้โดยไม่ใช้ Guava

ในฝรั่ง

private Map<String, Choice> nameMap(List<Choice> choices) {
    return Maps.uniqueIndex(choices, new Function<Choice, String>() {

        @Override
        public String apply(final Choice input) {
            return input.getName();
        }
    });
}

และฝรั่งกับ Java 8 lambdas

private Map<String, Choice> nameMap(List<Choice> choices) {
    return Maps.uniqueIndex(choices, Choice::getName);
}

คำตอบ:


1394

ตามCollectorsเอกสารมันง่ายเหมือน:

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName,
                                              Function.identity()));

167
ในฐานะที่เป็นหมายเหตุด้านแม้หลังจาก Java 8, JDK ยังคงไม่สามารถแข่งขันในการแข่งขันที่สั้น ทางเลือกของ Guava ดูเหมือนจะอ่านได้มาก: Maps.uniqueIndex(choices, Choice::getName).
Bogdan Calmac

3
การใช้ (นำเข้าแบบสแตติก) SeqจากไลบรารีJOOL (ซึ่งฉันอยากแนะนำให้ใครก็ตามที่ใช้ Java 8) คุณสามารถปรับปรุงความกะทัดรัดด้วย: seq(choices).toMap(Choice::getName)
lukens

5
มีประโยชน์ใด ๆ จากการใช้ Function.identity หรือไม่ ฉันหมายความว่ามัน -> มันสั้นกว่า
shabunc

9
@ shabunc ฉันไม่ทราบถึงประโยชน์ใด ๆ และใช้it -> itตัวเองจริงๆ Function.identity()ส่วนใหญ่จะใช้ที่นี่เพราะมันถูกใช้ในเอกสารอ้างอิงและนั่นคือทั้งหมดที่ฉันรู้เกี่ยวกับ lambdas ในขณะที่เขียน
zapl

12
@zapl, โอ้จริง ๆ แล้วมันมีเหตุผลอยู่เบื้องหลัง - stackoverflow.com/questions/28032827/…
shabunc

307

หากคีย์ของคุณไม่รับประกันว่าจะไม่ซ้ำกันสำหรับองค์ประกอบทั้งหมดในรายการคุณควรแปลงเป็น a Map<String, List<Choice>>แทนMap<String, Choice>

Map<String, List<Choice>> result =
 choices.stream().collect(Collectors.groupingBy(Choice::getName));

76
สิ่งนี้จะช่วยให้คุณแผนที่ <String, List <Choice>> ซึ่งเกี่ยวข้องกับความเป็นไปได้ของคีย์ที่ไม่ซ้ำกัน แต่ไม่ใช่สิ่งที่ OP ร้องขอ ใน Guava Multimaps.index (ตัวเลือก Choice :: getName) อาจเป็นตัวเลือกที่ดีกว่าถ้านี่คือสิ่งที่คุณต้องการ แต่อย่างใด
Richard Nichols

หรือใช้ Multimap <String, Choice> ของ Guava ซึ่งค่อนข้างมีประโยชน์ในสถานการณ์ที่คีย์แผนที่เดียวกันกับค่าหลายค่า มีวิธีการอรรถประโยชน์หลายอย่างที่พร้อมใช้งานใน Guava เพื่อใช้โครงสร้างข้อมูลดังกล่าวแทนที่จะสร้าง Map <String, List <Choice>>
Neeraj B.

1
@RichardNichols ทำไมฝรั่งMultimapsเป็นตัวเลือกที่ดีกว่า มันอาจเป็นความไม่สะดวกเพราะมันไม่ได้ส่งคืนMapวัตถุ
พวกเขา

156

ใช้getName()เป็นกุญแจสำคัญและChoiceเป็นค่าของแผนที่:

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName, c -> c));

19
โปรดเขียนคำอธิบายเพื่อให้ผู้ใช้สามารถเข้าใจได้
Mayank Jain

4
มันแย่มากจริง ๆ ไม่มีรายละเอียดเพิ่มเติมที่นี่เพราะฉันชอบคำตอบนี้ดีที่สุด
MadConan

Collectors.toMap(Choice::getName,c->c)(สั้น 2 chars)
Ferrybig

7
มันเท่ากับ choices.stream().collect(Collectors.toMap(choice -> choice.getName(),choice -> choice)); ฟังก์ชันแรกสำหรับคีย์, ฟังก์ชันที่สองสำหรับค่า
waterscar

16
ฉันรู้ว่าการดูและทำความเข้าใจง่ายแค่ไหนc -> cแต่Function.identity()มีข้อมูลเชิงความหมายมากขึ้น ฉันมักจะใช้การนำเข้าแบบคงที่เพื่อให้ฉันสามารถใช้identity()
แฮงค์ D

28

นี่คืออีกกรณีหนึ่งในกรณีที่คุณไม่ต้องการใช้ Collector.toMap ()

Map<String, Choice> result =
   choices.stream().collect(HashMap<String, Choice>::new, 
                           (m, c) -> m.put(c.getName(), c),
                           (m, u) -> {});

1
จะดีกว่าที่จะใช้งานแล้วCollectors.toMap()หรือ HashMap ของเราเองตามที่แสดงในตัวอย่างด้านบน
Swapnil Gangrade

1
ตัวอย่างนี้ให้ตัวอย่างสำหรับวิธีการวางอย่างอื่นในแผนที่ ฉันต้องการค่าที่ไม่ได้รับจากการเรียกใช้เมธอด ขอบคุณ!
th3morg

1
ฟังก์ชันอาร์กิวเมนต์ที่สามไม่ถูกต้อง คุณควรเตรียมฟังก์ชั่นบางอย่างเพื่อรวมสอง Hashmaps เช่น Hashmap :: putAll
jesantana

25

ส่วนใหญ่ของคำตอบที่ระบุไว้พลาดกรณีเมื่อรายการมีรายการที่ซ้ำกัน IllegalStateExceptionในกรณีที่มีคำตอบที่จะโยน อ้างอิงรหัสด้านล่างเพื่อจัดการรายการที่ซ้ำกันเช่นกัน:

public Map<String, Choice> convertListToMap(List<Choice> choices) {
    return choices.stream()
        .collect(Collectors.toMap(Choice::getName, choice -> choice,
            (oldValue, newValue) -> newValue));
  }

19

อีกหนึ่งตัวเลือกในวิธีที่ง่าย

Map<String,Choice> map = new HashMap<>();
choices.forEach(e->map.put(e.getName(),e));

3
ไม่มีความแตกต่างในการใช้ชนิดนี้หรือ Java 7
Ashish Lohia

3
ฉันพบสิ่งนี้ง่ายต่อการอ่านและเข้าใจสิ่งที่เกิดขึ้นมากกว่ากลไกอื่น ๆ
Freiheit

2
ดังนั้นถามด้วย Java 8 Streams
Mehraj Malik

18

ตัวอย่างเช่นหากคุณต้องการแปลงฟิลด์วัตถุเป็นแผนที่:

วัตถุตัวอย่าง:

class Item{
        private String code;
        private String name;

        public Item(String code, String name) {
            this.code = code;
            this.name = name;
        }

        //getters and setters
    }

และการดำเนินการแปลง List To Map:

List<Item> list = new ArrayList<>();
list.add(new Item("code1", "name1"));
list.add(new Item("code2", "name2"));

Map<String,String> map = list.stream()
     .collect(Collectors.toMap(Item::getCode, Item::getName));

12

หากคุณไม่ทราบใช้ห้องสมุดของบุคคลที่ 3 ของ AOL ไซคลอปส์ทำปฏิกิริยา lib (เปิดเผยผมมีส่วนร่วม) มีส่วนขยายสำหรับทุกJDK เก็บประเภทรวมทั้งรายการและแผนที่

ListX<Choices> choices;
Map<String, Choice> map = choices.toMap(c-> c.getName(),c->c);

10

ฉันพยายามทำสิ่งนี้และพบว่าการใช้คำตอบข้างต้นเมื่อใช้Functions.identity()สำหรับกุญแจไปยังแผนที่จากนั้นฉันมีปัญหาเกี่ยวกับการใช้วิธีการในท้องถิ่นที่ต้องการthis::localMethodNameใช้งานได้จริงเนื่องจากปัญหาการพิมพ์

Functions.identity()จริง ๆ แล้วทำอะไรบางอย่างกับการพิมพ์ในกรณีนี้ดังนั้นวิธีการจะทำงานโดยกลับมาObjectและยอมรับพารามิเตอร์ของObject

เพื่อแก้ปัญหานี้ฉันลงเอยด้วยการทิ้งFunctions.identity()และใช้s->sแทน

ดังนั้นรหัสของฉันในรายการของฉันไดเรกทอรีทั้งหมดภายในไดเรกทอรีและสำหรับแต่ละคนใช้ชื่อของไดเรกทอรีเป็นกุญแจสำคัญในแผนที่แล้วเรียกวิธีการที่มีชื่อไดเรกทอรีและส่งกลับคอลเลกชันของรายการดูเหมือนว่า:

Map<String, Collection<ItemType>> items = Arrays.stream(itemFilesDir.listFiles(File::isDirectory))
.map(File::getName)
.collect(Collectors.toMap(s->s, this::retrieveBrandItems));

10

คุณสามารถสร้างสตรีมของดัชนีโดยใช้ IntStream แล้วแปลงเป็นแผนที่:

Map<Integer,Item> map = 
IntStream.range(0,items.size())
         .boxed()
         .collect(Collectors.toMap (i -> i, i -> items.get(i)));

1
นี่ไม่ใช่ตัวเลือกที่ดีเพราะคุณเรียกใช้ get () สำหรับทุกองค์ประกอบดังนั้นการเพิ่มความซับซ้อนของการดำเนินการ (o (n * k) หากรายการเป็น hashmap)
Nicolas Nobelis

ไม่ได้รับ (i) ใน hashmap O (1)?
Ivo van der Veeken

@IvovanderVeeken การรับ (i) ในข้อมูลโค้ดอยู่ในรายการไม่ใช่แผนที่
ซากิ

@ ซากิฉันกำลังพูดถึงคำกล่าวของนิโคลัส ฉันไม่เห็นความซับซ้อน n * k หากรายการนั้นเป็น hashmap แทนที่จะเป็นรายการ
Ivo van der Veeken

8

ฉันจะเขียนวิธีการแปลงรายการเพื่อทำแผนที่โดยใช้ยาชื่อสามัญและผกผันของการควบคุม เพียงแค่วิธีการสากล!

บางทีเราอาจมีรายชื่อจำนวนเต็มหรือรายชื่อวัตถุ ดังนั้นคำถามต่อไปนี้: สิ่งที่ควรเป็นกุญแจสำคัญของแผนที่?

สร้างส่วนต่อประสาน

public interface KeyFinder<K, E> {
    K getKey(E e);
}

ตอนนี้ใช้การควบคุมแบบผกผัน:

  static <K, E> Map<K, E> listToMap(List<E> list, KeyFinder<K, E> finder) {
        return  list.stream().collect(Collectors.toMap(e -> finder.getKey(e) , e -> e));
    }

ตัวอย่างเช่นถ้าเรามีวัตถุของหนังสือคลาสนี้คือการเลือกคีย์สำหรับแผนที่

public class BookKeyFinder implements KeyFinder<Long, Book> {
    @Override
    public Long getKey(Book e) {
        return e.getPrice()
    }
}

6

ฉันใช้ไวยากรณ์นี้

Map<Integer, List<Choice>> choiceMap = 
choices.stream().collect(Collectors.groupingBy(choice -> choice.getName()));

11
groupingByสร้างไม่ได้Map<K,List<V>> Map<K,V>
Christoffer Hammarström

3
ซ้ำของคำตอบ ulises และString getName();(ไม่ใช่จำนวนเต็ม)
Barett

5
Map<String, Set<String>> collect = Arrays.asList(Locale.getAvailableLocales()).stream().collect(Collectors
                .toMap(l -> l.getDisplayCountry(), l -> Collections.singleton(l.getDisplayLanguage())));

4

สามารถทำได้ 2 วิธี ให้คนเป็นคลาสที่เราจะใช้เพื่อแสดงให้เห็น

public class Person {

    private String name;
    private int age;

    public String getAge() {
        return age;
    }
}

ให้บุคคลเป็นรายชื่อบุคคลที่จะถูกแปลงเป็นแผนที่

1. การใช้งาน foreach แบบง่ายและการแสดงออกของแลมบ์ดาในรายการ

Map<Integer,List<Person>> mapPersons = new HashMap<>();
persons.forEach(p->mapPersons.put(p.getAge(),p));

2. การใช้ Collector บนสตรีมที่กำหนดในรายการที่กำหนด

 Map<Integer,List<Person>> mapPersons = 
           persons.stream().collect(Collectors.groupingBy(Person::getAge));

4

เป็นไปได้ที่จะใช้สตรีมเพื่อทำสิ่งนี้ ในการลบความต้องการที่จะใช้อย่างชัดเจนCollectorsเป็นไปได้ที่จะนำเข้าtoMapแบบคงที่ (ตามที่แนะนำโดย Effective Java, edition ที่สาม)

import static java.util.stream.Collectors.toMap;

private static Map<String, Choice> nameMap(List<Choice> choices) {
    return choices.stream().collect(toMap(Choice::getName, it -> it));
}


3
Map<String,Choice> map=list.stream().collect(Collectors.toMap(Choice::getName, s->s));

ทำหน้าที่นี้ให้ฉันด้วย

Map<String,Choice> map=  list1.stream().collect(()-> new HashMap<String,Choice>(), 
            (r,s) -> r.put(s.getString(),s),(r,s) -> r.putAll(s));

3

หากค่าใหม่สำหรับชื่อคีย์เดียวกันจะต้องถูกแทนที่:

public Map < String, Choice > convertListToMap(List < Choice > choices) {
    return choices.stream()
        .collect(Collectors.toMap(Choice::getName,
            Function.identity(),
            (oldValue, newValue) - > newValue));
}

หากตัวเลือกทั้งหมดต้องถูกจัดกลุ่มในรายการชื่อ:

public Map < String, Choice > convertListToMap(List < Choice > choices) {
    return choices.stream().collect(Collectors.groupingBy(Choice::getName));
}

1
List<V> choices; // your list
Map<K,V> result = choices.stream().collect(Collectors.toMap(choice::getKey(),choice));
//assuming class "V" has a method to get the key, this method must handle case of duplicates too and provide a unique key.


-1
String array[] = {"ASDFASDFASDF","AA", "BBB", "CCCC", "DD", "EEDDDAD"};
    List<String> list = Arrays.asList(array);
    Map<Integer, String> map = list.stream()
            .collect(Collectors.toMap(s -> s.length(), s -> s, (x, y) -> {
                System.out.println("Dublicate key" + x);
                return x;
            },()-> new TreeMap<>((s1,s2)->s2.compareTo(s1))));
    System.out.println(map);

เผยแพร่คีย์ AA = 12 = ASDFASDFASDF, 7 = EEDDDAD, 4 = CCCC, 3 = BBB, 2 = AA}


2
คุณพยายามทำอะไรที่นี่ ?? คุณอ่านคำถามหรือไม่
navderm
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.