JavaでFinal修飾子を使いこなすたった15のステップ

JavaでFinal修飾子を効果的に使う方法Java
この記事は約20分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

あなたがプログラミングに初めて挑戦するとき、多くのキーワードや概念に出会うでしょう。

Javaでは特に、変数やメソッド、クラスにいくつもの修飾子を使うことがあります。

その中でも「Final修飾子」は非常に便利で、多くの場面で使用されます。

この記事ではFinal修飾子がどれほど便利であり、どのように適切に使えるかを初心者向けにわかりやすく解説します。

また、15個の詳細なサンプルコードも用意していますので、実際に手を動かしながら学ぶことができます。

●Final修飾子とは

Final修飾子はJavaの基本的な修飾子の一つであり、それが適用された変数、メソッド、またはクラスは「変更不可能」または「継承不可能」となります。

○Javaにおける修飾子の一般的な使い方

Javaでは、修飾子を使って変数やメソッド、クラスに特定の性質を付与します。

たとえば、「public」、「private」、「protected」などがありますが、これらはアクセス修飾子と呼ばれ、その名前の通りアクセス範囲を定義します。

Final修飾子もこの修飾子の一つです。

○Final修飾子の基本的な概念

Final修飾子は「最終的な」という意味を持ち、適用された要素が最終状態であることを意味します。

具体的には次のような性質を持ちます。

  • Final変数:一度代入された値は変更できない。
  • Finalメソッド:オーバーライド(再定義)することができない。
  • Finalクラス:継承することができない。

このような特性によって、プログラムの安全性を高めることができるのがFinal修飾子の特長です。

●Final修飾子の具体的な使い方

さて、Final修飾子の基本的な概念について理解したところで、次に具体的な使い方を見ていきましょう。

初心者にとっても簡単に理解できるように、3つの主要な用途に分けて説明します。

○サンプルコード1:Final変数の定義

Final修飾子を変数に使用すると、その変数は一度初期化されると再代入することができなくなります。

public class FinalVariableExample {
    public static void main(String[] args) {
        final int finalVariable = 10;  // Final変数の定義と初期化

        // finalVariable = 20;  // コンパイルエラー。再代入できない
    }
}

この例ではfinal int finalVariable = 10;でFinal変数を定義しています。

この状態で再代入しようとするとコンパイルエラーが発生します。

したがって、このコードはそのまま実行するとエラーなく終了しますが、コメントを外して再代入を試みるとコンパイルエラーになります。

○サンプルコード2:Finalメソッドの定義

Final修飾子をメソッドに使用する場合、そのメソッドは子クラスでオーバーライドできなくなります。

public class FinalMethodExample {
    public final void showFinalMessage() {
        System.out.println("This is a final method.");
    }
}

class ChildClass extends FinalMethodExample {
    // public void showFinalMessage() {}  // コンパイルエラー。オーバーライド不可
}

このコードはFinalMethodExampleクラスにshowFinalMessageというFinalメソッドを定義しています。

したがって、ChildClassでこのメソッドをオーバーライドしようとするとコンパイルエラーが発生します。

そのため、このコードもエラーなく実行終了しますが、コメントを外してオーバーライドを試みるとコンパイルエラーになります。

○サンプルコード3:Finalクラスの定義

Final修飾子をクラスに使用すると、そのクラスは他のクラスに継承できなくなります。

final public class FinalClassExample {
    public void showMessage() {
        System.out.println("This is a final class.");
    }
}

// class ChildClass extends FinalClassExample {}  // コンパイルエラー。継承不可

このコードでは、FinalClassExampleというFinalクラスを定義しています。

このクラスを継承しようとすると、コンパイルエラーが発生します。

したがって、このコードもそのままではエラーなく実行終了しますが、コメントを外して継承を試みるとコンパイルエラーになります。

●応用:Final修飾子を使った効率的なコーディング

Final修飾子の基礎的な使い方が分かったところで、次に応用的な使い方について解説します。Final修飾子は単に変数やメソッドを変更不可にするだけではありません。

設計上のベストプラクティスや安全なコーディングにも貢献する機能があります。

○サンプルコード4:Finalとイミュータブルオブジェクト

イミュータブルオブジェクトとは、一度生成された後はその状態が変わらないオブジェクトです。

Final修飾子をフィールドに使用することでイミュータブルなクラスを作成できます。

public final class ImmutablePerson {
    private final String name;
    private final int age;

    public ImmutablePerson(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 名前と年齢を取得するゲッターメソッド
    public String getName() { return name; }
    public int getAge() { return age; }
}

このコードによって生成されるImmutablePersonオブジェクトは、生成後に状態が変わることがありません。

そのため、マルチスレッド環境で安全に使用できます。

○サンプルコード5:Finalを使ったシングルトンパターン

シングルトンパターンとは、あるクラスが1つしかインスタンスを持たないようにするデザインパターンです。

Final修飾子を使用してシングルトンを実装することもできます。

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

このコードで生成されるSingletonクラスのインスタンスは、プログラム全体で1つしか存在しないことが保証されます。

これにより、リソースを効率的に使用できます。

○サンプルコード6:Finalと継承の制限

Final修飾子をクラスに使用すると、そのクラスは継承できなくなります。

これは、そのクラスが特定の動作しか許されないようにする際に有用です。

public final class NonInheritable {
    public void doSomething() {
        System.out.println("Doing something.");
    }
}

// class ChildClass extends NonInheritable {}  // コンパイルエラー

このNonInheritableクラスは継承できません。そのため、クラスの設計者が意図しないオーバーライドを防ぐことができます。

●Final修飾子の注意点

Final修飾子はJavaプログラミングで非常に便利な機能であり、変数やメソッド、クラスにおいてその値や動作を変更できなくする役目があります。

しかしその使用には注意が必要です。

ここでは、Final修飾子を使用する際のいくつかの注意点を取り上げます。

○変更不能な状態とは何か

Final修飾子を変数に適用すると、その変数は再代入できなくなります。

しかし、参照型の変数(オブジェクト)の場合、オブジェクトの内部の状態が変わらないわけではありません。

例えば、次のArrayListの例を見てみましょう。

final List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");

このコードでは、listは再代入はできないものの、その内容は変更できます。

そのため、Final修飾子を適用しても、オブジェクトの内部の状態が変わる可能性があることを理解する必要があります。

○サンプルコード7:Final修飾子の落とし穴

Final修飾子の使用には、一見してわからない落とし穴がいくつか存在します。

public class FinalPitfall {
    private final Map<String, Integer> scores;

    public FinalPitfall(Map<String, Integer> inputScores) {
        this.scores = inputScores;
    }

    public Map<String, Integer> getScores() {
        return scores;
    }
}

このコードではscoresはFinal修飾子が付与されていますが、外部からgetScoresメソッドを使用して取得したMapに変更を加えることができます。

FinalPitfall obj = new FinalPitfall(new HashMap<>());
obj.getScores().put("Alice", 90);

このような不具合を防ぐためには、getScoresメソッドで返す前にMapのコピーを作成する、またはCollections.unmodifiableMapを使用して変更不能なMapを返すことで安全を確保できます。

●Final修飾子の対処法

Final修飾子が持つ便利な特性に反して、いくつかの落とし穴や注意点が存在します。

ここでは、それらの問題をどのように解決するかに焦点を当て、具体的な対処法をご紹介します。

○サンプルコード8:Final修飾子の注意点に対処する方法

前のセクションで紹介したMapの例に続いて、finalが適用されたコレクションが外部から変更される問題に対処する方法を見ていきます。

import java.util.Collections;
import java.util.Map;
import java.util.HashMap;

public class FinalSolution {
    private final Map<String, Integer> scores;

    public FinalSolution(Map<String, Integer> inputScores) {
        // 元のMapをコピー
        this.scores = new HashMap<>(inputScores);
    }

    // 変更不能なMapを返す
    public Map<String, Integer> getScores() {
        return Collections.unmodifiableMap(scores);
    }
}

このコードではコンストラクタでinputScoresをコピーしてscoresに格納しています。

そして、getScoresメソッドでCollections.unmodifiableMapを用いて変更不能なMapを返しています。

このようにすることで、外部からscoresが変更されることはありません。

また、finalが適用されているため、scores自体の再代入も不可能です。

コードを実行すると、getScoresメソッドから取得したMapに対して変更を試みても、UnsupportedOperationExceptionがスローされるため、Mapの内容が変更されることはありません。

FinalSolution obj = new FinalSolution(new HashMap<>());
try {
    obj.getScores().put("Alice", 90);
} catch (UnsupportedOperationException e) {
    System.out.println("Mapの変更は許可されていません");
}

実行すると、”Mapの変更は許可されていません”というメッセージがコンソールに出力されます。

●Final修飾子のカスタマイズと拡張

JavaにおけるFinal修飾子は、単なる修飾子以上の機能を果たしています。

特にライブラリの開発や高度なプログラミングテクニックにおいて、この修飾子がどのように利用されるのかを深掘りしていきます。

○サンプルコード9:ライブラリとFinal修飾子

ライブラリを作成する際、Final修飾子はAPIの安定性を保つ役割を果たします。

下記のコードでは、MyLibraryクラス内でFinal修飾子を使用している部分があります。

// ライブラリの一例
public class MyLibrary {
    public final void execute() {
        initialize();
        run();
    }

    private void initialize() {
        // 初期化処理
    }

    private void run() {
        // 実行処理
    }
}

このコードではexecuteメソッドにfinal修飾子を付けています。

これにより、このメソッドはオーバーライドできません。

つまり、ライブラリの使用者がこのメソッドを誤ってオーバーライドすることによる予期せぬエラーを防ぐことができます。

このライブラリを使用して何らかの処理を実行する場合、executeメソッドが予期せぬ方法でオーバーライドされることはありません。

その結果、ライブラリの安定性が保たれます。

○サンプルコード10:Final修飾子とリフレクション

JavaのリフレクションAPIを使用すると、final修飾子が付いている場合でも内部状態を変更することができます。

これはセキュリティ上問題となる場合があります。

import java.lang.reflect.Field;

public class FinalReflection {
    public static void main(String[] args) {
        final String finalString = "Hello";

        try {
            Field field = String.class.getDeclaredField("value");
            field.setAccessible(true);
            field.set(finalString, "World".toCharArray());
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println(finalString); // 出力結果は "World"
    }
}

このコードでは、リフレクションAPIを使用してfinalStringの値を”World”に変更しています。

通常、final修飾子が付けられた変数は不変であるはずですが、この方法を用いるとその不変性を破ることができます。

コードを実行すると、出力結果は”World”となります。

つまり、finalが付いているにも関わらず、その値が変更されてしまっています。

Final修飾子の使用は多くの利点をもたらしますが、リフレクションなどの高度な機能を使うとその制約を破る可能性もあります。

そのため、Final修飾子を使用する際には、そのような側面も考慮する必要があります。

●Final修飾子のベストプラクティス

JavaでFinal修飾子をうまく利用する方法は数多く存在しますが、その中でも特に効果的な手法をいくつかご紹介します。

今回解説する内容は、プログラムの可読性を高めたり、保守性を向上させる目的でFinal修飾子を使用する場合のベストプラクティスです。

○サンプルコード11:Final修飾子の効果的な使い方

初めに、Final修飾子を用いてローカル変数や引数に一度だけ値が設定され、それ以後変更されないことを明示する方法を見てみましょう。

public void calculate(int a, final int b) {
    a = a + 10;  // aはfinal修飾子がついていないため、値を変更できる
    // b = b + 10;  // コンパイルエラー。bにはfinal修飾子がついているので値は変更できない
}

このコードは、calculateメソッドの中でabという二つの整数型引数を受け取ります。

bにはFinal修飾子がついており、このことからこの変数はメソッド内で変更されないことが明示されています。

これはコードを読む際に非常に役立つ情報であり、保守性と可読性を高めます。

○サンプルコード12:Final修飾子を活用したデザインパターン

次に、Final修飾子を活用した一般的なデザインパターンの一例、すなわち「不変オブジェクト(Immutable Object)」について説明します。

public final class ImmutablePerson {
    private final String name;
    private final int age;

    public ImmutablePerson(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

このコードでは、ImmutablePersonクラスはFinal修飾子を使って不変にされています。

nameageフィールドもfinalであり、コンストラクタで一度設定された後は変更できません。

不変オブジェクトはスレッドセーフであるため、多くの場合で利用されます。

このコードを実行した場合、新しくImmutablePersonオブジェクトを作成すると、その後そのオブジェクトのnameageは変更できなくなります。

それによって、このオブジェクトが参照される全ての箇所で一貫性が保たれるため、バグの発生リスクが減少します。

○サンプルコード13:Finalとパフォーマンス

Javaのパフォーマンスにおいて、Final修飾子がどのような影響を及ぼすかについて議論があります。

一部の開発者は、Final修飾子がJVM(Java Virtual Machine)による最適化を助けると考えています。

しかし、これは一概に言えるわけではありません。

ここでは、Final修飾子がパフォーマンスに与える影響についてサンプルコードを交えて解説します。

public class PerformanceTest {

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();

        final StringBuilder sbFinal = new StringBuilder();
        StringBuilder sbNonFinal = new StringBuilder();

        for (int i = 0; i < 1000000; i++) {
            sbFinal.append(i);
            sbNonFinal.append(i);
        }

        long endTime = System.currentTimeMillis();

        System.out.println("経過時間:" + (endTime - startTime) + "ms");
    }
}

このコードでは、StringBuilderのインスタンスをfinalと非finalでそれぞれ1つずつ作成し、1から1,000,000までの数字を連結しています。

その後、経過時間をミリ秒で出力しています。

このコードを実行すると、どちらもほぼ同じ時間で処理が完了することが多いです。

Javaでは、final修飾子は主にコードの安全性や可読性に寄与するものであり、パフォーマンスにはほとんど影響を与えないと考えられています。

○サンプルコード14:Final修飾子と多スレッド環境

Final修飾子は多スレッド環境においても重要な役割を果たします。

特に、不変オブジェクトを作成する際には必須です。

不変オブジェクトは、一度作成されるとその後は変更されないオブジェクトであり、多スレッド環境でのプログラミングにおいて非常に便利です。

public final class ImmutableData {
    private final int value;

    public ImmutableData(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

このコードでは、ImmutableDataというクラスを定義しています。

このクラスはfinal修飾子によって継承が禁止され、内部のvalueフィールドもfinal修飾子がついているために変更不可能です。

このような不変オブジェクトは、多スレッド環境で共有される場合でも、その状態が変わることがないため安全です。

このコードを実行すると、新しくImmutableDataオブジェクトを作成しても、その後はそのオブジェクトのvalueは変更できません。

この性質が多スレッド環境での安全性に貢献しています。

○サンプルコード15:Final修飾子とストリームAPI

ストリームAPIはJava 8から導入された強力な機能であり、データの変換や操作を効率的に行えます。

ここでは、Final修飾子がストリームAPIとどのように連携するかを見ていきます。

import java.util.stream.Collectors;
import java.util.List;
import java.util.Arrays;

public class FinalWithStreamAPI {

    public static void main(String[] args) {
        final List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        List<Integer> doubledNumbers = numbers.stream()
            .map(n -> n * 2)
            .collect(Collectors.toList());

        System.out.println(doubledNumbers);
    }
}

このコードでは、最初にfinal修飾子を使って整数型のリストnumbersを作成しています。

その後、このリストをストリームAPIで処理し、各要素を2倍にして新たなリストdoubledNumbersに格納しています。

最後にそのリストを出力しています。

このコードを実行すると、コンソールには[2, 4, 6, 8, 10]と出力されます。

この例で重要な点は、final修飾子を使ってnumbersが変更不可能であることです。

まとめ

この記事では、Final修飾子の基本的な概念から具体的な使い方、そしてそのベストプラクティスについて詳細に解説しました。

JavaプログラミングにおけるFinal修飾子の適切な活用方法を理解することは、コードの品質を向上させるだけでなく、より効率的なプログラミングを可能にします。

Final修飾子は、可読性、安全性、そしてコードの品質全般に寄与する重要な要素です。

しかし、その使い方を誤ると逆にコードの管理が難しくなることもありますので、そのバランスをしっかりと考えながら使用することが重要です。

この記事が、Final修飾子をより深く理解し、日々のコーディングに生かす一助となれば幸いです。