Java 8流经多个层并连接最底层

时间:2017-09-07 16:53:25

标签: java java-8 java-stream

我目前有一个多层结构数据,如下所示:

  

行业类有一个私有字段Set<Company>,可以为空。

     

公司类有一个私有字段Set<Division>,可以为空。

     

Division类有一个私有字段Set<Group>,可以为null。

     

组类有一个私有字段groupName,可以为null   可以使用getter(getGroupName())检索。

我正在尝试将Industry的实例一直流式传输到Group层,并使用&#34; /&#34;将所有groupName连接到一个String中。介于两者之间。

如果此行业实例未包含任何groupName,请返回字符串&#34; null&#34;。

基于我对Java 8的有限知识,我正在考虑这样的编码:

industry.stream()
     .flatmap(industry -> industry.getCompanies().stream())
     .filter(Objects::nonNull)
     .flatmap(company -> company.getDivisions().stream())
     .filter(Objects::nonNull)
     .flatmap(division -> division.getGroups().stream())
     .map(group -> group.getGroupName)
     .collect(Collectors.joining("/")));

这段代码似乎有些瑕疵。另外,我不知道在哪里添加声明,如果Industry无法检索任何groupName,而不是将所有groupName连接成一个字符串,只需返回一个字符串&#34; null&#34;。

在我的情况下使用Java 8流的正确方法是什么?

感谢。

4 个答案:

答案 0 :(得分:5)

Collectors.joining(…)基于班级StringJoiner。它提供了分隔符,前缀和后缀功能,但遗憾的是无法提供空值。

要添加该功能,我们必须重新实施Collectors.joining,感谢使用StringJoiner时不那么难。

更改流操作的最后一行

.collect(Collectors.joining("/"));

.filter(Objects::nonNull) // elide all null elements
.collect(()->new StringJoiner("/", "", "").setEmptyValue("null"), // use "null" when empty
         StringJoiner::add, StringJoiner::merge).toString();

答案 1 :(得分:0)

我理解你的问题几乎任何东西都可以为空。在这种情况下,您可以创建自己的函数来处理这个问题。我这样做了一个:

/**
 * Creates a stream function for the provided collection function which ignores all null values.
 * Will filter out null values passed into the collection function and null values from the resulting stream
 * @param collectionFn
 * @param <T>
 * @param <R>
 * @return
 */
public static <T, R> Function<T, Stream<R>> nullSafeMapper(Function<T, Collection<R>> collectionFn) {
    return (v) -> Optional.ofNullable(v)
            .map(collectionFn)
            .map(Collection::stream)
            .orElse(Stream.empty())
            .filter(Objects::nonNull);
}

基本上它是完全空的安全,过滤掉输入和输出中为空的任何东西。并可以这样使用:

industries.stream()
    .flatMap(SO46101593.nullSafeMapper(Industry::getCompanies))
    .flatMap(SO46101593.nullSafeMapper(Company::getDivisions))
    .flatMap(SO46101593.nullSafeMapper(Division::getGroups))
    .map(group -> group.getGroupName())
    .filter(Objects::nonNull) // filter out null group names
    .collect(Collectors.joining("/"));

你也可以把这个逻辑直接推到你的表达式中,但由于它必须重复3次才会有点......冗长和重复

答案 2 :(得分:0)

以下是空检查的示例:

String s = industries.stream()
.filter( i -> i.getCompanies() != null ).flatMap( i -> i.getCompanies().stream() )
    .filter( c -> c != null && c.getDivisions() != null ).flatMap( c -> c.getDivisions().stream() )
        .filter( d -> d != null && d.getGroups() != null ).flatMap( d -> d.getGroups().stream() )
            .filter( g -> g != null && g.getGroupName() != null ).map( g -> g.getGroupName() )                    
.collect( Collectors.joining("/") );   

您可以使用Holger的示例替换Collectors.joining("/")

答案 3 :(得分:0)

这应该这样做:

Stream.of(industry)
        .map(Industry::getCompanies).filter(Objects::nonNull)
        .flatMap(Set::stream)
        .map(Company::getDivisions).filter(Objects::nonNull)
        .flatMap(Set::stream)
        .map(Division::getGroups).filter(Objects::nonNull)
        .flatMap(Set::stream)
        .map(Group::getGroupName).filter(Objects::nonNull)
        .collect(Collectors.collectingAndThen(Collectors.joining("/"),
                names -> names.isEmpty() ? "null" : names));

我假设industry.stream()不正确,因为你说你是在“工业实例”工作。相反,我使用一个元素创建Stream<Industry>

在尝试对它们调用stream之前,您需要对集合进行空检查。你正在检查stream是否返回null,这已经太晚了。

从空结果到“null”的最终转换属于Collector中“终结者”功能的概念。 Collectors.joining不允许您直接指定整理程序,但可以使用Collectors.collectingAndThen将整理程序添加到任何现有Collector