2014-10-08 4 views
2

Я не могу найти хороший способ создать 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 с (а затем использовать StreamSupport/Spliterator), но это требует от меня, чтобы быть в состоянии перебрать источник несколько раз, так что это еще не является универсальным решением ,

ответ

2

Чтобы сделать Stream, вам нужен Spliterator. Затем вы делаете Stream с StreamSupport.stream(). (Это то, что делают все коллекции.)

Если у вас уже есть Iterable, вы можете получить Spliterator от своего метода spliterator() (хотя вы можете написать лучше один, по умолчанию является слабым).

Если у вас есть Iterator, вы можете превратить его в Spliterator с Spliterators.spliteratorUnknownSize(Iterator). (Опять же, это дает вам разделитель, но не обязательно оптимальный.)

Если у вас их нет, вы можете просто написать Spliterator; это обычно проще, чем писать Iterator (больше методов для реализации, но в целом проще, потому что вам не нужно дублировать логику между next и hasNext.)

+0

Спасибо Брайан. Класс 'Spliterators' и' spliteratorUnknownSize' были чем-то вроде недостающей части моей головоломки. Тот факт, что 'tryAdvance' должен вызывать следующее значение для потребителя, также был немного запутанным при использовании для реализации старых старых итераторов. Но я согласен, возможно, проще реализовать «Spliterator», когда-то привыкший к этому. – aioobe

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

Странно, что нет простой реализации абстрактного потока с абстрактными методами, подобными обычным Iterator. Похоже, что hasNext/next будет достаточным для реализации рудиментарного потока. – aioobe

+1

На самом деле я нахожу 'tryAdvance' гораздо более удобным для использования таких случаев, поскольку логика содержится в одном методе, а не распространяется на два метода и конструктор. Помимо дополнительных заводских методов, которые я добавил для более удобного использования, для реализации «Spliterator» требуется всего три небольших метода. Я не думаю, что это сложнее, чем реализация «Итератора». Обратите внимание, что вы можете создать «разделитель» из «Итератора» (http://docs.oracle.com/javase/8/docs/api/java/util/Spliterators.html#spliteratorUnknownSize-java.util.Iterator -int-) ... – Holger

+0

А, интересные моменты. Я не знал о методе spliteratorUnknownSize! – aioobe

Смежные вопросы