JAVAを勉強している者です。説明が下手ですいませんが、「匿名クラス」って一体なんなんでしょうか?ネットや参考書みても「その場で使い捨てるクラス」と書いてあるんですが、そんな機能なんかメリットあるんですか?
私はまだ勉強不足だと思いますが、「匿名クラス」ってなんのメリットがあるのか、いまいちわかりません。
わかりやすいような例や例えで教えてくれませんか?
できれば身近な例を挙げて教えてくれませんか?
よろしくお願いします。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

回答5件
0
少々難しいかも知れません。しかも長い。
###イントロダクション - Javaは制限だらけ
プログラムは、端的に言うと、命令と言う手続きの集合です。そして、Javaで何らかの手続きの集合は全てメソッドです。手続きとしてなんらかの処理を行いたい場合は、必ずメソッドを定義する必要があり、メソッド内でしか処理(手続きの内容)を書くこと(実装)はできません。
また、Javaでは、実装がある(手続きが書かれているということ)メソッドはクラス内でしか定義することができません(※1)。つまり、必ずクラスを定義しないと、実装があるメソッドを定義できないと言うことです。もし、何らかの任意の処理を行いたい場合は、どこかでメソッドを定義し、また、そのメソッドが定義されるクラスが必要になります。
※1 Java8からインターフェースでもstaticメソッドとdefaultメソッドでのみ内容を実装できるようになりました。しかし、インターフェースに結びつけられ、インターフェースを定義しないと実装があるメソッドを定義できないという点では、クラスと同じです。
もう一つ、Javaで重要なことがあります。Javaではメソッドが第一級オブジェクトでは無いと言うことです。第一級オブジェクトというのは聞いたことが無いかも知れませんが、Javaではプリミティブ値や他の通常のオブジェクトが第一級オブジェクトにあたります。第一級オブジェクトは、引数の一つとして他のメソッドに渡したり、変数に代入したりすることができるもので、普通のデータとして扱えるものと思って貰うとわかると思います。メソッドは、そんなこと、つまり、引数として渡したり、変数に代入するなどと言うことはできません。
ここまではいいでしょうか?まとめると、Javaという言語には次の制限があります。(※2)
- 全ての処理は、クラスの中のメソッドとして書く必要がある。
- メソッドは普通にデータ(プリミティブ値やオブジェクト)のように扱えない。
※2 これらはJavaの制限であって、他の言語ではそうではありません。例えば似たような名前のJavaScriptでは、逆に全てが関数と呼ばれるものものであり(メソッドもありますが、メソッドは関数の一種に過ぎません)、関数は第一級オブジェクトです。
このような制限があっても、困ることはあまりありません。しかし、処理自体を引数として渡したいと言った場合に、この制限はかなり厳しいものになります。そのときに役に立つのが匿名クラスです。そんなことがあるのか?どれだけ役に立つのかを見に行ってきましょう。
###任意の処理を指定する方法 - クラスを作るしか無いのか?
次のようなプログラムを考えてみました。(※3)
Java
1import java.util.ArrayList; 2import java.util.List; 3public class Test { 4 public static class Person { 5 private String name; 6 private int age; 7 public Person(String name, int age) { 8 this.name = name; 9 this.age = age; 10 } 11 public String getName() { return this.name; } 12 public int getAge() { return this.age; } 13 } 14 public static void main(String[] args) { 15 List<Person> list = new ArrayList<>(); 16 list.add(new Person("太郎", 18)); 17 list.add(new Person("花子", 17)); 18 list.add(new Person("権太", 20)); 19 for (final Person person : list) { 20 System.out.println(person.getName() + " " + person.getAge() + "歳"); 21 } 22 } 23}
※3 内部クラスを使っているのは単にソースが1ファイルで書けるようにするためですので、深い意味はありません。
人物の名前と年齢をまとめたデータをリスト化して処理するプログラムです。さて、このプログラムのlist
の内容ですが、add
を使って登録された順番になっています。出力する場合も、登録された順番で出力されます。ここで、一つ要望が出てきました。このlist
の内容について、年齢の若い順に並び替えて欲しい、と言われました。どうしましょうか?あ、諸事情により、Personは変更できない物とします。Personの実装がこれ以上肥大化するのは禁止という事らしいです。
通常、ListのソートといえばCollections.sort(List<T> list)を使います。しかし、今回はこれが使えません。なぜなら、List<T>のT(このコードではTはPersonのことです)がComparableインターフェースを実装してなければならないからです。PersonはComparableインターフェースを実装していません。PersonにComparableインターフェースを追加するというのもありますが、今回はPersonの実装が肥大化するのが禁止されているので、その方法も使えません。
ではどうするのか?というと、もう一つのsortメソッド、Collections.sort(List<T> list, Comparator<? super T> c)を使います。これはT、つまり、このコードで言うとPersonについて、Comparatorインターフェースを実装した、つまり、比較するメソッド(comapare)を実装したクラスのオブジェクトを渡し、それを使って比較しながらソートするというものです。こちらだったら、使えそうですので、早速書いてみましょう。
Java
1import java.util.ArrayList; 2import java.util.List; 3import java.util.Collections; 4import java.util.Comparator; 5public class Test { 6 public static class Person { 7 // 中略 8 } 9 public static class PersonComparatorByAge implements Comparator<Person> { 10 public int compare(Person o1, Person o2) { 11 return Integer.compare(o1.getAge(), o2.getAge()); 12 } 13 } 14 public static void main(String[] args) { 15 List<Person> list = new ArrayList<>(); 16 list.add(new Person("太郎", 18)); 17 list.add(new Person("花子", 17)); 18 list.add(new Person("権太", 20)); 19 Collections.sort(list, new PersonComparatorByAge()); 20 for (final Person person : list) { 21 System.out.println(person.getName() + " " + person.getAge() + "歳"); 22 } 23 } 24}
Comparator<Person>インターフェースを実装したPersonComaratorByAgeクラスを作って、そのオブジェクトを渡すことで無事年齢順にソートできました。
しかし、これって面倒とは思えませんか?他で再利用するのはともかく、ここでしかPersonComaratorByAgeを使わない場合はどうなんでしょう。わざわざちゃんとクラスを定義することは無駄にも思えます。それともう一つ。使うところと定義されるところがバラバラですので、Collections.sort(list, new PersonComparatorByAge());
を見てもどういう比較をしてソートするのかわかりません。もちろん、クラス名をわかりやすくしているため予想は付きますし、IDEの助けがあれば、すぐに定義部分を表示できるでしょう。でも、やっぱり、冗長です。
と言う不便さをなくすためにあるのが匿名クラスです。
###匿名クラス - 名前が無い、その場限りのクラス
早速、匿名クラスを使った例に書きかえます。
Java
1import java.util.ArrayList; 2import java.util.List; 3import java.util.Collections; 4import java.util.Comparator; 5public class Test { 6 public static class Person { 7 // 中略 8 } 9 public static void main(String[] args) { 10 List<Person> list = new ArrayList<>(); 11 list.add(new Person("太郎", 18)); 12 list.add(new Person("花子", 17)); 13 list.add(new Person("権太", 20)); 14 Collections.sort(list, new Comparator<Person>() { 15 public int compare(Person o1, Person o2) { 16 return Integer.compare(o1.getAge(), o2.getAge()); 17 } 18 }); 19 for (final Person person : list) { 20 System.out.println(person.getName() + " " + person.getAge() + "歳"); 21 } 22 } 23}
PersonComaratorByAgeクラスがなくなり、Colletions.sortの引数を渡すところで、同じ実装がそのまま書かれました。この場合ですと、使っているところで、どのようなソートをしているかの実装があるので、年齢の若い順にソートか、とすぐにわかります。余計なクラス定義も無くなりますし、クラス管理もしなくても良くなります。他でも利用とかはできなくなりますが、もし、この場所で使わなければ、特に問題はありません。
どうでしたか?便利だと思いましたか?ちょっとしたことを書くだけなら、クラス定義が不要になる分、匿名クラスは便利…なように見えます。しかし、実際は別にクラスとしてちゃんと定義して、別の場所でも利用できるようにした方がいい場合もあります。その点は一長一短なので、どちらとも言えないと言ったところでしょう。
さて、今回はこれで終わりではありません。この匿名クラスの書き方。普通にクラスを定義する場合とさほど書き込む量が変わりません。Java8からはもっと簡易な書き方が用意されるようになりました。それはラムダ式です。
###ラムダ式 - Javaは関数型プログラミングを夢見たのか?
ラムダ式とは何か?を説明するとLISPの歴史まで語ることになるので省きますが、Java以外のメジャーな言語でラムダ式が無かったのはC言語ぐらいです。Javaのラムダ式採用はかなり後発です。早速、実装を見てみましょう。
Java
1import java.util.ArrayList; 2import java.util.List; 3import java.util.Collections; 4public class Test { 5 public static class Person { 6 // 中略 7 } 8 public static void main(String[] args) { 9 List<Person> list = new ArrayList<>(); 10 list.add(new Person("太郎", 18)); 11 list.add(new Person("花子", 17)); 12 list.add(new Person("権太", 20)); 13 Collections.sort(list, 14 (o1, o2) -> Integer.compare(o1.getAge(), o2.getAge())); 15 for (final Person person : list) { 16 System.out.println(person.getName() + " " + person.getAge() + "歳"); 17 } 18 } 19}
匿名クラス部分が、(o1, o2) -> Integer.compare(o1.getAge(), o2.getAge())
という一行だけになりました。見ればわかるとおり、これは元々の実装のコアの部分です。つまり、周りの部分が全部無くなっています。さて、これはどういう事かというと、Javaのラムダ式は、このようにどんなインターフェースのオブジェクトが必要か自明の場合、ラムダ式の部分を自動的に匿名クラスと同じように扱ってくれます。つまり、これは匿名クラスを使った場合と実質同じ扱いです。
ただ、これは何でも使えるというわけではありません。実装が必要な抽象メソッドが一つだけという関数型インターフェースで無ければならないという条件があります。Comparatorインターフェースはその条件を満たす(compare()のみ実装が必要)ため、関数型インターフェースであり、ラムダ式を使用できると言うことです。
匿名クラスが必要になる場合のほとんどは、関数型インターフェースの実装であることが多いです。Java8以降しか対応する予定が無い!というのであれば、より便利になったラムダ式も使ってみてください。※4
※4 今回は説明しませんが、ラムダ式と似たような機能でメソッド参照というのがあります。変数名::メソッド名
と言う形でラムダ式と同じ所に書けるようになっています。興味があれば一度調べてみてください。
###もう一つの利点 - 制限付きクロージャー
さて、匿名クラスおよびラムダ式でかけば、とてもすっきり書けることがわかったと思います。しかし、もう一つの利点があります。それは、finalまたは実質的にfinalなローカル変数を内部で使用できるということです。
実行するときに引数"-r"をつけると年齢が高い順になるよう、プログラムを書き換えて見ましょう。
import java.util.ArrayList; import java.util.List; import java.util.Collections; public class Test { public static class Person { // 中略 } public static void main(String[] args) { final int order = (args.length < 1 || !"-r".equals(args[0])) ? 1 : -1; List<Person> list = new ArrayList<>(); list.add(new Person("太郎", 18)); list.add(new Person("花子", 17)); list.add(new Person("権太", 20)); Collections.sort( list, (o1, o2) -> Integer.compare(o1.getAge(), o2.getAge()) * order); for (final Person person : list) { System.out.println(person.getName() + " " + person.getAge() + "歳"); } } }
変数order
は"-r"がなければ1
、あれば-1
になります。そして、変数order
がラムダ式の中でも使えていることに注目です。これは匿名クラスであっても、同様に使用できます。このようにfinalなローカル変数を内部で使用することができます。普通のローカル変数以外にも仮引数であっても同様に使用可能です。このような仕組みをクロージャーといいます。クロージャーが何かを説明すると、また、LISPの歴史から始めることになりますので、ここでは割愛しておきます。
しかし、Javaのクロージャーには制限があります。それは、変数がfinal、または、実質的にfinalで無ければならないと言うことです。実質的にfinalとは、宣言時にfinalがなくても、finalが付いているものとして見なすことができるものを言います。暗黙的なfinalがついていると言った方がいいでしょう。なお、実質的にfinalなローカル変数に代入しようとすると、ラムダ式や匿名クラス内部での使用と矛盾するため、コンパイルエラーになります。(※5)
※5 このfinalまたは実質的にfinalという制限は、Java特有のもので、ほとんどの言語のクロージャーではそのような制限がありません。そのため、私は個人的に、Javaのこの機能はクロージャーでは無く、クロージャー擬きと考えています。
最後に、ラムダ式や匿名クラスを使わずに、別のクラスにする書き方だった場合はどうなるのかを示しましょう。
Java
1import java.util.ArrayList; 2import java.util.List; 3import java.util.Collections; 4import java.util.Comparator; 5public class Test { 6 public static class Person { 7 // 中略 8 } 9 public static class PersonComparatorByAge implements Comparator<Person> { 10 private int order; 11 public PersonComparatorByAge(int order) { this.order = order; } 12 public int compare(Person o1, Person o2) { 13 return Integer.compare(o1.getAge(), o2.getAge()) * this.order; 14 } 15 } 16 public static void main(String[] args) { 17 final int order = (args.length < 1 || !"-r".equals(args[0])) ? 1 : -1; 18 List<Person> list = new ArrayList<>(); 19 list.add(new Person("太郎", 18)); 20 list.add(new Person("花子", 17)); 21 list.add(new Person("権太", 20)); 22 Collections.sort(list, new PersonComparatorByAge(order)); 23 for (final Person person : list) { 24 System.out.println(person.getName() + " " + person.getAge() + "歳"); 25 } 26 } 27}
インスタンスフィールドとコンストラクタまで必要になってしまいました。できないことは無いですけど、一回限りしか使わないようなクラスにここまでするのは冗長すぎますよね。
投稿2017/01/07 13:13
編集2017/01/08 01:37総合スコア21751
0
ベストアンサー
JavaFXがタグについているのでGUIを使った話でも。
よく使うのは、ボタンをクリックしたときにどういう動作をさせるかを記述するときです。
例えばAndroidで、あるボタンをクリックしたときに画面下に文字を表示したい、というような場合、
java
1button.setOnClickListener(new View.OnClickListener(){ 2 @Override 3 public void onClick(View v){ 4 Toast.makeText(this, "クリックしたよ", Toast.LENGTH_LONG).show(); 5 } 6});
のように書きます。こうすることで、「このボタンを押すと何が起こるのか」がすぐ近くに書いてあってわかりやすいというメリットがあります。
また匿名クラスを使わない場合、別のjavaファイルにそれ用のクラスを作ることになると思います。今は1個なのでそれほど問題ではないかもしれませんが、もしこれが100とか200とかになったとしたらどうします?どれがどのボタンに結びついているのかわからなくなってきませんか?
匿名クラスの場合、使い捨てなのでそんな配慮が不要になります。
投稿2017/01/08 02:27
総合スコア20675
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

0
「使い捨て」という性質は、「影響範囲の制限」とも言えます。
プログラムを作るうえでは、どうしてもデバッグやセキュリティホール対策が必要となります。一方、「影響範囲が広い機能」の多用はこれらの作業の妨げとなります。影響範囲の広い機能が複雑に絡み合うと意図しないことが起こりやすくなるためです。
これが匿名クラスの有用性の一つです。
投稿2017/01/07 14:25
総合スコア4853
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
簡易にスレッドを作って実行したい時、下記の匿名クラスの使い方はとても便利です
java
1 new Thread (new Runnable () { 2 3 public void run () { 4 //スレッドで実行したい処理 5 } 6 }).start ();
下記ではスレッドを三つ同時に動かしていますが、上の文に単に実行したい内容を書き込んで3つ並べるだけで全てのスレッドを同時に動かせます。
java
1class AWP{ 2 3public static void main(String[] args){ 4 5 6 new Thread (new Runnable () { 7 8 public void run () { 9 for(int i=1;i<10;i++){ 10 System.out.println(i+"回目のスレッド0"); 11 try{ 12 Thread.sleep((int)(Math.random()*1000)); 13 14 }catch(Exception e){} 15 } 16 } 17 }).start (); 18 19 20 new Thread (new Runnable () { 21 22 public void run () { 23 for(int i=1;i<10;i++){ 24 System.out.println(i+"回目のスレッド1"); 25 try{ 26 Thread.sleep((int)(Math.random()*1000)); 27 }catch(Exception e){} 28 } 29 30 } 31 }).start (); 32 33 34 new Thread (new Runnable () { 35 36 public void run () { 37 for(int i=1;i<10;i++){ 38 System.out.println(i+"回目のスレッド2"); 39 try{ 40 Thread.sleep((int)(Math.random()*1000)); 41 }catch(Exception e){} 42 } 43 44 } 45 }).start (); 46 47 48 49} 50 51 52}
投稿2017/01/07 13:51

退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
InterfaceClass list = new InterfaceClass() { public Object interfaceName(int arg) { return null; } };
のように クラス宣言をせずに拡張したクラスのことを匿名クラスといいます。
投稿2017/01/07 10:19

退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

退会済みユーザー
2017/01/07 10:20 編集

あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/06/16 08:39
2020/06/16 10:09 編集