Stream#reject が欲しいと切に願う


まあ実際は切に願うほどではないんですけど、

List.of("a", "", "b").stream()
    .reject(s -> s.isEmpty())
    .collect(Collectors.toList());
// => ["a", "b"]

みたいに書きたいわけです。

でも実際はそんなものは無いわけで...

Stream#filter を使って書くわけです。

いろいろと書いてみると...

実装する関数で否定を返す

List.of("a", "", "b").stream()
    .filter(s -> !s.isEmpty())
    .collect(Collectors.toList());

まあシンプルな実装なら悪くないんですけど、複雑な条件とかだと頭混乱しちゃいます。

あとシンプルな条件でも、ぱっと見で ! を見逃すケースが怖いですよね。

Predicate#negate を使う

List.of("a", "", "b").stream()
    .filter(((Predicate<String>) (s -> s.isEmpty())).negate())
    .collect(Collectors.toList());

// う~んんんキャストうざい!
Predicate<String> emptyText = s -> s.isEmpty();
List.of("a", "", "b").stream()
    .filter(emptyText.negate())
    .collect(Collectors.toList());

Predicate#negate 自体はすごくやりたいことを実現できているんですが、キャストがががが...

否定条件を返すメソッド作る

// 元の条件の否定条件の Predicate 返す
private <E> Predicate<? super E> not(Predicate<? super E> p) {
    return p.negate();
}

こういうの作って、

List.of("a", "", "b").stream()
    .filter(not(s -> s.isEmpty()))
    .collect(Collectors.toList());

うん!悪くない!

ただ、not をユーティリティーとかにしてしまうと、StreamUtils.not みたいになって記述が長くなってしまうのがオシイ感じです。

ラップしてしまえ!

public class StreamWrapper<T> {

    private final Stream<T> stream;

    // private constructor
    private StreamWrapper(Stream<T> src) {
        this.stream = src;
    }

    // make instance
    public static <T> StreamWrapper<T> of(Stream<T> src) {
        return new StreamWrapper(src);
    }

    // Stream#filter をラップ
    public StreamWrapper<T> filter(Predicate<T> p) {
        return this.of(stream.filter(p));
    }

    // Stream#collect をラップ
    public <R, A> R collect(Collector<T, A, R> c) {
        return stream.collect(c);
    }

    // ...他にもいっぱいラップ

    /**
     * このストリームの要素のうち、指定された述語に一致<b>しない</b>ものから構成されるストリームを返します。
     * <p>
     * これは中間操作です。
     *
     * @param predicate
     *            各要素を含めるべきか判定する目的で各要素に適用する、非干渉でステートレスな述語
     * @return 新しいストリーム
     */
    public StreamWrapper<T> reject(Predicate<T> predicate) {
        return this.of(stream.filter(predicate.negate()));
    }

}

こういうの作って、

StreamWrapper.of(List.of("a", "", "b").stream())
    .reject(s -> s.isEmpty())
    .collect(Collectors.toList());

やったね!

まあ、reject ごときのためにこんなに手間のかかることしないけれど...

実装クラス作ってしまえ!

J・M・T(実装・マジ・手間

なかなかに手間のかかる子 Stream API

いざ「こういう中間操作欲しい!」ってなってもなかなか拡張するのには骨が折れます。

Java9 で takeWhile dropWhile ofNullable とか追加されて便利になっていますが、zip など主要っぽい機能は実装されていない(reject も)ので必要に応じて拡張してみてはいかがでしょう?