きり丸の技術日記

技術検証したり、資格等をここに残していきます。

Javaで配列の特定の条件を満たすデータのみを操作したかった

※ 泥臭いやり方でしか実践できていません。


JavaのLINEオープンチャットにて、「配列内部の特定条件のみ変換したい」という質問がありました。

今後、同様の質問が来たときにスマートな回答ができるようにいくつか検証したメモを残します。なお冒頭にも書いてあるとおり、泥臭いやり方での変換ですので、コメントやTwitterにてよりよいコードを教えていただけると非常に喜びます。

環境

  • Java
    • 17

仕様

次の仕様を満たせるようにします。

[When]
パラメータとして、1,2,3の配列を渡す。
[Then]
・偶数は+100する
・奇数は+50する
・※ 期待値は各案ごとに異なる

対応案1

拡張For文かStream式のMapに条件分岐と処理を記載する

一番素直に処理したパターンです。配列内の順序等も変更したくないのであれば、これが一番ベストです。

条件分岐と処理が混ざっているので嫌ですが、1週回ってシンプルな処理となります。

  @Test
  @DisplayName("""51, 102, 53の配列に変換する
      """)
  void test_01_01() {
    var base = Stream.of(1, 2, 3);

    var actual =
        base.map(e -> {
          if (e % 2 == 0) {
            return e + 100;
          } else {
            return e + 50;
          }
        }).collect(Collectors.toList());

    assertThat(actual).isEqualTo(Arrays.asList(51, 102, 53));
  }

対応案2

必要な処理を抽出してから操作し、マージする

配列内の順序は無視してよければ、こちらの方法も使用できます。

処理対象となる元の配列をSupplierクラスに変換します。Streamクラスを取得するためにSupplier#getを行う必要があります。

条件分岐ごとにStreamを分離させて処理させるため、配列内の順序が重要であれば結合させたのちにソートする必要があります。

少々面倒ですが、各抽出処理と操作を分離できるため、あとから確認したときに役割が分かりやすくなります。

  @Test
  @DisplayName("""
      ・合計値102, 51, 53を返却する
      """)
  void test_01_02() {
    Supplier<Stream<Integer>> base = () -> Stream.of(1, 2, 3);

    var evenList =
        base.get()
            .filter(e -> e % 2 == 0)
            .map(e -> e + 100);

    var oddList =
        base.get()
            .filter(e -> e % 2 != 0)
            .map(e -> e + 50);

    var actual = Stream.concat(evenList, oddList);

    assertThat(actual.collect(Collectors.toList()))
        .isEqualTo(Arrays.asList(102, 51, 53));
  }

なお、Supplierクラスを経由せずにStreamクラスでそのまま処理しようとすると次のエラーが発生します。

stream has already been operated upon or closed
java.lang.IllegalStateException: stream has already been operated upon or closed

対応案2の例だと偶数に+100処理した後、Streamが閉じられてしまうため、奇数の処理をしようとしたときにエラーが発生しています。Supplierクラスを挟んでいることで、Supplier#getをするたびに新しいStreamが生成されているため、問題が起こりません。

コンパイルエラーではなく、ランタイムエラーで発生するため、ちゃんとテストして確認しましょう。

ソースコード

※ CI/CDは落ちていますが、テスト自体はとおっています。

終わりに

JavaGoldの試験にてpeekメソッドを使用すると意図的に副作用を起こせるという認識がありました。ですので、「filterメソッドとpeekメソッドで期待どおり変換できるのでは」と回答したのですが検証すると嘘であることがわかりました。peekメソッドの使い方に関しては、別の記事にしたいとは考えています。

最終的には、対応案1で記載することは多いでしょうが、対応案2で処理できる可能性を見いだせたことは私のJavaスキルの成長だと考えることにします。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

参考情報

f:id:nainaistar:20211113160834p:plain