Javaの匿名クラスの考え方について


はじめに

OCJP-Goldの試験対策中に匿名クラスの理解に苦しみました。

匿名クラスはラムダ式やストリームAPIを理解するためにも大事な内容になります。

そのためここでは色々な書籍を読んで匿名クラスの理解に役立った考え方について整理します。

※匿名クラスとは何かといった内容についてはここでは触れないため、別の記事や書籍を参考にしていただければと思います。


匿名クラスを利用しない場合

まずは匿名クラスを利用せずに普通にインタフェースの実装を行い、メソッドを呼び出すコードについて確認します。


Sample1.java

 1 interface Foo {// インタフェース

2 void methodA();// 抽象メソッド
3 }
4
5 class FooImple implements Foo {// 実装クラス
6 @Override
7 public void methodA() {// 抽象メソッドのオーバーライド
8 System.out.println("methodA");
9 }
10 }
11
12 public class Sample1 {// 実行クラス
13 public static void main(String[] args) {
14
15 Foo f = new FooImple();// 実装クラスを生成して変数へ代入
16 f.methodA();// 実装クラスが代入された変数に対してメソッドの呼び出し
17
18 new FooImple().methodA();// メソッドの呼び出しだけならば変数への代入は不要
19 }
20 }


実行結果

methodA

methodA

Sample1.javaではインタフェースであるFoo、その実装クラスであるFooImple、そして実行クラスのSampole1が登場します。

特段難しい点はありませんが、ここで実装と呼び出しを行っているmethodA()メソッドが仮に、他で再利用する予定がなく、その場限りで必要であるメソッドであるとします。

そのような場合にわざわざFooインタフェースの実装クラスを定義し、利用するという2段階を行うことは手間であるという感覚が出てきます。

また、処理自体は大した処理でもないのでコード自体は短く済みますが、似たような実装を行うメソッドを多く呼び出したいという場合には、実装したいメソッドの数だけ実装クラスを定義する必要があります。

これが同じクラスファイル内であれば問題は小さいように感じますが、別のクラスファイルなどに分かれている場合などには管理することも手間になります。


匿名クラスを利用した場合

このような手間を解決する時に利用できるのが匿名クラスを用いた実装です。

匿名クラスを用いた実装にも2つの書き方があります。

それぞれコードで確認します。


匿名クラスで生成したインスタンスを変数に代入する場合


Sample2.java

 1 interface Foo {// インタフェース

2 void methodA();// 抽象メソッド
3 }
4
5 public class Sample2 {// 実行クラス
6 public static void main(String[] args) {
7 Foo f = new Foo() {// 変数fに匿名クラスで実装した結果を代入
8 @Override
9 public void methodA() {// 抽象メソッドのオーバーライド
10 System.out.println("methodA");
11 }
12 };
13 f.methodA();// 実装クラスに対してメソッドの呼び出し
14 }
15 }


実行結果

methodA


Sample2.javaではインタフェースであるFoo、そして実行クラスのSampole2が登場します。

Sample1.javaで登場したFooインタフェースの実装クラスであるFooImpleについては匿名クラスで実装することで登場しなくなります。

匿名クラスを利用することで「クラスの宣言」と「インスタンスの生成」を同時に行うことができるのです。

このサンプルだけを見てもイメージが掴めない方は上述のSample1.javaとしっかりと見比べてください。

Sample1.javaの15行目がSample2.javaの7~12行目に置き換わっています。

Foo f = new FooImple();の右辺がnew Foo()になっていることが分かります。

また、FooImpleクラスの中身(メソッド定義)がFoo f = new Foo()以下(8~10行目)にそのまま移動していることが分かるかと思います。

このようにすることで再利用する予定のない実装クラスをわざわざ別で定義することなく、実行クラス内で定義と利用の2つを行うことができます。

なおSample1.javaの18行目についてはFooImpleクラスが定義されていないと利用できないため、Sample2.javaからは削除しています。

このパターンは次で説明します。


匿名クラスで生成したインスタンスを変数に代入しない場合

また、メソッドを実行したいだけであり、実装したクラスを再利用する予定がない場合には変数に代入する必要もありません。

これは先ほど触れたSample1.javaの18行目に関係しています。


Sample3.java

 1 interface Foo {// インタフェース

2 void methodA();// 抽象メソッド
3 }
4
5 public class Sample3 {// 実行クラス
6 public static void main(String[] args) {
7 new Foo() {// 変数宣言なしで匿名クラスでの実装
8 @Override
9 public void methodA() {// 抽象メソッドのオーバーライド
10 System.out.println("methodA");
11 }
12 }.methodA();// 実装クラスに対してメソッドの呼び出し
13 }
14 }

※実行結果はSample2と同様のため省略

このサンプルだけを見てもイメージが掴めない方は上述のSample2.javaとしっかりと見比べてください。

Sample2.javaとほとんど同じですが、7、12行目が異なっています。

Foo f = new Foo()(7行目)の左辺がなくなっていることと、f.methodA();(13行目)のfがなくなっていることが分かるかと思います。

これは生成したインスタンスを変数fに代入し、その変数fに対してmethodA()を呼び出していた実装を、変数に代入せずに一時的に利用してmethodA()を呼び出しているからです。


追記(2019/1/31)

Sample1,2,3では引数と戻り値を持たないインタフェースの実装について整理しましたが、引数と戻り値を持つインタフェースの実装に関してもサンプルコードを作成しましたので追記します。

Sample1,2,3に考え方が理解できていれば特に難しい部分はないため、説明については省略します。


匿名クラスを利用しない場合


Sample4.java

 1 interface Foo {// インタフェース

2 String methodA(String str);// String型を引数に取り、String型を返す抽象メソッド
3 }
4
5 class FooImple implements Foo {// 実装クラス
6 @Override
7 public String methodA(String str) {// 抽象メソッドのオーバーライド
8 return "Hello " + str;
9 }
10 }
11
12 public class Sample4 {// 実行クラス
13 public static void main(String[] args) {
14 Foo f = new FooImple();// 実装クラスを生成して変数fに代入
15 String str = f.methodA("methodA");// メソッドの戻り値を変数strに代入
16 System.out.println(str);
17 }
18 }


実行結果

Hello methodA



匿名クラスを利用した場合


匿名クラスで生成したインスタンスを変数に代入する場合


Sample5.java

 1 interface Foo {// インタフェース

2 String methodA(String str);// String型を引数に取り、String型を返す抽象メソッド
3 }
4
5 public class Sample5 {// 実行クラス
6 public static void main(String[] args) {
7 Foo f = new Foo() {// 実装クラスを生成して変数fに代入
8 @Override
9 public String methodA(String str) {// 抽象メソッドのオーバーライド
10 return "Hello " + str;
11 }
12 };
13 String str = f.methodA("methodA");// メソッドの戻り値を変数strに代入
14 System.out.println(str);
15 }
16 }

※実行結果はSample4と同様のため省略


匿名クラスで生成したインスタンスを変数に代入しない場合


Sample6.java

 1 interface Foo {// インタフェース

2 String methodA(String str);// String型を引数に取り、String型を返す抽象メソッド
3 }
4
5 public class Sample6 {// 実行クラス
6 public static void main(String[] args) {
7 String str = new Foo() {// 実装クラスを生成するが変数に代入せず、メソッドの実行結果を変数strに代入
8 @Override
9 public String methodA(String str) {// 抽象メソッドのオーバーライド
10 return "Hello " + str;
11 }
12 }.methodA("methodA");// 生成したインスタンスに対してメソッドの呼び出し
13 System.out.println(str);
14 }
15 }

※実行結果はSample4と同様のため省略


まとめ

匿名クラスの考え方について整理しました。

大事なのは匿名クラスを利用しない場合とした場合でどのような違いがあるのかを理解することです。

そうすることで匿名クラスのメリットなどについても理解できますし、試験の対策としても有用です。

しっかりとSample1,2,3を見比べてそれぞれの違いについて理解してください。

また、以下でサンプルコードをアップしていますのでよろしければ参考にどうぞ。

追記(2019/1/31)

Sample4,5,6に関しても上記のサンプルコードに追加しました。