web-dev-qa-db-ja.com

ラムダ式で使用される変数は最終的または実質的に最終的であるべきです

ラムダ式で使用される変数は最終的または実質的に最終的であるべきです

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;
}
80
user3610470

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を設定しないのですか?

これらのヒントもあなたが改善するのに役立つことを願っています。

49

他の答えは要件を証明しますが、彼らは理由が説明されていませんなぜ要件が存在するのか

JLSはなぜ §15.27.2 に言及している:

実質的に最終的な変数を制限すると、動的に変化するローカル変数へのアクセスが禁止されます。これを取り込むと、並行性の問題が生じる可能性があります。

バグのリスクを減らすために、彼らは捕獲された変数が決して変異しないようにすることを決めました。

39
Vince Emigh

ラムダから、あなたは最終的でない何かへの参照を得ることができません。変数を保持するには、ラムダの外側から最後のラッパーを宣言する必要があります。

このラッパーとして最後の '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();
}   
36
DMozzy

Java 8には、「実質的に最終的な」変数という新しい概念があります。これは、初期化後に値が決して変化しない非最終ローカル変数が「実質的に最終的」と呼ばれることを意味します。

この概念が導入されたのは、Java 8より前では、anonymous classで非最終ローカル変数を使用できなかったためです。 anonymous classのローカル変数にアクセスしたい場合は、それを最終的なものにする必要があります。

lambdaが導入されたとき、この制限は緩和されました。そのため、ローカル変数finalをラムダとして初期化した後に変更しない場合は、それを匿名クラスにする必要があります。

Java 8は、開発者がfinalを使うたびにローカル変数lambdaを宣言するという苦労を認識し、この概念を導入し、ローカル変数をfinalにする必要がなくなりました。 anonymous classのルールがまだ変更されていないのであれば、finalを使用するたびにlambdasキーワードを記述する必要はありません。

私は良い説明を見つけました ここ

30
Dinesh Arora

あなたの例では、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];
}
6

この種の問題に対する一般的な回避策よりも変数を変更する必要がない場合 lambdaを使用するコード部分を抽出し、method-parameterにfinalキーワードを使用します。

0
robie2011