簡體   English   中英

使用Collection.stream按特定屬性動態分組

[英]Dynamic grouping by specific attributes with Collection.stream

我試圖通過使用Java 8 Collection-Stream按多個屬性對對象列表進行分組。

效果很好:

public class MyClass
{
   public String title;
   public String type;
   public String module;
   public MyClass(String title, String type, String module)
   {
      this.type = type;
      this.title = title;
      this.module= module;
   }
}

List<MyClass> data = new ArrayList();
data.add(new MyClass("1","A","B"));
data.add(new MyClass("2","A","B"));
data.add(new MyClass("3","A","C"));
data.add(new MyClass("4","B","A"));

Object result = data.stream().collect(Collectors.groupingBy((MyClass m) 
-> m.type, Collectors.groupingBy((MyClass m) -> m.module)));

但我想使其更具動態性。 我只想指定一個應該用於GroupBy的字符串數組(或列表)。

就像是:

Object groupListBy(List data, String[] groupByFieldNames)
{
    //magic code
}

我想打電話給:

groupListBy(data, new String[]{"type","module"});

像我的示例一樣,如何使groupBy-Method更動態?

使代碼更具動態性的主要問題是,您事先不知道要分組多少個元素。 在這種情況下,最好按所有元素的List進行分組。 之所以可行,是因為如果兩個列表的所有元素均相同且順序相同,則它們相等。

在這種情況下,我們將按每個數據類型和模塊組成的列表進行分組,而不是按類型和模塊進行分組。

private static Map<List<String>, List<MyClass>> groupListBy(List<MyClass> data, String[] groupByFieldNames) {
    final MethodHandles.Lookup lookup = MethodHandles.lookup();
    List<MethodHandle> handles = 
        Arrays.stream(groupByFieldNames)
              .map(field -> {
                  try {
                      return lookup.findGetter(MyClass.class, field, String.class);
                  } catch (Exception e) {
                      throw new RuntimeException(e);
                  }
              }).collect(toList());
    return data.stream().collect(groupingBy(
            d -> handles.stream()
                        .map(handle -> {
                            try {
                                return (String) handle.invokeExact(d);
                            } catch (Throwable e) {
                                throw new RuntimeException(e);
                            }
                        }).collect(toList())
        ));
}

代碼的第一部分將字段名稱數組轉換為MethodHandleList 對於每個字段,都為該字段檢索一個MethodHandle :這是通過從MethodHandles.lookup()獲取一個查找並使用MethodHandles.lookup()查找給定字段名稱的句柄來findGetter

產生方法句柄,以提供對非靜態字段的讀取訪問權限。

其余代碼創建分類器,以將其分組。 在數據實例上調用所有句柄以返回String值列表。 Stream被收集到List以用作分類器。

樣例代碼:

public static void main(String[] args) {
    List<MyClass> data = new ArrayList<>();
    data.add(new MyClass("1", "A", "B"));
    data.add(new MyClass("2", "A", "B"));
    data.add(new MyClass("3", "A", "C"));
    data.add(new MyClass("4", "B", "A"));

    System.out.println(groupListBy(data, new String[] { "type", "module" }));
}

輸出:

{[B, A]=[4], [A, B]=[1, 2], [A, C]=[3]}

重寫MyClass.toString()以僅返回title

除了名稱列表,您還可以考慮提供功能列表(強制一個)以對元素進行分組。

這些函數應該將MyClass的元素映射到對象,因此可以使用Function<MyClass, ?>

private static Map<List<Object>, List<MyClass>> groupListBy(List<MyClass> data, Function<MyClass, ?> mandatory, Function<MyClass, ?>... others) {
   return data.stream()
              .collect(groupingBy(cl -> Stream.concat(Stream.of(mandatory), Stream.of(others)).map(f -> f.apply(cl)).collect(toList())));
}

還有一些通話示例:

groupListBy(data, m -> m.type); //group only by type
groupListBy(data, m -> m.type, m -> m.module); //group by type and by module

當然,您可以使此方法通用,以便它返回Map<List<Object>, List<U>> ,其函數類型為U -> Object

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM