WisdomSoft - for your serial experiences.

5.6 final修飾子

final 修飾子を用いると、初期化以降は変更できない変数や、オーバーライドできないメソッドを宣言できます。

5.6.1 読み取り専用フィールド

ある程度経験をつんだプログラマは、リテラルをコード内に直接記述するべきではないという習慣を身につけます。プログラムの動作にかかわる設定情報などはディスクファイルに退避させ、アルゴリズムやプログラムの内部事情に関係するリテラルは、一ヶ所で集中管理するようになります。そうすることで、柔軟性や保守性を向上させることができるのです。

リテラル情報を内部のコードや利用者に与えるには final 修飾子を指定した final フィールドを使うと便利です。final 修飾子が指定されたフィールドは読み取り専用として扱われるため、初期化子以外に外部から代入することはできなくなります。そのため、何らかの意味を持つ定数値を外部に提供する時に適します。

final 修飾子
final  フィールド名 = 初期化子

final 修飾子は、static やアクセス修飾子と組み合わせて指定することができます。通常、リテラルを保存する読み取り専用フィールドはインスタンスごとに保有する意味はないので static を指定するべきでしょう。また、習慣としてリテラルを保存する final フィールドの名前は大文字にします。

コード1
class Test {
	private final static String TITLE = "Kitty on your lap";
	public static void main(String args[]) {
		//TITLE = "あなたのひざの仔猫"; //代入不可
		System.out.println(TITLE);
	}
}
実行結果
>java Test
Kitty on your lap

コード1の Test クラスは、final フィールド TITLE を宣言しています。このフィールドは文字列リテラルで初期化され、それ以降値が変更されることはありません。このフィールドに対して代入を行うとコンパイル・エラーとなります。

リテラルで初期化する final フィールドはどのインスタンスから見ても常に同じ値となるため static を指定しましたが、インスタンス固有の値で初期化し、インスタンスごとに読み取り専用フィールドを持たせたい場合もあります。例えば、コンストラクタに与えられた値で初期化し、構造的な情報を与えることが目的のクラスなどです。このようなクラスは、値が変更されることを望みませんが、アクセッサ・メソッドを通すのは冗長です。そこで、インスタンスごとの読み取り専用フィールドを公開すれば、スマートに情報を提供できるでしょう。

final フィールドは初期化子を省略することができます。初期化子が指定されていない final フィールドをブランク final と呼びます。ブランク final のクラス変数は静的初期化子で、インスタンス変数はコンストラクタで初期化することができます。もちろん、それ以外のブロックからは値を代入することはできません。複雑な計算の結果を読み取り専用フィールドとして提供する場合は、ブランク final で宣言し、静的初期化子やコンストラクタでフィールドを初期化するのです。

ブランク final のクラス変数は静的初期化子が終了するまでに確実な代入状態にならなければならず、ブランク final のインスタンス変数はコンストラクタが終了する時点で確実な代入状態にならなければなりません。そうでなければコンパイル・エラーとなります。

インスタンスごとの読み取り専用フィールドを作成したい場合、ブランク final のインスタンス変数を宣言し、コンストラクタからこのフィールドを初期化します。ブランク final のフィールドは、コンストラクタからは値を代入することができるため、インスタンス固有の読み取り専用値を提供することができるようになります。

コード2
class Point {
	public final int x , y;
	public Point(int x , int y) {
		this.x = x;
		this.y = y;
	}
}

class Test {
	public static void main(String args[]) {
		Point pt = new Point(400 , 300);

		//pt.x += 100; //代入不可
		//pt.y = 200;  //代入不可
		System.out.println("X = " + pt.x + " : Y = " + pt.y);
	}
}
実行結果
>java Test
X = 400 : Y = 300

コード2では、2次元座標の点を表す Point クラスを作成しています。このクラスは、点の座標を x フィールドと y フィールドで提供していますが、これらのインスタンス・変数はブランク final による読み取り専用フィールドとなっています。Point クラスのコンストラクタからは値を代入することができますが、それ以外のブロックからは代入できません。

この方法は、座標やサイズ、色などの単純なデータ構造を提供する有効な手段の 1 つと考えることができるでしょう。参照型のデータは、メソッドなどに渡すと不正にデータが書き換えられる危険性があります。しかし、読み取り専用フィールドであればそのような心配はありません。この程度の単純な構造であれば、インスタンスの生成も大きな負荷にはならないので、セキュリティを重視するシステムならば、新しい座標を指すたびに新しいインスタンスを作った方が、フィールドを書き換えるよりも安全であると考えられるでしょう。

5.6.2 オーバーライドの阻止

オーバーライドは抽象化されたメソッドを実装したり、クラスの機能をサブクラスで拡張することができるすばらしい機能です。しかし、すべての開発者がオーバーライドを歓迎するとは限りません。オーバーライドを想定した抽象メソッドや、比較的抽象化されているクラスは良いですが、何らかの処理に特化した限定的なクラスでは、他者の手による不正な拡張は避けたいと考えるでしょう。

例えば、そのメソッドが呼び出されなくなってしまうことで、クラス全体が正常に動作できなくなってしまう場合、オーバーライドや隠蔽は避けるべきです。メソッドが不正にオーバーライドされることを防ぐには final 修飾子をメソッドに指定します。final 修飾子はアクセス修飾子などと併用することができますが、逆の意味を持つ abstract 修飾子と同時に指定することはできません。また、private メソッドは性質上、暗黙的に final となります。private メソッドに final 修飾子を指定することは可能ですが、意味のある行為ではありません。

コード3
class A {
	public final void show() {
		System.out.println("Kitty on your lap");
	}
}

class Test extends A {
	public static void main(String args[]) {}
	public void show() {}
}
実行結果
>javac Test.java
Test.java:9: Test の show() は A の show() をオーバーライドできません。; オーバ
ーライドされたメソッドは final です。
        public void show() {}
                    ^
エラー 1 個

コード3は A クラスでオーバーライドを拒否する final メソッド show() を宣言しています。A クラスを継承する Test クラスでは、同一のシグネチャを持つ show() メソッドを宣言し、オーバーライドしようと試みていますが、final メソッドのオーバーライドはできないので、コンパイル・エラーとなります。

メソッドがオーバーライドされる可能性を否定することによって、コンパイラがメソッドの呼び出しを最適化することも可能となります。

5.6.3 継承の禁止

final メソッドによるオーバーライドの禁止は、完成されたメソッドを不正に隠蔽されないように防ぐものでした。同様に final 修飾子をクラスに指定することによって、完成されたクラスが不正に継承されることを防ぐことができるようになります。静的メンバだけを提供する数学クラスなど、用途が限定的で拡張の余地がない場合は final クラスとして宣言すると良いでしょう。

final クラスはサブクラスが存在しないので、メソッドは常に final メソッドとなります。暗黙的に final メソッドとして宣言されますが、明示的に final 修飾子をメソッドに指定しても問題はありません。

final クラスを継承しようとすれば、コンパイル・エラーとなります。

コード4
final class A {}
class Test extends A {
	public static void main(String args[]) {}
}
実行結果
>javac Test.java
Test.java:2: final A からは継承できません。
class Test extends A {
                   ^
エラー 1 個

コード4では final 修飾子を指定した A クラスを宣言しています。Test クラスでこれを継承しようと試みていますが、final クラスは継承することができないため、コンパイル・エラーとなります。