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

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

新規登録して質問してみよう
ただいま回答率
85.50%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

2回答

20761閲覧

ラムダ式から参照されるローカル変数は、finalまたは事実上のfinalでなければならない理由

mosa

総合スコア218

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

6グッド

6クリップ

投稿2016/07/28 06:04

編集2016/07/28 06:29

Java8 において、ラムダ式外部のローカル変数を変更しようとすると、「ラムダ式から参照されるローカル変数は、finalまたは事実上のfinalである必要があります」というエラーになると思います。
また、Exceptionもラムダ式外部でCatchすることができません。

parallelストリーム であればNGなのは理解できますが、parallelでないものまでこのエラーとなります。
理由がわかる方がいらっしゃれば教えていただけないでしょうか。

parallel のときのみこの制約を適用する、という仕様では複雑になってしまうからでしょうか。

A-pZ, tignear, Mr_Roboto, ikuwow, Ryo, yohhoy👍を押しています

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

ローカル変数については、JVMの仕組み上、他のスコープから書き換えるという動作の実装ができないため、finalあるいは一度しか書き込みのない変数について、ラムダの生成時にコピーして渡すような実装になっています。ということで、「内部から書き換えうる」ものを弾くためにfinal限定となっています(プライベート変数については、親のクラスに見えないアクセサを作って対応しています)。

例外については、Javaの検査例外という仕組み上、ラムダから、ラムダを渡すインターフェースに指定されていない検査例外を投げうる形とするとコンパイルエラーになります。なお、非検査例外やインターフェースの例外指定があるために投げて構わない例外については(同期実行の場合)ラムダの外側でキャッチできます(参考)。

(編注)検査例外も状況によっては投げられるので、書き換えました。

投稿2016/07/28 07:45

編集2016/07/28 13:54
maisumakun

総合スコア145121

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/07/28 12:01

世間ではラムダとStream APIをよく混同されますが、正確には「ラムダ」からは例外をスローするのはOKであり、Stream APIでのスローはNGなわけです。 その一方でチェック例外をスローできるStream APIの実装例もあります。 https://github.com/JeffreyFalgout/ThrowingStream
mosa

2016/08/01 04:05

すみません。final の話と例外の話を同時にしてしまったのは良くなかったですね。 また、ラムダとStreamを混同して書いていました。 「実装がそうだから」ということであれば、理論的には将来的にparallelでないストリームに関しては final、もしくは実質的finalでなくてもOKとするようにすることも可能なのでしょうか。
maisumakun

2016/08/01 04:44

オブジェクトのアクセスは別ですが、それ以外の「プリミティブ型の変数への参照を取る」という機能がJVMレベルで存在しないので、実装しようとすればJavaレベルでなくJVM側にも手を入れる必要が出てくる可能性もあり、あまり現実的ではありません。そして、言語レベルで対応しなくても「publicなフィールドを持ったオブジェクト」を引き回すことで代用可能ですので、積極的にサポートするだけの動機もないと考えます。
raccy

2016/08/01 21:37

Scalaのラムダ式ではvalの変数(Javaでいうfinalな変数)も、varの変数(Javaでいうfinalではない変数)も参照できますし、varの変数は再代入もできます。JVMレベルでどう実現しているかまでは確認していませんが、今のJVMのままでも実現できそうな気はします。
mosa

2016/08/08 11:22

もう少しだけ引っ張らせてください。 Scalaは全てがオブジェクトなので、可能であると思います。 Javaでは下記の2つは等価?だと思うのですが、であれば、(JVMでなく)コンパイラでやってくれないものか・・・、と思いまして。 そもそも等価とはいえないですか?見当違いなことを言っていたらスミマセン。 ■非ラムダ List<String> list = Arrays.asList("hoge", "fuga"); int a = 0; for(String elem : list){ if(elem.startsWith("h")){ a++; } } ■ラムダ List<String> list = Arrays.asList("hoge", "fuga"); final int[] a = {0}; list.stream().filter(elem -> elem.startsWith("h")).forEach(elem -> a[0]++);
guest

0

mosaさん、私も十分理解していないのですが(特にinvokedynamic周り)、Javaはスタックフレーム形式でメソッドを実行しているというところに制約があるんだろうと思います。

一般的にはメソッドを呼び出すたびにスタックを積んでそこにローカル変数を入れれば良いわけです。しかし、Stream APIでは、invokedynamicを使って中間操作、終端操作を処理する流れで、スタックに格納するローカル変数を前もって決めなければならず、finalにしなければならないわけです。シーケンシャルであろうとパラレルであろうと、そういう仕組みになっているのでしょう。

JVMに手を入れる程度の話ではなく、それ以前にJava言語仕様を変えなければなりません。すると、互換性が損なわれることになり、エンタープライズ向けアプリケーションで大問題になります。

投稿2016/08/01 17:49

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問