使用参数化流

时间:2016-05-28 20:44:42

标签: java lambda java-8 java-stream

此代码:

Kid[] kids = Kid.getSimpleArray();
String names = Stream.of(kids)
                .filter(Kid::hasToy)
                .map(Kid::getSurname)
                .collect(Collectors.joining(", "));
out.println(names);

给了我这个结果:

Stępień,Inglot,Czubówna,Lepiej,Łagowska

我正在考虑一个解决方案,让它像这样:

1.Stępień,2.Inglot,3.Czubówna,4.Lepiej,5.Łagowska

P.S我想不出一个更好的问题来得到我正在寻找的答案。

4 个答案:

答案 0 :(得分:4)

请注意,这真的很难看,不是我应该在这里给出的建议,但现在是。

您可以使用临时收藏品作为柜台。优点是我们可以阻止引用作为final,但仍然添加元素。

每次Kid通过过滤器并在String中获取整数的大小时,我们都会添加任何内容。

final List tmp = new ArrayList();
Kid[] kids = Kid.getSimpleArray();
String names = Stream.of(kids)
                .filter(Kid::hasToy)
                .peek(x -> tmp.add(1))
                .map(x -> tmp.size() + "." + x.getSurname())
                .collect(Collectors.joining(", "));
out.println(names);

修改

以下是@Sam和@Tunaki提供的另外两个解决方案

@Sam

final AtomicInteger ai = new AtomicInteger();
Kid[] kids = Kid.getSimpleArray();
String names = Stream.of(kids)
                .filter(Kid::hasToy)
                .map(x -> ai.incrementAndGet() + "." + x.getSurname())
                .collect(Collectors.joining(", "));
out.println(names);

@Tunaki

Kid[] kids = Kid.getSimpleArray();
Kid[] filteredKids = Arrays.stream(kids).filter(Kid::hasToy).toArray(Kid[]::new);
String names = IntStream.range(0, filteredKids.length)
                        .mapToObj(i -> i + "." + filteredKids[i].getSurname())
                        .collect(Collectors.joining(", ");
out.println(names);

答案 1 :(得分:2)

使用增强标准Stream API的第三方免费库StreamEx(由我编写),您可以zip带有数字的流:

<p><highlight class="highlight" data-id="1464586442243" data-container="body" data-toggle="popover" data-placement="right" data-content="<p>first</p><button class=&quot;btn edit_annotation&quot; data-id=&quot;1464586442243&quot;>Edit</button>&amp;nbsp;<button class=&quot;btn delete_annotation&quot; data-id=&quot;1464586442243&quot;>Delete</button>" id="anchor_1464586442243" data-original-title="" title="">Section</highlight> 1 remov<highlight></highlight>able true</p>

答案 2 :(得分:1)

Streams只能同时对一个项目进行操作。每个计算只知道它正在操作的元素。因此,尝试在订单中添加项目位置的一些知识总是一个黑客。

收集到List而不是字符串将允许您对所有项目的列表进行最终计算,而不是单独处理单个项目。这是一种采用List并将其格式化为逗号分隔的String编号项的方法:

static String enumerateAndJoin(List<String> l, String separator) {
    StringBuffer sb = new StringBuffer();
    ListIterator<String> it = l.listIterator();
    while(it.hasNext()) {
        sb.append(it.nextIndex() + 1).append(".").append(it.next());
        if (it.hasNext()) {
            sb.append(separator);
        }
    }
    return sb.toString();
}

你当然可以这样做:

 List<String> surnames = Stream.of(kids)
            .filter(Kid::hasToy)
            .map(Kid::getSurname)
            .collect(toList());

 String output = enumerateAndJoin(surnames, ", ");

但是如果你在几个地方使用它,那么定义一个新的收集器可能会很方便。

Collector<String, ?, String> myCollector = collectingAndThen(toList(), list -> enumerateAndJoin(list, ", "));

String output = Stream.of(kids)
            .filter(Kid::hasToy)
            .map(Kid::getSurname)
            .collect(myCollector);

然而,所有这些真正有用的是模糊代码和浪费周期。最终,我们现在对这些物品进行了两次传递。通过将流处理为迭代器来计算已经遇到的项目数量的'hack'解决方案只需执行一次。这种方法只适用于非常简单的有序流,并且根本不适用于流。

该流对于其流畅的filtermap方法肯定具有吸引力。但是,如果你能够承受更多的冗长,为什么不首先使用真正的迭代器迭代kids呢?

int i = 0;
Iterator<Kid> it = new ArrayIterator<>(kids);
StringBuffer sb = new StringBuffer();
while (it.hasNext()) {
    Kid kid = it.next();
    if (!kid.hasToy()) continue;
    sb.append(++i).append(".").append(kid.getSurname());
    if (it.hasNext()) {
        sb.append(", ");
    }
}

对于保存一两行的hybrid方法并且可以说更清楚,我喜欢Guava的FluentIterable

int i = 0;
Iterator<Kid> kidsWithToys = FluentIterable.of(kids).filter(Kid::hasToy).iterator();
StringBuffer sb = new StringBuffer();
while (kidsWithToys.hasNext()) {
    sb.append(++i).append(".").append(kidsWithToys.next().getSurname());
    if (kidsWithToys.hasNext()) {
        sb.append(", ");
    }
}

对于某些问题,流不是答案,我认为这是其中之一。

答案 3 :(得分:1)

在您的示例中,完成所有过滤后,将收集并加入字符串。由于您希望最终得到字符串值,因此我们可以创建toString()版本(忘记您的Stream已包含字符串的那一刻),这个版本会计入前缀,如下所示:

public static<T> Function<T, String> getCountedToStringFunc(){
    AtomicInteger i = new AtomicInteger(0);
    return t -> i.incrementAndGet() + ". " + t.toString();
}

你可以像这样使用它:

myStream
.filter(...)
.map(getCountedToStringFunc())
.collect(Collectors.joining(",")));

或者,注意到必须记住在管道中尽可能晚地应用计数器会产生可维护性风险如果您希望将来修改管道,您可以完全将其从管道中取出并使其成为一部分终止功能:

myStream
.filter(...)
.collect(Collectors.mapping(getCountedToStringFunc(),joining(","))));

后一种方法的好处是可以使用它为分区或分组数据创建单独的计数器。