LoginSignup
2
2

More than 5 years have passed since last update.

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

Posted at

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

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 自体はすごくやりたいことを実現できているんですが、キャストがががが... :scream:

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

// 元の条件の否定条件の 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(実装・マジ・手間 :innocent:

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

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

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

2
2
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2