final クラス
サブクラスを定義することが望ましくない、あるいは必要ないクラスは、final クラスとして宣言することができます。final クラスが extends 句に出てくると、コンパイルエラーになります。したがって、一切サブクラスを定義することができなくなります。結果として、final クラスのメソッドはオーバーライドされることもありません。
final と abstract を同時に宣言することもコンパイルエラーになります。abstract はクラス定義が完了していないことを意味していますから、サブクラスが定義できないと、クラス定義が完了できません。このような宣言は意味を為さないため、コンパイルエラーとしています。
final メソッド
サブクラスでオーバーライドされたくないメソッドは final メソッドとして宣言することができます。オーバーライドしようとするとコンパイルエラーになります。
暗黙の final メソッド
private メソッドはオーバーライドできませんので、暗黙的に final メソッドです。
また、final クラスのメソッドも同様に、自動的に final メソッドとなります。
インライン化
実行時、仮想マシンによるネイティブコードへの翻訳や最適化の際に、final メソッドはインライン化されることがあります。暗黙の final メソッドも対象です。
インライン化とは、メソッドの呼び出しの代わりに、メソッドの本体のコードを直接呼び出し元の位置に埋め込むことです。インライン化の際には、メソッド呼び出しのセマンティクスは維持されます。例えば、以下のようなことが考慮されます。
- インスタンスメソッドを呼び出すターゲットが null の場合、インライン化されていても NullPointerException が発生する
- 実引数の評価順がメソッド呼び出しのときと変わらないように処理する
以下の例を見てください。
add1() は public な final メソッドです。final メソッドですが、public なので別のクラスから呼び出されるため、呼び出し元と呼び出し先のクラスが別々にコンパイルされることまで考慮する必要があります。そうなると、コンパイル時のインライン化ができません。
add2() は private メソッドなので、暗黙の final メソッドです。private なので他クラスから呼び出されることを考慮する必要はあまりありません (リフレクションはありますが)。そのため、コンパイル時にインライン化してもいいように思えます。
add3() は private static メソッドで、やはりコンパイル時にインライン化しても良さそうです。
public class FinalMethod { public final int add1(final int x, final int y) { return x + y; } private int add2(final int x, final int y) { return x + y; } private static final int add3(final int x, final int y) { return x + y; } public static final void main(final String[] args) { FinalMethod f = new FinalMethod(); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { int p = f.add1(i, j); int q = f.add2(i, j); int r = FinalMethod.add3(i, j); System.out.println("p=" + p + ", q=" + q + ", r=" + r); } } } }
ところが、実際にコンパイル後のバイトコードを確認してみると、いずれもインライン化はされていません。以下に抜粋します。
18 aload_1 [f] 19 iload_2 [i] 20 iload_3 [j] 21 invokevirtual FinalMethod.add1(int, int) : int [24] 24 istore 4 [p]
26 aload_1 [f] 27 iload_2 [i] 28 iload_3 [j] 29 invokevirtual FinalMethod.add2(int, int) : int [26] 32 istore 5 [q]
34 iload_2 [i] 35 iload_3 [j] 36 invokestatic FinalMethod.add3(int, int) : int [28] 39 istore 6 [r]
このように、コンパイル時はインライン化されません。ただし、実行時には行われている可能性があります。