Java 8 个按可选属性分组的流

Java 8 Streams Grouping by an Optional Attribute

我正在尝试对来自属性的计算值进行分组。计算值是可选的 - 更清楚一点,他是一个简化的例子:

class Foo: 
   int id;
   Group group;
   .. some other stuff

class Group: 
   String groupId;
   ... (some other stuff)

class SomeName:
   String someAttribute;

class Converter:
   public Optional<SomeName> getSomenameFromGroup(Group)

我无法更改 Converter 中的方法,因为它不属于我。

我有一个 Foo 列表,我想按 SomeName 的 "someAttribute" 进行过滤。

例如,我有这样的东西:

Map<String, List<Foo>> fooBySomeName = 
  fooList.stream().collect(Collectors
    .groupingBy(foo -> {
        Optional<SomeName> name = 
           converter.getSomenameFromGroup(foo.getGroup.getGroupId());
        return name.isPresent() ? name.get().someAttribute() : "";
    }));

但问题是,如果名称不在 groupingBy 语句中,我不想在我的地图中添加任何内容。我有这样的事情:

fooBySomeNames.remove("")

我认为这可以从地图中删除按该键分组的任何内容,但是在 groupingBy 语句中是否有更清晰或更正确的方法来执行此操作?

您可以使用过滤器删除条目,如下所示。

Map<String, List<Foo>> fooBySomeName = fooList.stream()
    .filter(foo -> fooToSomeAttribute(foo).isPresent())
    .collect(Collectors.groupingBy(foo -> fooToSomeAttribute(foo).get()));

private static Optional<String> fooToSomeAttribute(Foo foo)
{
    return Optional.ofNullable(foo)
        .map(Foo::getGroup)
        .flatMap(new Converter()::getSomenameFromGroup)
        .map(SomeName::getSomeAttribute);
}

或者,使用 pair 对象,您可以避免对每个 Foo 的 someAttribute 进行双重计算:

Map<String, List<Foo>> fooBySomeName = fooList.stream()
    .filter(Objects::nonNull)
    .map(FooAndSomeAttribute::new)
    .filter(pair -> pair.getSomeAttribute().isPresent())
    .collect(Collectors.groupingBy(
        pair -> pair.getSomeAttribute().get(),
        Collectors.mapping(
            FooAndSomeAttribute::getFoo, 
            Collectors.toList())));

private static class FooAndSomeAttribute
{
    private final Foo foo;
    private final Optional<String> someAttribute;

    public FooAndSomeAttribute(Foo foo)
    {
        this.foo = foo;
        this.someAttribute = Optional.ofNullable(foo)
            .map(Foo::getGroup)
            .flatMap(new Converter()::getSomenameFromGroup)
            .map(SomeName::getSomeAttribute);
    }

    public Foo getFoo() 
    {
        return foo;
    }

    public Optional<String> getSomeAttribute() 
    {
        return someAttribute;
    }
}