ラムダ式で使用される変数は最終的または実質的に最終的であるべきです
calTz
を使用しようとすると、このエラーが表示されます。
private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
try {
cal.getComponents().getComponents("VTIMEZONE").forEach(component->{
VTimeZone v = (VTimeZone) component;
v.getTimeZoneId();
if(calTz==null) {
calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
}
});
} catch (Exception e) {
log.warn("Unable to determine ical timezone", e);
}
return null;
}
final
変数は、1回しかインスタンス化できないことを意味します。 Javaでは、ラムダおよび匿名内部クラスでは、最終的でない変数を使用できません。
古いfor-eachループを使ってコードをリファクタリングできます。
private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
try {
for(Component component : cal.getComponents().getComponents("VTIMEZONE")) {
VTimeZone v = (VTimeZone) component;
v.getTimeZoneId();
if(calTz==null) {
calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
}
}
} catch (Exception e) {
log.warn("Unable to determine ical timezone", e);
}
return null;
}
このコードの一部の意味がわからなくても。
v.getTimeZoneId();
を呼び出すcalTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
では、最初に渡されたcalTz
を変更しないでください。そして、このメソッドでは使用しません。null
を返します、なぜ戻り値の型としてvoid
を設定しないのですか?これらのヒントもあなたが改善するのに役立つことを願っています。
他の答えは要件を証明しますが、彼らは理由が説明されていませんなぜ要件が存在するのか。
JLSはなぜ §15.27.2 に言及している:
実質的に最終的な変数を制限すると、動的に変化するローカル変数へのアクセスが禁止されます。これを取り込むと、並行性の問題が生じる可能性があります。
バグのリスクを減らすために、彼らは捕獲された変数が決して変異しないようにすることを決めました。
ラムダから、あなたは最終的でない何かへの参照を得ることができません。変数を保持するには、ラムダの外側から最後のラッパーを宣言する必要があります。
このラッパーとして最後の 'reference'オブジェクトを追加しました。
private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
final AtomicReference<TimeZone> reference = new AtomicReference<>();
try {
cal.getComponents().getComponents("VTIMEZONE").forEach(component->{
VTimeZone v = (VTimeZone) component;
v.getTimeZoneId();
if(reference.get()==null) {
reference.set(TimeZone.getTimeZone(v.getTimeZoneId().getValue()));
}
});
} catch (Exception e) {
//log.warn("Unable to determine ical timezone", e);
}
return reference.get();
}
Java 8には、「実質的に最終的な」変数という新しい概念があります。これは、初期化後に値が決して変化しない非最終ローカル変数が「実質的に最終的」と呼ばれることを意味します。
この概念が導入されたのは、Java 8より前では、anonymous class
で非最終ローカル変数を使用できなかったためです。 anonymous class
のローカル変数にアクセスしたい場合は、それを最終的なものにする必要があります。
lambda
が導入されたとき、この制限は緩和されました。そのため、ローカル変数final
をラムダとして初期化した後に変更しない場合は、それを匿名クラスにする必要があります。
Java 8は、開発者がfinal
を使うたびにローカル変数lambda
を宣言するという苦労を認識し、この概念を導入し、ローカル変数をfinal
にする必要がなくなりました。 anonymous class
のルールがまだ変更されていないのであれば、final
を使用するたびにlambdas
キーワードを記述する必要はありません。
私は良い説明を見つけました ここ
あなたの例では、forEach
をlamdbaで単純なfor
ループに置き換えて、任意の変数を自由に変更することができます。または、おそらく変数を変更する必要がないようにコードをリファクタリングします。ただし、完全性を期して、エラーの意味とその回避方法について説明します。
Java 8言語仕様、 15.27.2 :
使用されているがラムダ式の中で宣言されていない局所変数、仮パラメータ、または例外パラメータは、finalまたは事実上finalとして宣言されなければならない( §4.12.4 )。使用を試みます。
基本的には、ラムダ(またはローカル/無名クラス)内からローカル変数(この場合はcalTz
)を変更することはできません。 Javaでそれを実現するには、可変オブジェクトを使用し、ラムダから(最終変数を介して)それを変更する必要があります。ここでの可変オブジェクトの一例は一つの要素の配列です。
private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
TimeZone[] result = { null };
try {
cal.getComponents().getComponents("VTIMEZONE").forEach(component -> {
...
result[0] = ...;
...
}
} catch (Exception e) {
log.warn("Unable to determine ical timezone", e);
}
return result[0];
}
この種の問題に対する一般的な回避策よりも変数を変更する必要がない場合 lambdaを使用するコード部分を抽出し、method-parameterにfinalキーワードを使用します。