2017-10-18 92 views
4

我有以下兩類:的Java 8流映射分組操作

Person

public class Person { 

    private final Long id; 
    private final String address; 
    private final String phone; 

    public Person(Long id, String address, String phone) { 
     this.id = id; 
     this.address = address; 
     this.phone = phone; 
    } 

    public Long getId() { 
     return id; 
    } 

    public String getAddress() { 
     return address; 
    } 

    public String getPhone() { 
     return phone; 
    } 

    @Override 
    public String toString() { 
     return "Person [id=" + id + ", address=" + address + ", phone=" + phone + "]"; 
    } 
} 

CollectivePerson

import java.util.HashSet; 
import java.util.Set; 

public class CollectivePerson { 

    private final Long id; 
    private final Set<String> addresses; 
    private final Set<String> phones; 

    public CollectivePerson(Long id) { 
     this.id = id; 
     this.addresses = new HashSet<>(); 
     this.phones = new HashSet<>(); 
    } 

    public Long getId() { 
     return id; 
    } 

    public Set<String> getAddresses() { 
     return addresses; 
    } 

    public Set<String> getPhones() { 
     return phones; 
    } 

    @Override 
    public String toString() { 
     return "CollectivePerson [id=" + id + ", addresses=" + addresses + ", phones=" + phones + "]"; 
    } 
} 

我想有流操作,以便:

  • The Person映射到CollectivePerson
  • addressPersonphoneCollectivePerson分別合併成addressesphones對於具有相同id

所有Person的I寫了下面的代碼段用於此目的:

import java.util.ArrayList; 
import java.util.HashMap; 
import java.util.List; 
import java.util.Map; 
import java.util.Objects; 
import java.util.stream.Collectors; 

public class Main { 

    public static void main(String[] args) { 
     Person person1 = new Person(1L, "Address 1", "Phone 1"); 
     Person person2 = new Person(2L, "Address 2", "Phone 2"); 
     Person person3 = new Person(3L, "Address 3", "Phone 3"); 
     Person person11 = new Person(1L, "Address 4", "Phone 4"); 
     Person person21 = new Person(2L, "Address 5", "Phone 5"); 
     Person person22 = new Person(2L, "Address 6", "Phone 6"); 

     List<Person> persons = new ArrayList<>(); 
     persons.add(person1); 
     persons.add(person11); 
     persons.add(person2); 
     persons.add(person21); 
     persons.add(person22); 
     persons.add(person3); 

     Map<Long, CollectivePerson> map = new HashMap<>(); 
     List<CollectivePerson> collectivePersons = persons.stream() 
       .map((Person person) -> { 
        CollectivePerson collectivePerson = map.get(person.getId()); 

        if (Objects.isNull(collectivePerson)) { 
         collectivePerson = new CollectivePerson(person.getId()); 
         map.put(person.getId(), collectivePerson); 

         collectivePerson.getAddresses().add(person.getAddress()); 
         collectivePerson.getPhones().add(person.getPhone()); 

         return collectivePerson; 
        } else { 
         collectivePerson.getAddresses().add(person.getAddress()); 
         collectivePerson.getPhones().add(person.getPhone()); 

         return null; 
        } 
       }) 
       .filter(Objects::nonNull) 
       .collect(Collectors.<CollectivePerson>toList()); 

     collectivePersons.forEach(System.out::println); 
    } 
} 

它做的工作和輸出爲:

CollectivePerson [id=1, addresses=[Address 1, Address 4], phones=[Phone 1, Phone 4]] 
CollectivePerson [id=2, addresses=[Address 2, Address 6, Address 5], phones=[Phone 5, Phone 2, Phone 6]] 
CollectivePerson [id=3, addresses=[Address 3], phones=[Phone 3]] 

但我相信有可能是一個更好的辦法,分組來實現相同的流路。任何指針都會很棒。

回答

3

而是操縱外部Map,你應該使用一個收藏家。有toMapgroupingBy,都允許解決這個問題,儘管由於你的類設計有點冗長。主要的障礙是缺乏的現有方法之一,合併一個PersonCollectivePerson或構建體來自給定Person實例或方法的CollectivePerson用於合併兩個CollectivePerson實例。

一種方法用做內置收藏家將

List<CollectivePerson> collectivePersons = persons.stream() 
    .map(p -> { 
     CollectivePerson cp = new CollectivePerson(p.getId()); 
     cp.getAddresses().add(p.getAddress()); 
     cp.getPhones().add(p.getPhone()); 
     return cp; 
    }) 
    .collect(Collectors.collectingAndThen(Collectors.toMap(
     CollectivePerson::getId, Function.identity(), 
     (cp1, cp2) -> { 
      cp1.getAddresses().addAll(cp2.getAddresses()); 
      cp1.getPhones().addAll(cp2.getPhones()); 
      return cp1; 
     }), 
     m -> new ArrayList<>(m.values()) 
    )); 

,但在這種情況下,一個自定義的收集器可能更簡單:

Collection<CollectivePerson> collectivePersons = persons.stream() 
    .collect(
     HashMap<Long,CollectivePerson>::new, 
     (m,p) -> { 
      CollectivePerson cp=m.computeIfAbsent(p.getId(), CollectivePerson::new); 
      cp.getAddresses().add(p.getAddress()); 
      cp.getPhones().add(p.getPhone()); 
     }, 
     (m1,m2) -> m2.forEach((l,cp) -> m1.merge(l, cp, (cp1,cp2) -> { 
      cp1.getAddresses().addAll(cp2.getAddresses()); 
      cp1.getPhones().addAll(cp2.getPhones()); 
      return cp1; 
     }))).values(); 

雙方將來自一個預定義的方法中受益合併兩個CollectivePerson實例,而第一個變體也將受益於CollectivePerson(Long id, Set<String> addresses, Set<String> phones)構造函數或更好,CollectivePerson(Person p)構造函數,而第二個將受益於CollectivePerson.add(Person p)方法...

請注意,第二個變體在不復制的情況下返回Map s值的Collection視圖。如果您確實需要List,則可以像使用裝訂器功能中的第一個變型那樣簡單地使用new ArrayList<>(«map» .values())來簡化合同。

+0

謝謝你,我採取了你的第二個變種,它非常快。在約10秒鐘內操作~1000000個'人員'。 –

5

可以使用Collectors.toMap與合併功能:

public static <T, K, U, M extends Map<K, U>> 
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper, 
          Function<? super T, ? extends U> valueMapper, 
          BinaryOperator<U> mergeFunction, 
          Supplier<M> mapSupplier) 

的映射是這樣的:

Map<Long,CollectivePerson> collectivePersons = 
    persons.stream() 
     .collect(Collectors.toMap (Person::getId, 
            p -> { 
             CollectivePerson cp = new CollectivePerson (p.getId()); 
             cp.getAddresses().add (p.getAddress()); 
             cp.getPhones().add(p.getPhone()); 
             return cp; 
            }, 
            (cp1,cp2) -> { 
             cp1.getAddresses().addAll(cp2.getAddresses()); 
             cp1.getPhones().addAll(cp2.getPhones()); 
             return cp1; 
            }, 
            HashMap::new)); 

您可以用方便地提取從MapList<CollectivePerson>

new ArrayList<>(collectivePersons.values()) 

這是輸出Map爲您的樣品輸入:

{1=CollectivePerson [id=1, addresses=[Address 1, Address 4], phones=[Phone 1, Phone 4]], 
2=CollectivePerson [id=2, addresses=[Address 2, Address 6, Address 5], phones=[Phone 5, Phone 2, Phone 6]], 
3=CollectivePerson [id=3, addresses=[Address 3], phones=[Phone 3]]} 
+1

沒有必要指定'HashMap :: new';這個任務並不要求map是'HashMap'的一個實例... – Holger

+0

@Holger你是對的。出於某種原因,我認爲採用合併功能的'toMap'唯一變體也需要供應商。也就是說,我只是注意到,實現3參數映射(即沒有供應商)是'返回地圖(keyMapper,valueMapper,mergeFunction,HashMap :: new);':) – Eran

+1

是的,在目前的實施中,它總是產生一個'HashMap',就像'toList()'總是產生一個'ArrayList'一樣,然而,這並不能保證,如果沒有要求得到這些類型的確切實例,你應該允許實現改變任何有利於變革的承諾。反過來說,沒有一種方法可以在不需要合併功能的情況下獲取地圖供應商。 – Holger

1

使用groupBy收藏家分組您的人!

List<CollectivePerson> list = persons.stream().collect(Collectors.groupingBy(Person::getId)).entrySet().stream().map(x -> { 
    // map all the addresses from the list of persons sharing the same id 
    Set<String> addresses = x.getValue().stream().map(Person::getAddress).collect(Collectors.toSet()); 
    // map all the phones from the list of persons sharing the same id 
    Set<String> phones = x.getValue().stream().map(Person::getPhone).collect(Collectors.toSet()); 
    // declare this constructor that takes three parameters 
    return new CollectivePerson(x.getKey(), addresses, phones); 
}).collect(Collectors.toList()); 

對於這個工作,你需要添加此構造:

public CollectivePerson(Long id, Set<String> addresses, Set<String> phones) { 
    this.id = id; 
    this.addresses = addresses; 
    this.phones = phones; 
} 
0
Map<Long, CollectivePerson> map = persons.stream(). 
      collect(Collectors.groupingBy(Person::getId, 
        Collectors.collectingAndThen(Collectors.toList(), 
          Main::downColl))); 

使用用於從具有相同id的人的列表創建CollectivePerson對象的方法的參考。

public static CollectivePerson downColl(List<Person> ps) { 

    CollectivePerson cp = new CollectivePerson(ps.get(0).getId());   
    for (Person p:ps) { 
     cp.getAddresses().add(p.getAddress()); 
     cp.getPhones().add(p.getPhone()); 
    } 
    return cp; 
}