Mengingat,

class Foo {
private Long id;
private String name;
private String category;
private List<String> categories;
// getters & setters
}

Saya memiliki daftar objek.

final Foo f1 = new Foo(1L, "a", "c1");
final Foo f2 = new Foo(1L, "a", "c2");
final Foo f3 = new Foo(2L, "a", "c1");

final List<Foo> li = List.of(f1, f2, f3);

Yang terlihat seperti

{[Foo [id=1, name=a, category=c1, categories=null], Foo [id=1, name=a, category=c2, categories=null]], [Foo [id=2, name=a, category=c1, categories=null]]}

Saya ingin mengubah ini menjadi

[Foo [id=1, name=a, category=null, categories=[c1, c2]], Foo [id=2, name=a, category=null, categories=[c1]]]

Yaitu menyusun individu category ke dalam daftar categories.

Ini adalah kode saat ini yang mencapai apa yang saya inginkan.

public static void main(final String[] args) {
        final Foo f1 = new Foo(1L, "a", "c1");
        final Foo f2 = new Foo(1L, "a", "c2");
        final Foo f3 = new Foo(2L, "a", "c1");

        final List<Foo> li = List.of(f1, f2, f3);
        li.forEach(e -> System.out.println(e));

        final Map<Long, List<Foo>> collect = li.stream().collect(Collectors.groupingBy(Foo::getId));
        System.out.println(collect);

        final List<Foo> grouped = new ArrayList<>();
        collect.forEach((k, v) -> {
            System.out
                    .println("key=" + k + "val=" + v.stream().map(e1 -> e1.getCategory()).collect(Collectors.toList()));

            final Foo foo = collect.get(k).get(0);
            foo.setCategories(v.stream().map(e1 -> e1.getCategory()).collect(Collectors.toList()));
            foo.setCategory(null);

            grouped.add(foo);
        });

        System.out.println(grouped);
    }

Apakah ada cara untuk melakukan ini menggunakan stream & lambda saja tanpa harus membobol beberapa langkah? Tujuannya agar kode ini lebih elegan, mudah dibaca, dan menyampaikan maksud kepada pembaca.

Pertanyaan ini mirip dengan Grup menurut dan jumlah objek seperti di SQL dengan Java lambdas? tetapi tidak membantu saya karena ada agregasi yang dilakukan sedangkan di sini bukan agregasi.

1
Cyriac George 19 November 2020, 21:21

1 menjawab

Jawaban Terbaik

Ini dapat dilakukan dengan menerapkan fungsi merge yang akan mengakumulasi kategori dalam daftar kategori, dan kemudian menggunakan operasi reduce aliran:

class Foo {
    static Foo merge(Foo accum, Foo other) {
        if (null == accum.categories) {
            accum.categories = new ArrayList<>();
            if (null != accum.category) {
                accum.categories.add(accum.category);
                accum.category = null;
            }
        }
        accum.categories.add(other.category);

        return accum;
    }
}

Penerapan:

static List<Foo> joinedCategoriesReduce(List<Foo> input) {
    return input
            .stream()
            .collect(Collectors.groupingBy(Foo::getId))  // Map<Integer, List<Foo>>
            .values().stream()   // Stream<List<Foo>>
            .map(v -> v.stream() // Stream<Foo>
                    .reduce(new Foo(v.get(0).getId(), v.get(0).getName(), (String)null), Foo::merge)
            )
            .collect(Collectors.toList());
}

Uji

final Foo f1 = new Foo(1L, "a", "c1");
final Foo f2 = new Foo(1L, "a", "c2");
final Foo f3 = new Foo(2L, "a", "c1");

final List<Foo> li = List.of(f1, f2, f3);
joinedCategoriesReduce(li).forEach(System.out::println);

Keluaran

Foo(id=1, name=a, category=null, categories=[c1, c2])
Foo(id=2, name=a, category=null, categories=[c1])

Pilihan lain adalah menyediakan konstruktor Foo yang menerima daftar kategori:

// class Foo
public Foo(Long id, String name, List<String> cats) {
    this.id = id;
    this.name = name;
    this.categories = cats;
}

Kemudian entri peta dapat dengan mudah dipetakan ulang ke instance Foo dengan daftar kategori:

static List<Foo> joinedCategoriesMap(List<Foo> input) {
    return input
            .stream()
            .collect(Collectors.groupingBy(Foo::getId))
            .values().stream()
            .map(v -> new Foo(
                    v.get(0).getId(),
                    v.get(0).getName(),
                    v.stream().map(Foo::getCategory).collect(Collectors.toList()))
            )
            .collect(Collectors.toList());
}

Demo online

1
Alex Rudenko 19 November 2020, 19:32