2017-09-04 93 views
3

我有一個Java類:Java集合 - 通過多組屬性

class Person { 
    String firstName; 
    String lastName; 
    int income; 

public Person(String firstName, String lastName, int income) 
{ 
    this.firstName = firstName; 
    this.lastName = lastName; 
    this.income = income; 
} 

我有一個Collection<Person>,與4×Person對象:

Collection<Person> persons = new ArrayList<>(); 
    persons.add(new Person("John", "Smith", 5)); 
    persons.add(new Person("Mary", "Miller", 2)); 
    persons.add(new Person("John", "Smith", 4)); 
    persons.add(new Person("John", "Wilson", 4)); 

我想打一個新的集合實例,但是來自具有相同「firstName」和「lastName」的元素使1元素,並且結果「收入」將是每個元素的「收入」的總和。因此,對於這種特殊情況下,所產生的集合將有3個元素,而「約翰·史密斯」將有「收入」 = 9

在SQL,等效查詢:

SELECT FIRSTNAME, LASTNAME, SUM(INCOME) FROM PERSON GROUP BY FIRSTNAME, LASTNAME 

我發現僅包含Map作爲結果的答案,「key」包含用於分組的列。我想從最初的(ArrayList<Person>)直接獲取類似的集合類型,而不是Map,因爲如果在我的集合中有數百萬個元素,則會降低代碼的性能。如果我在SQL方面工作,我知道這很容易,但在這種情況下,我必須在Java方面工作。

+0

請注意,大多數Java解決方案不會使用SQL使用的任何重度優化。所以你的分組等價物總是不那麼高效,使用它們之間的映射,然後將所有內容轉換爲你想要的輸出格式。另一種方法是使用一個在Java中提供SQL語法的庫,這些庫也可能使用一些重要的優化。 – Zabuza

回答

0

您可以使用Java 8流珍藏groupingBy:

Map<String, Integer> sum = items.stream().collect(
      Collectors.groupingBy(p -> p.getFirstName()+p.getSecondName(), Collectors.summingInt(Person::getIncome))); 
+0

確實有用且緊湊,但是OP表示他搜索了一個不提供「Map」而是「ArrayList」的解決方案。您可以在最後轉換結果。 – Zabuza

2

我不知道這是否是最漂亮的解決方案,但你可以嘗試groupByfirstNamelastName與它們之間的分隔符,讓我們比如.。在收集包含您的firstName.lastNameMap<String, Integer>的數據後,您將從中創建Person的新列表。

List<Person> collect = persons.stream() 
      .collect(Collectors.groupingBy(person -> person.getFirstName() + "." + person.getLastName(), 
        Collectors.summingInt(Person::getIncome))) 
      .entrySet().stream().map(entry -> new Person(entry.getKey().split(".")[0], 
                     entry.getKey().split(".")[1], 
                     entry.getValue())) 
      .collect(Collectors.toList()); 
+0

*或*你需要一個擁有''AbstractMap.SimpleEntry'或java-9''List.of()'''hashcode/equals'的'Holder' ... – Eugene

0

我覺得JoSQL是用自己的方式去這裏,它允許你在Java對象運行SQL查詢:

JoSQLSQL的Java對象)提供的能力讓開發人員將SQL語句應用於Java對象集合。 JoSQL提供了對任何Java對象進行搜索,排序和分組的能力,並且當您希望對一組Java對象執行類似SQL的查詢時應該應用JoSQL。

這是如何使用它在你的情況:

Query q=new Query(); 
q.parse("SELECT firstname, lastname, SUM(income) FROM package.Person GROUP BY firstname, lastname"); 
List<?> results=q.execute(names).getResults(); 

您也可以按照此JoSQL tutorial進一步閱讀。

1

我發現下面的答案:

List<Person> collect = persons.stream() 
      .collect(Collectors.groupingBy(person -> person.getFirstName() + "." + person.getLastName(), 
        Collectors.summingInt(Person::getIncome))) 
      .entrySet().stream().map(entry -> new Person(entry.getKey().split(".")[0], 
                     entry.getKey().split(".")[1], 
                     entry.getValue())) 
      .collect(Collectors.toList()); 

不要這樣幹!它使用很多內存。使用Wrapper(PersonComparator)來處理需要分組的字段。

public class Main { 
    public static void main(String[] args) { 
     Collection<Person> persons = new ArrayList<>(); 
     persons.add(new Person("John", "Smith", 5)); 
     persons.add(new Person("Mary", "Miller", 2)); 
     persons.add(new Person("John", "Smith", 4)); 
     persons.add(new Person("John", "Wilson", 4)); 

     Map<Person, Integer> groupedByIncomes = persons.stream() 
       .collect(
         Collectors.groupingBy(
           Person::getPersonComparator, 
           Collectors.summingInt(Person::getIncome) 
         ) 
       ) 
       .entrySet() 
       .stream() 
       .collect(Collectors.toMap(
         e -> e.getKey().person, 
         Map.Entry::getValue 
       )); 

     System.out.println(groupedByIncomes); 
    } 

    static class Person { 
     String firstName; 
     String lastName; 
     int income; 

     public Person(String firstName, String lastName, int income) { 
      this.firstName = firstName; 
      this.lastName = lastName; 
      this.income = income; 
     } 

     public String getFirstName() { 
      return firstName; 
     } 

     public String getLastName() { 
      return lastName; 
     } 

     public int getIncome() { 
      return income; 
     } 

     PersonComparator getPersonComparator() { 
      return new PersonComparator(this); 
     } 

     static class PersonComparator { 
      Person person; 

      PersonComparator(Person person) { 
       this.person = person; 
      } 

      @Override 
      public boolean equals(Object o) { 
       if (this == o) return true; 
       if (o == null || getClass() != o.getClass()) return false; 
       PersonComparator that = (PersonComparator) o; 
       if (!person.getFirstName().equals(that.person.getFirstName())) return false; 
       return person.getLastName().equals(that.person.getLastName()); 
      } 

      @Override 
      public int hashCode() { 
       int result = person.getFirstName().hashCode(); 
       result = 31 * result + person.getLastName().hashCode(); 
       return result; 
      } 
     } 
    } 
} 

如果您需要框架解決方案f.e.當您需要對數據類型進行抽象時(SQL,Mongo或Collections),我建議您使用QueryDSL:http://www.querydsl.com/

+0

在我的任務中,我必須使用許多groupingBy相同的集合,所以我不能使用equals()和hashCode()的版本。所以,現在我會嘗試這個答案。但是如果我的性能出現問題,我可以使用更多的Compator類? –

+0

equals()和hashCode()用於自定義比較器,它通過條件反映組:firstName和lastName是條件 - 所以使用這些字段 –

相關問題