1. 序章

Java 8で導入された最も興味深い機能の1つは、事実上最終的なものです。 これにより、最終的なもののように効果的に処理および使用される変数、フィールド、およびパラメーターに対してfinal修飾子を記述しないようにすることができます。

このチュートリアルでは、この機能のの起源と、最終的なキーワードと比較したコンパイラによる処理方法について説明します。 さらに、効果的に最終変数の問題のあるユースケースに関して使用するソリューションを検討します。

2. 事実上最終的な起源

簡単に言うと、オブジェクトまたはプリミティブ値は、初期化後に値を変更しない場合、事実上最終的なものになります。 オブジェクトの場合、オブジェクトの参照を変更しないと、参照されているオブジェクトの状態に変更が生じたとしても、事実上最終的なものになります。

導入前は、匿名クラスで非最終ローカル変数を使用できませんでした。 匿名クラス、内部クラス、ラムダ式内で複数の値が割り当てられている変数を使用することはできません。 この機能の導入により、事実上最終的な変数に final 修飾子を使用する必要がなくなり、いくつかのキーストロークを節約できます。

匿名クラスは内部クラスであり、 JLS 8.1.3で指定されているように、非最終変数または非有効最終変数にアクセスしたり、それらを囲んでいるスコープで変更したりすることはできません。同じ制限が適用されますアクセス権があると同時実行の問題が発生する可能性があるため、ラムダ式に変換します。

3. ファイナルvs事実上ファイナル

final変数が事実上finalであるかどうかを理解する最も簡単な方法は、finalキーワードを削除するとコードをコンパイルして実行できるかどうかを考えることです。

@FunctionalInterface
public interface FunctionalInterface {
    void testEffectivelyFinal();
    default void test() {
        int effectivelyFinalInt = 10;
        FunctionalInterface functionalInterface 
            = () -> System.out.println("Value of effectively variable is : " + effectivelyFinalInt);
    }
}

値を再割り当てするか、上記の事実上最終変数を変更すると、発生場所に関係なくコードが無効になります。

3.1. コンパイラの扱い

JLS 4.12.4 は、コンパイル時エラーを発生させずにメソッドパラメーターまたはローカル変数から final 修飾子を削除すると、事実上最終的なものになると述べています。 さらに、有効なプログラムの変数の宣言に final キーワードを追加すると、事実上finalになります。

Javaコンパイラは、 final 変数の場合とは異なり、効果的にfinal変数の追加の最適化を行いません。

2つのfinalString 変数を宣言するが、それらを連結にのみ使用する簡単な例を考えてみましょう。

public static void main(String[] args) {
    final String hello = "hello";
    final String world = "world";
    String test = hello + " " + world;
    System.out.println(test);
}

コンパイラは、上記のmainメソッドで実行されるコードを次のように変更します。

public static void main(String[] var0) {
    String var1 = "hello world";
    System.out.println(var1);
}

一方、最終修飾子を削除すると、変数は事実上最終と見なされますが、コンパイラはそれらを削除しません。これらは連結にのみ使用されるためです。

4. 原子修飾

一般に、ラムダ式と匿名クラスで使用される変数を変更することはお勧めできません。 これらの変数がメソッドブロック内でどのように使用されるかを知ることはできません。 それらを変更すると、マルチスレッド環境で予期しない結果が生じる可能性があります。

ラムダ式を使用する場合のベストプラクティスを説明するチュートリアルと、一般的なアンチパターンを変更する場合のを説明するチュートリアルがすでにあります。 しかし、アトミック性によってスレッドセーフを実現するような場合に変数を変更できる代替アプローチがあります。

パッケージjava.util.concurrent.atomicは、AtomicReferenceAtomicIntegerなどのクラスを提供します。 それらを使用して、ラムダ式内の変数をアトミックに変更できます。

public static void main(String[] args) {
    AtomicInteger effectivelyFinalInt = new AtomicInteger(10);
    FunctionalInterface functionalInterface = effectivelyFinalInt::incrementAndGet;
}

5. 結論

このチュートリアルでは、finalと事実上final変数の最も顕著な違いについて学びました。 さらに、ラムダ関数内の変数を変更できる安全な代替手段を提供しました。