原文はこちら。
https://openjdk.java.net/projects/amber/LVTIFAQ.html
Why have
ローカル変数はJavaの主役です。ローカル変数を使うと、メソッドが中間値を安いコストで格納することにより、重要な結果を計算できます。フィールドとは異なり、ローカル変数は宣言されたブロック内でのみ利用するため、コンパイラは常にローカル変数が利用前に初期化されていると保証できます。コードを理解する上で、ローカル変数の名前や初期値は、ローカル変数の方よりも重要であることがよくあります。型は重要ではありますが、次の行で使う名前のほうが重要なことがあります。
型ではなく
Does this make Java dynamically typed? Is this like
どちらの質問に対する答えも、Noです。Javaは変わらず静的型言語で、
Javaコンパイラは長年にわたって型推論をしてきました。例えばJava 8では、ラムダ式のパラメータには明示的な型を必要としていませんが、これはコンパイラがラムダ式での使われ方から、パラメータの型を推論しているからです。
Is a
いいえ。
Javaの場合、
さらに、Javaでは
機能が導入されると、まず最初はプログラマーがその機能を使用したり、過度に使用したり、悪用したりすることさえあるでしょう。が、それはよくあることです。合理的な利用方法に関するガイドラインにコミュニティが落ち着くまでには時間がかかります。大部分のローカル変数宣言ではなく、かなり頻繁に
ローカル変数の型推論[Local Variable Type Inference (LVTI)]から、機能提供開始時期にあわせてこのFAQやLVTI Style Guidelinesのような、推奨する利用方法に関する資料を公開しています。
Where can
ただし、
例えば、メソッドの戻り値がメソッドの
推論によりフィールドの型が決まる場合、フィールドのイニシャライザへの変更の結果フィールドの型が変わる可能性があり、結果としてreflective codeを予期せずに破壊する可能性があります。
型推論は実装内ではOKですが、APIではNGです。APIのコントラクトは明示的に宣言すべきです。
APIの一部ではないプライベート・フィールドやメソッドではどうでしょうか?理論的には、従属コンパイルと動的リンクにより互換性を損なう心配がないため、privateフィールドとprivateメソッドの戻り値の型に対して
Why is an initializer required on the right-hand side of
変数の型は、イニシャライザの型から推測されます。これはもちろん、varはイニシャライザがある場合にのみ使用できるということです。変数への代入から型を推測することも可能でしたが、その場合、この機能がかなり複雑になってしまい、誤解を招くか、診断しづらいエラーにつながる可能性があります。物事を単純にするために、ローカル情報だけを使って型推論するようvarを定義しました。
宣言とは別の複数の場所での代入を基にして型推論できたとしましょう。例えば以下のような例です。
あるいは、すべての割り当てと互換性のあるタイプを選択することもできますが、この場合、推論される型がObjectであると予想される方がいらっしゃるかもしれません。というのも、ObjectはStringとIntegerの共通のスーパークラスであるためです。しかし残念ながら、この状況はもっと複雑です。StringとIntegerの両方がSerializableにしてComparableであるため、共通のスーパータイプは以下のような奇妙な交差型になるでしょう。
これらの問題を避けるため、明示的なイニシャライザを使った型推論を要求したほうがよいと思われます。
Why can't you use
以下のような宣言を考えてみましょう(これは誤りです)
nullに初期化されたvar宣言がObject型を持つと推測されるように特別な規則を作ることもできました。確かに可能なのですが、プログラマの意図に対する疑問が出てきます。変数は後で他の値に割り当てることができるように、nullに初期化されている場合、変数の型をObjectとして推論するのは正しい選択であるとは考えづらいのです。
このケースを処理するための特別なルールを作らず、禁止することにしました。Object型の変数が必要な場合、明示的に宣言する必要があります。
Can you use
はい、可能です。しかし、おそらくは期待されているようなものではないでしょう。例えば以下の例を考えます。
https://openjdk.java.net/projects/amber/LVTIFAQ.html
Why have var
in Java?(なぜJavaにvar
?)
ローカル変数はJavaの主役です。ローカル変数を使うと、メソッドが中間値を安いコストで格納することにより、重要な結果を計算できます。フィールドとは異なり、ローカル変数は宣言されたブロック内でのみ利用するため、コンパイラは常にローカル変数が利用前に初期化されていると保証できます。コードを理解する上で、ローカル変数の名前や初期値は、ローカル変数の方よりも重要であることがよくあります。型は重要ではありますが、次の行で使う名前のほうが重要なことがあります。var
を使うことで、ローカル変数宣言の重要な部分を目立たせることができます。型ではなく
var
を使う場合、Javaコンパイラは初期値から変数の型を推論します。これは特に型が長い名前であったり、複雑なパラメータ化された型、もしくは型が初期値と重複している場合に価値があります。var
を使うことで、コードの読みやすさを犠牲にせずに簡潔にできますし、土岐には冗長性を取り除いて可読性を向上させることもできます。Does this make Java dynamically typed? Is this like var
in JavaScript?(これはJavaが動的な型になるということですか?Javaのvar
はJavaScriptのvar
のようなものですか?)
どちらの質問に対する答えも、Noです。Javaは変わらず静的型言語で、var
が加わったからといって変わることはありません。型ではなく、var
をローカル変数の宣言で利用可能、というだけです。var
を使う場合、Javaコンパイラはコンパイル時に変数のイニシャライザから取得する型情報を使って変数の型を推論します。その後、変数の静的型として推論された型を使います。通常、推論した型は明示的に書いた場合と同じ型のため、var
を使って宣言した変数は、型を明示的に記述した場合とまったく同じように正しく振る舞います。Javaコンパイラは長年にわたって型推論をしてきました。例えばJava 8では、ラムダ式のパラメータには明示的な型を必要としていませんが、これはコンパイラがラムダ式での使われ方から、パラメータの型を推論しているからです。
上記のコード・スニペットでは、ラムダ式のパラメータList<Person> list = ...
list.stream().filter(p -> p.getAge() > 18) ...
p
は静的な型Person
を持つと推論しています。getAge
メソッドを持たないようにPerson
クラスを変更した場合、もしくはPerson
以外の型のリストに変更した場合、型推論はコンパイル時のエラーで失敗します。Is a var
variable final?(var
で宣言した変数はfinalですか?)
いいえ。var
で宣言したローカル変数はデフォルトでfinalではありません。ただし、final
修飾子はvar
宣言に追加できます。Javaではfinal var person = new Person();
final var
の短縮はありません。Scalaのような言語ではval
を使ってイミュータブル(final)変数を宣言します。Scalaの場合、全変数(ローカル変数でもフィールドでも)が以下の形式の構文を使って宣言されるため、うまくいきます。もしくはval name : type
型推論させたいか否かによって、宣言のvar name : type
": type"
部分を含めることも、省略することもできます。Scalaの場合、可変性(mutability)と不変性(immutability)の選択が型推論と直交します。Javaの場合、
var
は型推論が必要な場合にのみ利用できます。つまり、明示的に型宣言されている箇所では利用できません。val
を追加した場合、この場合も型推論を用いる箇所でのみ利用できます。型が明示的に宣言されている場合、Javaでvar
もしくはval
の利用によって不変性を制御することはできません。さらに、Javaでは
var
をローカル変数に対してのみ利用できます(フィールドは対象外です)。不変性はフィールドではずっと重要ではありますが、イミュータブルなローカル変数は(イミュータブルなフィールド変数に比べると)あまり使われません。var
/val
キーワードを使って不変性を制御することはScalaからJavaへきれいに引き継ぐべきであるような機能ですが、JavaにおいてはScalaにおける場合ほど有用ではないと思われます。Won't bad developers misuse this feature to write terrible code?(ひどい開発者がこの機能を誤用してとんでもないコードを書くのではないでしょうか?)
はい、ひどい開発者はどんなふうにしてもひどいコードを書くことでしょう。機能を保留しても、ひどいコードを禁止できないでしょうしかし、適切に利用すれば、開発者は型推論を使ってよりよいコードを書くことができます。var
の利用によって、新しい変数の宣言のオーバーヘッドが減るため、開発者はよりよいコードを書くことができます。変数の宣言のオーバーヘッドが高いと、開発者はそのオーバーヘッドを避けようとします。そのため、より多くの変数宣言をせずにすむよう、可読性を損なう複雑なネスト、もしくはチェーンになった式を作成するでしょう。var
を使うと名前付き変数に部分式(subexpression)を引き渡すオーバーヘッドが少なくなるため、開発者はvar
を使うようになるでしょう。その結果、コードがきれいになるでしょう。機能が導入されると、まず最初はプログラマーがその機能を使用したり、過度に使用したり、悪用したりすることさえあるでしょう。が、それはよくあることです。合理的な利用方法に関するガイドラインにコミュニティが落ち着くまでには時間がかかります。大部分のローカル変数宣言ではなく、かなり頻繁に
var
を使用することはおそらく合理的です。ローカル変数の型推論[Local Variable Type Inference (LVTI)]から、機能提供開始時期にあわせてこのFAQやLVTI Style Guidelinesのような、推奨する利用方法に関する資料を公開しています。
Style Guidelines for Local Variable Type Inference in Javaこうした取り組みがコミュニティによる合理的な
https://openjdk.java.net/projects/amber/LVTIstyle.html
https://orablogs-jp.blogspot.com/2018/03/style-guidelines-for-local-variable.html
var
の利用方法の収束を加速し、ほとんどのvar
の乱用を避ける手助けになることを願っています。Where can var
be used?(var
を利用可能な箇所は?)
var
はローカル変数やfor-loopのインデックス変数、try-with-resourcesにおけるリソース変数の宣言目的で利用できます。ただし、
var
はフィールドやメソッドパラメータ、メソッドの戻り値で利用することはできません。それは、これらの場所の型は明示的にクラスファイルやjavadoc仕様書に現れているからです。型推論を使えば、イニシャライザへの変更によって変数の推論された型を変更するのはきわめて簡単です。ローカル変数の場合、これは問題ありません。というのも、ローカル変数はスコープが限定されており、ローカル変数の型はクラスファイルに直接記録されていないからです。しかしながら、フィールドやメソッドのパラメータ、メソッドの戻り値の型を推論する場合、型推論では簡単に問題が発生する可能性があります。例えば、メソッドの戻り値がメソッドの
return
文から推論されたとしましょう。メソッドの実装に対する変更により、return
文の式の型が変更される可能性があります。この結果、メソッドの戻り値の型が変わるため、ソースやバイナリの非互換性が発生する可能性があります。このような互換性のない変更は、実装の無害な変更から生じるべきではありません。推論によりフィールドの型が決まる場合、フィールドのイニシャライザへの変更の結果フィールドの型が変わる可能性があり、結果としてreflective codeを予期せずに破壊する可能性があります。
型推論は実装内ではOKですが、APIではNGです。APIのコントラクトは明示的に宣言すべきです。
APIの一部ではないプライベート・フィールドやメソッドではどうでしょうか?理論的には、従属コンパイルと動的リンクにより互換性を損なう心配がないため、privateフィールドとprivateメソッドの戻り値の型に対して
var
をサポートすることも可能でしたが、簡単のためにこのように型推論のスコープを制限することにしました。いくつかのフィールドといくつかのメソッドの戻り値を含むように境界をプッシュしようとすると、そのフィーチャはかなり複雑で難しくなるものの、有用性はそれほど向上しません。Why is an initializer required on the right-hand side of var
?(イニシャライザがvar
の右辺に必要な理由は?)
変数の型は、イニシャライザの型から推測されます。これはもちろん、varはイニシャライザがある場合にのみ使用できるということです。変数への代入から型を推測することも可能でしたが、その場合、この機能がかなり複雑になってしまい、誤解を招くか、診断しづらいエラーにつながる可能性があります。物事を単純にするために、ローカル情報だけを使って型推論するようvarを定義しました。宣言とは別の複数の場所での代入を基にして型推論できたとしましょう。例えば以下のような例です。
(例えば)最初の代入に基づいて型を選択した場合、エラーの原因から遠く離れた別の文でエラーが発生します(これは「遠隔操作」問題とも呼ばれることがあります)。var x;
...
x = "foo";
...
x = 17;
あるいは、すべての割り当てと互換性のあるタイプを選択することもできますが、この場合、推論される型がObjectであると予想される方がいらっしゃるかもしれません。というのも、ObjectはStringとIntegerの共通のスーパークラスであるためです。しかし残念ながら、この状況はもっと複雑です。StringとIntegerの両方がSerializableにしてComparableであるため、共通のスーパータイプは以下のような奇妙な交差型になるでしょう。
(この型の変数を明示的に宣言できないことに注意してください。)また、17をxに代入すると、予期せず、望ましくないボクシング変換(boxing conversion)が発生します。Serializable & Comparable<? extends Serializable & Comparable<...>>
これらの問題を避けるため、明示的なイニシャライザを使った型推論を要求したほうがよいと思われます。
Why can't you use var
with null
?(null
と一緒にvar
を使ってはいけない理由は?)
以下のような宣言を考えてみましょう(これは誤りです)nullリテラルは、Javaのすべての参照型のサブタイプである特殊なnull型(JLS 4.1)の値を示します。var x = null; // ERROR
The Java® Language Specification - Java SE 11 Editionnull型の唯一の値はnullそのものなので、null型の変数に代入できる唯一の値はnullです。これはあまり役に立ちません。
The Kinds of Types and Values
https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.1
nullに初期化されたvar宣言がObject型を持つと推測されるように特別な規則を作ることもできました。確かに可能なのですが、プログラマの意図に対する疑問が出てきます。変数は後で他の値に割り当てることができるように、nullに初期化されている場合、変数の型をObjectとして推論するのは正しい選択であるとは考えづらいのです。
このケースを処理するための特別なルールを作らず、禁止することにしました。Object型の変数が必要な場合、明示的に宣言する必要があります。
Can you use var
with a diamond on the right-hand side?(右辺でダイアモンド演算子と一緒にvar
を利用できる?)
はい、可能です。しかし、おそらくは期待されているようなものではないでしょう。例えば以下の例を考えます。この場合、リストの型がvar list = new ArrayList<>();
ArrayList<Object>
であると推論されます。一般的には、右側でダイヤモンドを使う場合は左側で明示的な型を、左側でvarを使う場合には右側には明示的な型を、それぞれ使用するのが望ましいでしょう。詳細については、LVTIスタイルガイドラインのガイドラインG6を参照してください。Style Guidelines for Local Variable Type Inference in Java
G6. Take care when using var with diamond or generic methods.
https://openjdk.java.net/projects/amber/LVTIstyle.html#g6.-take-care-when-using-var-with-diamond-or-generic-methods.
https://orablogs-jp.blogspot.com/2018/03/style-guidelines-for-local-variable.html#G6