从头开始或从迭代器创建流

时间:2014-10-08 11:35:06

标签: java java-8 java-stream

我无法找到从头开始创建Stream的好方法。举例来说(注意,下面的代码只是为了讨论的例子)我有

Matcher m = Pattern.compile(re).matcher(input);
List<String> matches = new ArrayList<>();
while (m.find())
    matches.add(m.group());

并希望利用流API。我想做点像

List<String> matches = Stream.of(() -> m.find(), () -> m.group())
                             .collect(Collectors.toList());

其中() -> m.find()是一个函数,告诉我是否有更多元素,而() -> m.group()是一个提供下一个元素的函数。

例如,创建Iterator很容易:

class MatchIterator implements Iterator<String> {
    Matcher m;
    boolean hasNext;
    public MatchIterator(Matcher m) {
        this.m = m;
        hasNext = m.find();
    }
    @Override
    public boolean hasNext() {
        return hasNext;
    }
    @Override
    public String next() {
        String next = m.group();
        hasNext = m.find();
        return next;
    }
}

但我无法想出一种从迭代器中创建流的简单方法。


编辑:我意识到我可以创建Iterable来创建MatchIterator s(然后使用StreamSupport / Spliterator),但这需要我能够迭代来源多次,所以它仍然不是一个通用的解决方案。

2 个答案:

答案 0 :(得分:2)

如果您可以重新规划正则表达式以指定边界而不是匹配,那么您可以查看Pattern.splitAsStream。我没有找到类似的解决方案来获得匹配流,所以我做了一个将在这个答案结束后跟进。

此解决方案将允许创建Stream<MatchResult>而不是Stream<String>,因为使用map很容易.map(MatchResult::group)整个匹配的这种流,但提供更大的灵活性。请参阅以下用例:

String testcase="first \"second item\" third";
MatchSpliterator.stream("\"([^\"]+)\"|\\S+", testcase)
    .map(r->Optional.ofNullable(r.group(1)).orElseGet(r::group))
    .forEach(s->System.out.println("match: "+s));

打印

match: first
match: second item
match: third

当然,使用List<String>

收集到MatchSpliterator.stream(pattern, input) .map(MatchResult::group).collect(Collectors.toList());是直截了当的

实现:

public class MatchSpliterator implements Spliterator<MatchResult> {

    public static Stream<MatchResult> stream(String pattern, CharSequence input) {
        return stream(Pattern.compile(pattern), input);
    }
    public static Stream<MatchResult> stream(Pattern p, CharSequence input) {
        return stream(p.matcher(input));
    }
    public static Stream<MatchResult> stream(Matcher matcher) {
        return StreamSupport.stream(new MatchSpliterator(matcher), false);
    }
    private final Matcher matcher;

    private MatchSpliterator(Matcher m) {
        matcher=m;
    }
    public boolean tryAdvance(Consumer<? super MatchResult> action) {
        if(matcher.find()) {
            action.accept(matcher.toMatchResult());
            return true;
        }
        return false;
    }
    public Spliterator<MatchResult> trySplit() {
        return null;
    }
    public long estimateSize() {
        return Long.MAX_VALUE;
    }
    public int characteristics() {
        return NONNULL|ORDERED;
    }
}

答案 1 :(得分:2)

要制作Stream,您需要Spliterator。然后使用Stream制作StreamSupport.stream()。 (这就是所有收藏品所做的。)

如果您已经拥有Iterable,则可以从Spliterator方法获取spliterator()(尽管您可能想要写一个更好的方法;默认为弱)。

如果您有Iterator,则可以将其转为Spliterator Spliterators.spliteratorUnknownSize(Iterator)。 (同样,这会让你成为分裂者,但不一定是最佳分裂者。)

如果你没有,你可以写一个Spliterator;它通常比编写Iterator更容易(实现的方法更多,但通常更简单,因为您不必在nexthasNext之间复制逻辑。)