質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.53%

Java8のラムダ式での変数参照について(その2)

解決済

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 10K+

kidss

score 15

前回の質問でも書きましたが、ラムダ式において外側で定義されているローカル変数を使う時は、「ローカル変数に暗黙にfinalが付いているものと見なされる」ため、その変数に再代入を行うとコンパイルエラーになるという事は理解できました。

たまに、Java8のstream()APIによって、for文を全て置き換えるという記事を見たりしますが(私も出来れば置き換えたい)実際は例外処理がうまく扱えなかったり、外側で定義した変数が実質的finalであったりと、難しいんじゃないかと思えてきました。ListやMapには値を追加出来るとしても、私の認識としては、変更出来る事自体がおかしく、final変数は変更しない運用の方がいいのではないかと思っています。

例外は非検査例外を使えばなんとかなりそうですが、特に変数に関してはどうしようもないのかなと思い初めてます。

前置きが長くなりましたが、Javaの経験が長い方、Java8を理解出来ている方の意見として、for文を全て置き換えるような事は可能なのでしょうか?

可能なのであれば、特に外側で定義された変数の扱いなどをどうされているのか非常に興味がありますので教えて頂きたいです。
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

+2

ラムダでのforの置き換えに限らない話ですが、不変オブジェクトを中心にプログラミングするスタイルが現在注目されています。この設計指針はマルチコアCPUを活用するという観点では非常に有効なのです。

これに対してオブジェクトの値を変更するとその全ての参照元に伝播すること(副作用といいます)を活用してGUIプログラミングが為されてきたという事情もあります。副作用を活用することが便利な状況では活用すればよいと思いますが、そのようなケースでも副作用を積極的に利用する箇所以外は出来るだけ不変オブジェクトを中心にプログラミングするほうがバグの発生を抑えられるという経験則が広がっているかと思います。

「外側で定義された変数の扱い」とおっしゃっている部分というのは変数の副作用に係る部分かと思います。排除することも出来ますが、それによるメリットが少なければ無理をして排除する必要もありませんし、副作用を活用しているのだという部分については容易に置き換えることは難しいでしょう。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/04/15 00:21

    回答ありがとうございます。不変オブジェクトの考え方はあまりしたことなかったです。副作用に関しても非常に勉強になりました。

    キャンセル

checkベストアンサー

+1


外側で定義された変数については、私はfinalにすることは抵抗がありませんので、普通に使います。
デフォルトがfinalなら良かったのにと思っているくらいです。(関係ありませんが、例えばRustという言語ではデフォルトで不変ですがmutというキーワードで可変の変数が作れます)
とはいえ、すべての変更しない変数にfinalを付けるのは嫌なので、必要最低限のものにつけるようにしています。なので、"effectively final"の機能は歓迎しています。



次に、for文をforEachについていくつか考えてみます。


拡張for文(for-eachループ)で使用できるIterableインターフェイスを実装していれば、ストリームでなくてもforEachメソッドが使えます。

Iterable - Java SE 8 APIドキュメント



配列は直接forEachが使えません。
最近のJavaでは配列を使わないほうが良いとされていますが、旧来のAPIを使うと配列を使わざるを得ない場合もありますので、その場合はArrays.asList()を使ってListに変換することができます。Stream.of()を使ってストリームにすることもできます。
これは無理にforEachにしなくても良いと思います。

// import java.util.Arrays;
// import java.util.stream.Stream;

String[] sa = { "aaa", "bbb", "333" };
Arrays.asList(sa).forEach(System.out::println);
Stream.of(sa).forEach(System.out::println);



あるリストを同じ条件で加工して新しいリストを作る処理(map)は、ストリームで書くと1行で書けます。これはforEachを使いたいケースですね。
もうちょっと簡潔に書けたらなお良かったのですが。

// import java.util.ArrayList;
// import java.util.Arrays;
// import java.util.List;
// import java.util.stream.Collectors;

// 従来のやり方
List<String> a = Arrays.asList("aaa", "bbb", "333");
List<String> adash = new ArrayList<>();
for (String s : a) {
    adash.add(s.toUpperCase());
}
System.out.println(adash); // => AAA, BBB, 333

// ストリーム版
List<String> b = Arrays.asList("aaa", "bbb", "333");
List<String> bdash = b.stream().map(String::toUpperCase).collect(Collectors.toList());
System.out.println(bdash); // => AAA, BBB, 333



拡張for文で書けないようなケース、例えばカウンター変数を使うようなケースは、置き換えるのに抵抗がありますね。
(例えばScalaという言語ではzipWithIndexメソッドが標準で使えますが、Javaではタプルが無いので実現が難しいですね。)

// import java.util.Arrays;
// import java.util.List;
// import java.util.concurrent.atomic.AtomicInteger;

// 従来のやり方
List<String> a = Arrays.asList("aaa", "bbb", "333");
for (int i = 0; i < a.size(); i++) {
    System.out.printf("%d:%s ", i, a.get(i));
} // => 0:aaa 1:bbb 2:333

// ストリーム版
AtomicInteger count = new AtomicInteger();
a.forEach(x -> {
    System.out.printf("%d:%s ", count.getAndIncrement(), x);
}); // => 0:aaa 1:bbb 2:333



ということで、すべてのfor文を置き換える必要は無く、ケースバイケースで良いんじゃないかと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/04/15 00:19

    回答ありがとうございます。サンプルも書いていただいて大変分かりやすかったです。図々しくもついでに1点追加で質問したいのですが、、、

    finalで宣言した変数について
    ラムダ式に限った事ではありませんが、finalで宣言した不変オブジェクト以外については状態変更が出来てしまいますが、この点に関しては特に問題ないと思われますか?今までfinalを付ける変数に関しては、状態変更すらしない前提でやっていましたが、色々考えると代入さえ出来なければ問題ないようにも思ってきています。
    ```lang-java
    final List<String> names = new ArrayList<>();
    ...
    names = new ArrayList<>(); // finalなので代入はNG
    names.add("hoge"); // オブジェクトの状態変更なのでOK
    ```

    キャンセル

  • 2015/04/15 08:32

    Javaに限って言えば、まだ可変オブジェクトを排除するスタイルを完全には適用できないと思っています。
    回答に書いたmapの例のように、適用できるパターンがあってそれを使えばコードがすっきりするなら使って、コードが却って複雑になるようなら可変オブジェクトを使えば良いと思いますよ。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.53%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる