finalキーワードで「変更できない」を宣言しよう

docs

Javaプログラミングの学習、順調に進んでますか?前に「変数」についてじっくり解説しました。変数はデータを一時的にしまっておく箱のようなもので、その中身はプログラムの途中で自由に変更できましたよね。

変数って何? データの入れ物を理解しよう | ToolDocs

でも、Javaには**「一度決めたら二度と変えたくない!」**ってときに使う、とっても便利なキーワードがあるんです。それが今回紹介するfinalキーワード。これを使うと、変数、メソッド、クラスを「変更できないもの」にできます。今回はその使い方と、「どうしてそんなものが必要なの?」って疑問に答えていきますね!


final変数:値は一度だけ設定可能!

まずは一番よく使う、変数にfinalをつける場合から見ていきましょう。

基本のきほん

変数にfinalをつけると、その変数は一度だけ値を代入できます。その後は、値を変更しようとするとエラーになっちゃいます。

Java

public class FinalVariableExample {
    public static void main(String[] args) {
        final int maxScore = 100; // maxScoreは100で固定!
        System.out.println("最高スコア: " + maxScore);

        // maxScore = 200; // ← これをやるとエラーになるよ!
        // System.out.println("変更後の最高スコア: " + maxScore);
    }
}

この例だと、maxScorefinalがついているので、100と初期化されたら、もう別の値を代入できません。

paizaで実行した結果

初期化はどこで?

final変数の初期化は、宣言と同時に行うのが一般的です。

Java

final String APP_NAME = "My Awesome App"; // 宣言と同時に初期化

でも、ちょっと変わった初期化の仕方もできます。

Java

public class LazyInitializationExample {
    final String userName; // ここではまだ初期化してない

    public LazyInitializationExample(String name) {
        this.userName = name; // コンストラクタで初期化!
    }

    public static void main(String[] args) {
        LazyInitializationExample user1 = new LazyInitializationExample("Taro");
        System.out.println("ユーザー名: " + user1.userName);

        // user1.userName = "Jiro"; // 当然エラー!
    }
}

このように、宣言時には初期化せず、コンストラクタ(オブジェクトが作られるときに呼ばれる特別なメソッド)の中で一度だけ初期化することもできます。これ、後から値が決まる場合に便利ですね。

paizaで実行した結果

定数としての利用

final変数は、プログラム中で変わらない値を表現する**「定数」として非常によく使われます。定数として使う場合は、慣習としてすべて大文字**で名前をつけ、単語の区切りはアンダースコア(_)でつなげます。

Java

public class ConstantExample {
    public static final double PI = 3.14159; // 円周率
    public static final int MAX_USERS = 1000; // 最大ユーザー数
    public static final String GREETING_MESSAGE = "Hello, Java learners!"; // 挨拶メッセージ

    public static void main(String[] args) {
        double radius = 5.0;
        double circumference = 2 * PI * radius;
        System.out.println("円周: " + circumference);

        System.out.println("最大ユーザー数: " + MAX_USERS);
        System.out.println(GREETING_MESSAGE);
    }
}

public static finalとつけているのは、プログラムのどこからでもアクセスできて、かつ変更できない定数として使いたいからですね。staticについては「クラスとオブジェクト」の記事で詳しく解説します!

paizaで実行した結果

final参照変数:参照先は変更不可、でも中身は?

ここがちょっとややこしいところ。finalをオブジェクトの参照変数につけた場合です。

Java

import java.util.ArrayList;
import java.util.List;

public class FinalObjectExample {
    public static void main(String[] args) {
        final List<String> fruits = new ArrayList<>(); // fruitsは一度だけnew ArrayList<>()を指す

        fruits.add("Apple"); // リストの中身は追加できる!
        fruits.add("Banana");
        System.out.println("現在のフルーツ: " + fruits);

        // fruits = new ArrayList<>(); // ← これはエラー!別のリストを指すことはできない
    }
}

見ての通り、fruitsという参照変数はfinalなので、一度ArrayListオブジェクトを指したら、別のArrayListオブジェクトを指すように変更はできません

でも、その参照しているArrayListオブジェクトの中身(要素)は変更できますaddで要素を追加したり、removeで削除したりは自由です。つまり、finalが守るのは「参照先の変更」であって「参照先の中身の変更」ではないんです。ちょっと混乱しやすいので、しっかり覚えておきましょう!

paizaで実行した結果


finalメソッド:オーバーライドは許さない!

次はメソッドにfinalをつける場合です。

オーバーライドって何だっけ?

前回の記事で、親クラスのメソッドを子クラスで上書きすることを「オーバーライド」って言いましたよね。覚えてますか?

Java

class Animal {
    void makeSound() {
        System.out.println("動物の鳴き声");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() { // オーバーライドしてる
        System.out.println("ワンワン!");
    }
}

finalメソッドの威力

もし、親クラスのメソッドを子クラスで絶対にオーバーライドされたくない、と開発者が考えた場合、そのメソッドにfinalをつけます。

Java

class BaseClass {
    final void importantMethod() { // このメソッドはオーバーライド禁止!
        System.out.println("これは重要な処理です。変更はできません。");
    }

    void changeableMethod() {
        System.out.println("これは変更可能なメソッドです。");
    }
}

class SubClass extends BaseClass {
    // @Override // ← これをつけようとするとエラーになる!
    // void importantMethod() {
    //     System.out.println("変更しようとしました!");
    // }

    @Override
    void changeableMethod() { // これはOK
        System.out.println("変更後の変更可能なメソッドです。");
    }
}

public class FinalMethodExample {
    public static void main(String[] args) {
        SubClass sub = new SubClass();
        sub.importantMethod(); // 呼び出しはできる
        sub.changeableMethod();
    }
}

importantMethodfinalがついているので、SubClassでそれをオーバーライドしようとするとコンパイルエラーになります。これは、そのメソッドのロジックが非常に重要で、勝手に変更されては困る場合に便利です。

たとえば、フレームワークの根幹となる処理や、セキュリティに関わる処理などにfinalが使われることがあります。

vscodeで実行した結果


finalクラス:継承はさせない!

最後にクラスにfinalをつける場合です。

継承って何だっけ?

クラスとオブジェクトの概念 – オブジェクト指向の入り口 | ToolDocs の記事で、親クラスの性質を子クラスが引き継ぐことを「継承」と説明しましたね。

Java

class Vehicle { // 親クラス
    void move() {
        System.out.println("乗り物が移動します。");
    }
}

class Car extends Vehicle { // CarはVehicleを継承
    // ...
}

finalクラスの威力

もし、そのクラスを絶対に継承させたくない、と開発者が考えた場合、そのクラスにfinalをつけます。

Java

final class SecretKeyGenerator { // このクラスは継承禁止!
    public String generateKey() {
        // 複雑なキー生成ロジック
        return "super-secret-key-123";
    }
}

// class MaliciousKeyGenerator extends SecretKeyGenerator { // ← これをやるとエラーになる!
//     // 継承してキー生成ロジックを改ざんしようとする
// }

public class FinalClassExample {
    public static void main(String[] args[] args) {
        SecretKeyGenerator generator = new SecretKeyGenerator();
        System.out.println("生成されたキー: " + generator.generateKey());
    }
}

SecretKeyGeneratorクラスにfinalがついているので、別のクラスがそれを継承しようとするとコンパイルエラーになります。これは、そのクラスの内部ロジックが非常に重要で、勝手に改変されるのを防ぎたい場合に有効です。

たとえば、Java標準ライブラリのStringクラスはfinalクラスになっています。これは、文字列操作の安全性やパフォーマンスを保証するため、勝手に継承して挙動を変えられないようにするためです。

vscodeで実行した結果(finalがついたクラスを継承しようとした所)


まとめ

お疲れ様でした!今回はJavaのfinalキーワードについて、変数、メソッド、クラスそれぞれに適用した場合の動作を、具体的な例を交えて解説しました。

  • final変数: 一度初期化したら値の変更はできません。定数としてよく使われます。オブジェクトの参照変数にfinalをつけた場合、参照先は変更できませんが、参照先のオブジェクトの中身は変更できます。
  • finalメソッド: 子クラスでオーバーライドすることを禁止します。重要なロジックの変更を防ぎたい場合に利用します。
  • finalクラス: 継承することを禁止します。クラス全体の構造やロジックが勝手に変更されるのを防ぎたい場合に利用します。

finalキーワードは、「不変性」(変更できないこと)を保証するための大切なツールです。これを使うことで、プログラムの安全性や予測可能性が高まり、バグの発生を抑えることにも繋がります。

最初は難しく感じるかもしれませんが、実際にコードを書いてみて、エラーを体験しながら少しずつ慣れていきましょう。

コメント

タイトルとURLをコピーしました