Java 8は値または関数を繰り返すための良い方法を提供しますか?


118

他の多くの言語、例えば。Haskell、値や関数を複数回繰り返すのは簡単です。値1の8つのコピーのリストを取得するには、次のようにします。

take 8 (repeat 1)

しかし、Java 8ではまだこれが見つかりません。Java8のJDKにそのような関数はありますか?

または、代わりに次のような範囲に相当するもの

[1..8]

それはJavaのような冗長なステートメントの明らかな置き換えと思われるでしょう

for (int i = 1; i <= 8; i++) {
    System.out.println(i);
}

のようなものを持っている

Range.from(1, 8).forEach(i -> System.out.println(i))

この特定の例は実際にははるかに簡潔に見えませんが...うまくいけば、より読みやすくなります。


2
Streams APIについて学習しましたか?JDKに関する限り、これが最善の策です。それはレンジ関数を持っています、それは私がこれまでに見つけたものです。
Marko Topolnik 2013

1
@MarkoTopolnik Streamsクラスが削除されました(より正確には、他のいくつかのクラスに分割され、一部のメソッドは完全に削除されました)。
アッシリア2013

3
forループの詳細を呼び出します!それはあなたがCobolの時代にはなかった良いことです。昇順の数値を表示するには、Cobolで10を超える宣言文が必要です。最近の若者は、それがどれほど優れているかを理解していません。
Gilbert Le Blanc

1
@GilbertLeBlancの冗長性はそれとは関係ありません。ループは構成可能ではなく、ストリームは構成可能です。ループは不可避の繰り返しを引き起こしますが、ストリームは再利用を許可します。そのため、ストリームはループよりも定量的に優れた抽象化であり、推奨されます。
Alain O'Dea 2015

2
@GilbertLeBlancと私たちは雪の中で裸足でコーディングする必要がありました。
Dawood ibnカリーム2016

回答:


155

この特定の例では、次のことができます。

IntStream.rangeClosed(1, 8)
         .forEach(System.out::println);

1以外のステップが必要な場合は、たとえば、ステップ2のマッピング関数を使用できます。

IntStream.rangeClosed(1, 8)
         .map(i -> 2 * i - 1)
         .forEach(System.out::println);

または、カスタムの反復を作成して、反復のサイズを制限します。

IntStream.iterate(1, i -> i + 2)
         .limit(8)
         .forEach(System.out::println);

4
クロージャーは、Javaコードを完全に変換します。その日を楽しみにしています...
Marko Topolnik 2013

1
@jwentingそれは本当に依存します-通常、GUIのもの(SwingまたはJavaFX)では、匿名クラスのために多くのボイラープレートが削除されます。
アッシリア2013

8
@jwenting FPの経験がある人なら、高階関数を中心に展開するコードは純粋な勝利です。その経験のない人にとっては、あなたのスキルをアップグレードする時がきました---さもなければほこりに取り残されるリスク。
Marko Topolnik 2013

2
@MarkoTopolnik少し新しいバージョンのjavadocを使用することをお勧めします(ビルド78を指し、最新はビルド105です:download.java.net/lambda/b105/docs/api/java/util/stream/…
マークロットビール

1
@GraemeMoss引き続き同じパターン(IntStream.rangeClosed(1, 8).forEach(i -> methodNoArgs());)を使用できますが、IMOを混乱させ、その場合はループが示されているようです。
アッシリア2013

65

ここに私が先日遭遇した別のテクニックがあります:

Collections.nCopies(8, 1)
           .stream()
           .forEach(i -> System.out.println(i));

このCollections.nCopies呼び出しにより、指定した値のList包含nコピーが作成されます。この場合は、ボックス化されたInteger値1です。もちろん、実際にはn要素を持つリストは作成されません。値と長さのみを含む「仮想化」リストを作成し、get範囲内への呼び出しは値を返すだけです。このnCopiesメソッドは、JDK 1.2でCollections Frameworkが導入されて以来存在しています。もちろん、その結果からストリームを作成する機能は、Java SE 8で追加されました。

おお、同じ行数で同じことをする別の方法。

ただし、この手法はIntStream.generateand IntStream.iterate手法よりも高速であり、驚くべきことに、IntStream.range手法よりも高速です。

iterategenerate結果は、おそらくあまりにも驚くべきことではありません。ストリームフレームワーク(実際には、これらのストリームのスプリッター)は、ラムダが毎回異なる値を生成する可能性があり、無制限の数の結果が生成されるという想定に基づいて構築されています。これにより、並列分割が特に困難になります。このiterate方法では、呼び出しごとに前の呼び出しの結果が必要になるため、この方法にも問題があります。したがって、ストリームは繰り返し定数を生成するために使用しgenerateiterateあまり機能しません。

のパフォーマンスが比較的低いのrangeは驚くべきことです。これも仮想化されているため、要素は実際にはすべてメモリ内に存在するわけではなく、サイズは事前にわかっています。これにより、高速で簡単に並列化可能なスプリッターが作成されます。しかし、意外にもうまくいきませんでした。おそらくその理由はrange、範囲の各要素の値を計算し、その上で関数を呼び出す必要があるためです。しかし、この関数はその入力を無視して定数を返すだけなので、これがインライン化されて殺されていないことに驚いています。

Collections.nCopiesプリミティブな特殊化がないため、この手法では値を処理するためにボックス化/ボックス化解除を行う必要がありますList。値は毎回同じなので、基本的に一度ボックス化され、そのボックスはすべてのnコピーで共有されます。私はボクシング/アンボクシングが高度に最適化されていて、本質的にさえされていると思います、そしてそれはうまくインライン化することができます。

これがコードです:

    public static final int LIMIT = 500_000_000;
    public static final long VALUE = 3L;

    public long range() {
        return
            LongStream.range(0, LIMIT)
                .parallel()
                .map(i -> VALUE)
                .map(i -> i % 73 % 13)
                .sum();
}

    public long ncopies() {
        return
            Collections.nCopies(LIMIT, VALUE)
                .parallelStream()
                .mapToLong(i -> i)
                .map(i -> i % 73 % 13)
                .sum();
}

そして、これがJMHの結果です:(2.8GHz Core2Duo)

Benchmark                    Mode   Samples         Mean   Mean error    Units
c.s.q.SO18532488.ncopies    thrpt         5        7.547        2.904    ops/s
c.s.q.SO18532488.range      thrpt         5        0.317        0.064    ops/s

ncopiesバージョンにはかなりのばらつきがありますが、全体的にはレンジバージョンよりも20倍速く快適に見えます。(しかし、私は何か間違ったことをしたと信じても構わないと思います。)

このnCopiesテクニックがうまく機能していることに驚いています。内部的には、それほど特別なことはせず、仮想化リストのストリームはIntStream.range!これを高速に行うには、専用のスプリッターを作成する必要があると思っていましたが、すでにかなり良いようです。


6
あまり熟練していない開発者nCopiesは、実際には何もコピーせず、「コピー」がすべて1つのオブジェクトを指していることを理解すると、混乱したりトラブルに巻き込まれたりする可能性があります。この例のボックス化されたプリミティブのように、そのオブジェクトが不変である場合は常に安全です。「boxed once」ステートメントでこれをほのめかしていますが、その動作は自動ボクシングに固有ではないため、ここで警告を明示的に呼び出すとよいでしょう。
ウィリアム価格

1
つまり、それLongStream.rangeはそれよりもかなり遅いことを意味しIntStream.rangeますか?したがって、IntStreamLongStreamすべての整数型に使用する)を提供しないという考えが廃止されたことは良いことです。順次使用の場合、ストリームを使用する理由はまったくないことに注意してください。Collections.nCopies(8, 1).forEach(i -> System.out.println(i));同じように機能しますCollections.nCopies(8, 1).stream().forEach(i -> System.out.println(i));が、より効率的かもしれませんCollections.<Runnable>nCopies(8, () -> System.out.println(1)).forEach(Runnable::run);
Holger

1
@Holger、これらのテストはクリーンタイププロファイルで実行されたため、実際の世界とは無関係です。おそらくLongStream.range、それは2つのマップがあるため、性能が悪いLongFunctionの内側をしながら、ncopiesと3つのマップを持ってIntFunctionToLongFunctionそしてLongFunction、したがって、すべてのラムダは単相です。事前に汚染されたタイプのプロファイル(実際のケースに近い)でこのテストを実行するncopiesと、1.5倍遅くなります。
Tagir Valeev、2015

1
時期尚早の最適化FTW
Rafael Bugajewski

1
完全を期すために、これら両方の手法を単純な古いforループと比較するベンチマークを見るとよいでしょう。あなたの解決策はStreamコードよりも速いですが、私の推測では、forループはこれらのどちらかを大幅に上回っています。
タイプレーサー

35

完全性のため、そして私は自分自身を助けることができなかったので:)

限られた定数のシーケンスを生成することは、Javaレベルの冗長性がある場合にのみ、Haskellで表示されるものにかなり近くなります。

IntStream.generate(() -> 1)
         .limit(8)
         .forEach(System.out::println);

() -> 11のみを生成しますが、これは意図されたものですか?したがって、出力はになります1 1 1 1 1 1 1 1
クリスチャンウレンブーム2014年

4
はい、OPの最初のHaskellの例によるとtake 8 (repeat 1)。assyliasは他のすべてのケースをほぼカバーしていました。
clstrfsck 2014年

3
Stream<T>generate他のタイプの無限ストリームを取得するための汎用メソッドもあり、同じ方法で制限できます。
zstewart 2017年

11

繰り返し関数がどこかとして定義されると

public static BiConsumer<Integer, Runnable> repeat = (n, f) -> {
    for (int i = 1; i <= n; i++)
        f.run();
};

あなたはそれを時々そしてこのようにそれを使うことができます、例えば:

repeat.accept(8, () -> System.out.println("Yes"));

Haskellのものを取得して同等のものにする

take 8 (repeat 1)

あなたは書くことができます

StringBuilder s = new StringBuilder();
repeat.accept(8, () -> s.append("1"));

2
これは素晴らしいです。しかし、私は変更することによって、反復バックの数を提供するために、それを改変RunnableFunction<Integer, ?>、次いで使用しますf.apply(i)
Fons 2017

0

これは、time関数を実装するための私のソリューションです。私はジュニアなので、理想的ではないかもしれないと認めます。これが何らかの理由で良い考えではない場合は、喜んでお知らせします。

public static <T extends Object, R extends Void> R times(int count, Function<T, R> f, T t) {
    while (count > 0) {
        f.apply(t);
        count--;
    }
    return null;
}

次に使用例をいくつか示します。

Function<String, Void> greet = greeting -> {
    System.out.println(greeting);
    return null;
};

times(3, greet, "Hello World!");
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.