Java Streams - forEach with Action-action和post-Action

时间:2017-11-12 20:49:49

标签: java java-stream

使用Stream.forEach()时,我在想是否无法在流不为空时添加预执行和后期操作。例如,在打印List时,可以在流为空时添加内容或写入其他内容。

现在我想出了像

这样的东西
private static <T> void forEach(Stream<T> stream, Consumer<? super T> action,
    Runnable preAction, Runnable postAction, Runnable ifEmpty) {
    AtomicBoolean hasElements = new AtomicBoolean(false);
    Consumer<T> preActionConsumer = x -> {
        if (hasElements.compareAndSet(false, true)) {
            preAction.run();
        }
    };
    stream.forEach(preActionConsumer.andThen(action));
    if (hasElements.get()) {
        postAction.run();
    } else {
        ifEmpty.run();
    }
}

对于顺序流,这应该有效,如果不是的话? 这种方法是否正确,它是“好主意”有这样的方法还是有任何警告?

这对并行流不起作用,因为preAction可能比执行action的另一个线程慢,但正确实现它而不诉诸同步或其他并发工具,这会破坏并行的目的溪流可能不容易......

编辑:添加用例。使用正则表达式从文件中读取整数并将其写入另一个文件。使用这种方法,我不必在内存中创建一个String,然后将其写入某个文件。 (显然,对于我的实际任务,我使用的是更复杂的正则表达式。)

public static void main(String[] args) throws IOException {
    Stream<String> lines = Files.lines(Paths.get("foo.txt"));

    Pattern findInts = Pattern.compile("(\\d+)");
    Path barFile = Paths.get("bar.txt");
    try (BufferedWriter writer = Files.newBufferedWriter(barFile , StandardOpenOption.CREATE_NEW)) {
        lines.flatMap(x -> findInts.matcher(x).results())
                .forEach(x-> convertCheckedIOException(() ->  {
                            writer.write(x.group(1));
                            writer.newLine();
                        })
                );
    }
}

public static void convertCheckedIOException(Run r) {
    try {
        r.run();
    } catch (IOException e) {
        throw new UncheckedIOException(e);
    }
}

interface Run {
    void run() throws IOException;
}

2 个答案:

答案 0 :(得分:2)

使用合适的工具完成工作。此任务不会从Stream API中受益。

func splitVideoUrl(videoUrl:String) -> String {

    //let token = videoUrl.split(separator: "user/")
    let ttstr:String = " https://www.youtube.com/user/myYoutubeChennal"
    if let range = ttstr.range(of: "user/") {
        let firstPart = String(describing: ttstr.startIndex..<range.lowerBound)
        print(firstPart) // print Hello
        return firstPart
    }
    return ""
}

您仍然可以引入Stream操作,例如

Pattern intPattern = Pattern.compile("\\d+");
try(Scanner scanner = new Scanner(Paths.get("foo.txt"));
    BufferedWriter writer = Files.newBufferedWriter(Paths.get("bar.txt"), CREATE_NEW)) {

    String s = scanner.findWithinHorizon(intPattern, 0);
    if(s == null) {
        // perform empty action
    } else {
        // perform pre action
        do {
            writer.append(s);
            writer.newLine();
        } while( (s=scanner.findWithinHorizon(intPattern, 0)) != null);
        // perform post action
    }
}

但是必须处理已检查的Pattern intPattern = Pattern.compile("\\d+"); try(Scanner scanner = new Scanner(Paths.get("foo.txt")); BufferedWriter writer = Files.newBufferedWriter(Paths.get("bar.txt"), CREATE_NEW)) { String firstLine = scanner.findWithinHorizon(intPattern, 0); if(firstLine == null) { // perform empty action } else { // perform pre action Stream.concat(Stream.of(firstLine), scanner.findAll(intPattern).map(MatchResult::group)) .forEach(line -> convertCheckedIOException(() -> { writer.write(line); writer.newLine(); }) ); // perform post action } } 只会使代码复杂化而无益。

答案 1 :(得分:1)

我喜欢拥有这样的工具的想法。起初我想过使用由preaction设置/取消设置的第二个标志,并且停止操作可能就足够了。但更复杂的是事先将preAction放在每个动作调用的前面,而不仅仅是第一个动作调用。

我提出了一个同步解决方案,可以强制执行订单preactionspost/empty。需要注意的是,第一批并行线程必须等待第一批并行线程完成,因为它们会遇到synchronized

private static <T> void forEach(Stream<T> stream, Consumer<? super T> action, Runnable preAction, Runnable postAction, Runnable ifEmpty)
{
    AtomicBoolean hasElements = new AtomicBoolean(false);

    stream.forEach(new Consumer<T>()
    {
        private Consumer<? super T> delegate = new Consumer<T>()
        {
            private Consumer<? super T> delegate2 = new Consumer<T>()
            {
                @Override
                public void accept(T x)
                {
                    System.out.println("check");
                    hasElements.set(true);
                    preAction.run();
                    action.accept(x);
                    delegate2 = action; // rest of first batch won't run preAction anymore
                    delegate = action; // next batches won't even synchronize anymore
                }
            };

            @Override
            public void accept(T x)
            {
                synchronized (this)
                {
                    delegate2.accept(x);
                }
            }
        };

        @Override
        public void accept(T x)
        {
            delegate.accept(x);
        }
    });

    if (hasElements.get()) { postAction.run(); } else { ifEmpty.run(); }
}

public static void main(String[] args)
{
    Stream<Integer> s = Stream.generate(() -> 1).limit(1000).parallel();
    forEach(s, i -> System.out.println(Thread.currentThread().getId()), () -> System.out.println("pre"),
            () -> System.out.println("post"), () -> System.out.println("empty"));
}

Output:
check
pre
...
many thread IDs
...
post