Search code examples
collectionsjava-8iteratorfactoryfluent

Iterator from object with next() and get()


Given an object like this:

Matcher matcher = pattern.matcher(sql);

with usage like so:

Set<String> matches = new HashSet<>();
while (matcher.find()) {
    matches.add(matcher.group());
}

I'd like to replace this while loop by something more object-oriented like so:

new Iterator<String>() {
    @Override
    public boolean hasNext() {
        return matcher.find();
    }

    @Override
    public String next() {
        return matcher.group();
    }
}

so that I can easily e.g. make a Stream of matches, stick to using fluent APIs and such.

The thing is, I don't know and can't find a more concise way to create this Stream or Iterator. An anonymous class like above is too verbose for my taste.

I had hoped to find something like IteratorFactory.from(matcher::find, matcher::group) or StreamSupport.of(matcher::find, matcher::group) in the jdk, but so far no luck. I've no doubt libraries like apache commons or guava provide something for this, but let's say I can't use those.

Is there a convenient factory for Streams or Iterators that takes a hasNext/next method combo in the jdk?


Solution

  • In java-9 you could do it via:

    Set<String> result = matcher.results()
                .map(MatchResult::group)
                .collect(Collectors.toSet());
    System.out.println(result);
    

    In java-8 you would need a back-port for this, taken from Holger's fabulous answer

    EDIT

    There is a single method btw tryAdvance that could incorporate find/group, something like this:

    static class MyIterator extends AbstractSpliterator<String> {
    
        private Matcher matcher;
    
        public MyIterator(Matcher matcher) {
            // I can't think of a better way to estimate the size here
            // may be you can figure a better one here
            super(matcher.regionEnd() - matcher.regionStart(), 0);
            this.matcher = matcher;
        }
    
        @Override
        public boolean tryAdvance(Consumer<? super String> action) {
            while (matcher.find()) {
                action.accept(matcher.group());
                return true;
            }
    
            return false;
        }
    }
    

    And usage for example:

    Pattern p = Pattern.compile("\\d");
    Matcher m = p.matcher("12345");
    Set<String> result = StreamSupport.stream(new MyIterator(m), false)
                .collect(Collectors.toSet());