Java 8 Streams按可选属性分组

时间:2016-10-20 22:53:34

标签: java java-stream optional collectors

我正在尝试使用属性中的计算值进行分组。计算出的值是可选的 - 为了更加清楚,他是一个简化的例子:

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&#34; someAttribute&#34;过滤。

例如,我有这样的事情:

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语句中是否有更清晰或更正确的方法?

1 个答案:

答案 0 :(得分:3)

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

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;
    }
}