• ベストアンサー

インターフェイスの使い方がわかりません(初心者です)

Javaを始めたばかりの初心者です。 「やさしいJava」を買って一通り学んだのですが、 インターフェイスの使い方がよくわからず、困っています。 インターフェイスを実装することでインターフェイスが持つメソッドがすべて定義されてることが保障される、 というのはわかるんですが、そのことがどうして有用なのでしょう? また、具体的にはスレッドを扱うときにRunnableインターフェイスを実装する理由がわかりません。 Threadクラスのオブジェクトを作成するときに、 Runnableを実装したクラスのオブジェクトの変数を 引数にしないといけないんですよね? このとき、Runnableインターフェイスが 「runメソッドが定義されていなければならない」 とだけいうものだったとしたら、 Runnableを実装してなくてもrunメソッドさえ定義してあれば 実行できそうな気がするんですが・・・

質問者が選んだベストアンサー

  • ベストアンサー
回答No.4

>AnimalIF animal = (AnimalIF)dog; の意味がよくわかりません(>_<)(右辺はどういう意味なのでしょう? GogImplクラスのインスタンスであるdogオブジェクトをAnimalIFにキャストしています。 キャストとは型変換のことであり、クラス型の置き換えです。 詳しくはhttp://www.javaroad.jp/java_class14.htm をご覧になってはどうでしょうか。 ここで何故こんなことを行っているかというと、処理を動的に割り当てるためです。 dog,cat(実装クラス)ともにAnimalIF(インターフェースクラス)を実装しています。 この場合、dog,cat共にAnimalIFへの型変換が可能となります。 型変換されたAnimalIFはrunメソッドを実行できますが、その実際の処理はdogから型変換された場合とcatから型変換された場合とでそれぞれ違います。 こうして動的に実装を置き換えたりすることで汎用化を実現しています。 >Car car1 = new Car("1号車"); >Thread th1 = new Thread(car1); >th1.start(); この場合はCarクラスが実装となりdog,catに該当します。この場合th1.start()をコールした時に内部コールされるのはcar1.run()となります。 では、たとえばCarクラスの他にTrainクラスというものを作成しRunnableを実装するとします。その場合、Threadへの引数として Thread th1 = new Thread(train1); とすることが可能となり、th1.start()をコールした時に内部コールされるのはtrain1.run()となります。 つまり、th1.start()を呼び出すのですが、実際に内部コールされるのはtrain1.run()なのです。 これはThreadクラスの処理がそのように書かれているからであり、Threadクラス設計者はこのメソッドの動作を保障するためにインターフェースを使用しそこに制約を設けているのです。 もちろん制約は見えないだけでrunメソッドの実装だけではないかもしれません。 先ほどのキャストのようにrunメソッド以外にも制約が存在するかもしれません。 ですのでrunメソッドを実装すればいいのでは?という答えにはならないのです。 ただし、他の手法でもrunメソッドを実行する方法はあるので私は間違いではないと発言しました。 そこは誤解のないように。

sioois
質問者

お礼

詳しくありがとうございます。 > GogImplクラスのインスタンスであるdogオブジェクトをAnimalIFにキャストしています。 すみませんきちんと参考書を読み直したらわかりました。 > つまり、th1.start()を呼び出すのですが、実際に内部コールされるのはtrain1.run()なのです。 つまりThreadオブジェクトを作るときにコンストラクタ内で与えられた引数(今回の場合はcar1とかtrain1ですね)を受けておいて、それを使ってtrain1.run()などをコールしてるのですね。 > これはThreadクラスの処理がそのように書かれているからであり、Threadクラス設計者はこのメソッドの動作を保障するためにインターフェースを使用しそこに制約を設けているのです。 ということはRunnableを実装していればThreadクラスの動作が保障されるから、Runnableを実装していないと処理されないよう制約をかけた、ということですか。もしかしてThreadクラスの処理の中でRunnable型の変数を宣言して、そこにRunnableを実装したクラス(今回の例だとCarとかTrain)のオブジェクトを代入するような処理を行っているんでしょうか。確かにそうだとすればRunnableを実装しないとうまくいかないですね。 総合すると、この場合RunnableインターフェイスはThreadクラスの動作を保障するための統一の規格として実装させている、みたいな感じでしょうか。

その他の回答 (4)

  • UKY
  • ベストアンサー率50% (604/1207)
回答No.5

オブジェクト指向が分かってないというよりも、「型」の概念・有用性を理解しきれていないということじゃないかな。(「インタフェース」も「型」もオブジェクト指向に必須の概念ではないので) Java は「型」の概念が強い言語だから、プログラムを書くときに文法レベルで型を意識しないといけない。「型」というのは、一言で言えばデータやオブジェクトの種類のこと。 二つの整数同士を足し算したり引き算したりできるのは当たり前だけど、スレッドと文字列を掛け算するなんて意味不明だよね? あるデータやオブジェクトに対してどういう操作 (=メソッドや演算) を行うことができるかを明確にするには、そのデータやオブジェクトの種類 (=型) を知っていなければいけない。そして、「クラス」や「インタフェース」は「型」を明確に表すための概念なのだ。 例えば、「歩くことができる」という意味の Walkable というインタフェースを考えよう。 実際にオブジェクトを走らせるために、walk というメソッドがある。 interface Walkable { void walk(); } そして、人間・犬・ロボットという三つのクラスが実際に歩くことができるとしよう。 class 人間 implements Walkable { public void walk() { 略 } } class 犬 implements Walkable { public void walk() { 略 } } class ロボット implements Walkable { public void walk() { 略 } } で、実際に歩かせるプログラムを書いてみると、単純に書けば、次のようになる。 人間 human = new 人間(); human.walk(); 犬 dog = new 犬(); dog.walk(); ロボット robot = new ロボット(); robot.walk(); でも、この書き方ははっきりいって面倒だよね。一つ一つのクラス(という型)に対して、個別のプログラムを書かなくちゃいけない。それはなぜかというと、一つ一つの変数には型が決まっていて、それに対応するオブジェクトしか代入できないから。上の例で言うと、human という変数は人間という型を持っているから、人間クラスのオブジェクトしか代入できない。 じゃあ、人間であるか犬であるかとかなんていちいち気にしないから、とにかく歩くことができるものを歩かせる、というプログラムを書いてみよう。(これが「汎用設計」) Walkable walkable = どっかから歩けるものを持ってくる(); walkable.walk(); ここでの「歩けるもの」というのは、実際には人間クラスのオブジェクトかもしれないし、あるいは犬クラスのオブジェクトかもしれないし、あるいは他のクラスのオブジェクトかもしれないけど、今はどのクラスのオブジェクトなのかということはどうでもよくて、とにかく「歩ける」ということ、つまり Walkable インタフェースを実装しているということが大切なのだ。 Walkable インタフェースを実装しているから、人間であれ犬であれロボットであれ walk というメソッドが使えることが保証される。 ポイントは、Walkable というインタフェースが一つの型として作用していること。さっき言ったように、オブジェクトがどんなメソッドを持っているかを判断するには、そのオブジェクトの型を知っていなくちゃいけない。逆に言えば、型がはっきりしてさえいれば実体がどうなっているかなんてどうでもいいのだ。 つまり、コンパイラは型のことしか考えていない。コンパイラはオブジェクトの実体がどうなっているかなんてまるで気にしていない。 だから、たとえあるクラスに run というメソッドがあっても、Runnable インタフェースを実装していなければ、コンパイラは許してくれない。コンパイラが気にかけているのは Runnable を実装しているかどうかということだけだから。 じゃあ、何でコンパイラは型という形式的な概念にこだわるのか。それは、プログラムのミスをできるだけ見落とさないようにするため。 うまく動く「はず」のプログラムがコンパイルできたのに実際にはうまく動かないのよりは、ちゃんと動くことを論理的に保証できるプログラムを書いて、それをコンパイラにしっかりチェックしてもらった方がいい、ということ。 注意すべきは、「型」はコンパイル時に明確に判断できるけど、「実体」は実際にプログラムをコンパイルして実行してみないとわからないということ。そして、コンパイル前のソースコードを眺めるのは簡単だけど、実際にプログラムが動作している様子・状態を分析するのは難しいということ。 JavaScript のような、型やインタフェースの概念がなくコンパイル時の細かい文法チェックもないオブジェクト指向言語と比べてみると「型」のありがたみがもっとよく分かるかもしれない。 以上、長くてすみません。

sioois
質問者

お礼

なるほど。クラスもインターフェイスも「型」を明確にあらわすためのものだったのですか。 > 注意すべきは、「型」はコンパイル時に明確に判断できるけど、「実体」は実際にプログラムをコンパイルして実行してみないとわからないということ。そして、コンパイル前のソースコードを眺めるのは簡単だけど、実際にプログラムが動作している様子・状態を分析するのは難しいということ。 よくわかりました。型があっているかどうかの判別は簡単だから、あらかじめ実装させてそのインターフェイスの型で変数を宣言させているのですね。大規模なプログラムを組むときに有利だ、といわれる理由が少しわかった気がします。

  • ngsvx
  • ベストアンサー率49% (157/315)
回答No.3

こういう質問がでるのは、オブジェクト指向がわかっていないためです。 JAVAはオブジェクト指向で考えたものを具体的に表現をするための道具ですので、オブジェクト指向がわからないと当然使いこなすことはできません。 オブジェクト指向の勉強をしないと、今回の件が解決しても他にも疑問が多く出てくると思います。 ただ、プログラム初心者にとって、最初からオブジェクト指向を勉強するのは頭が混乱すると思いますので、まずはそういうものだと思って先へ進むのがいいかと思います。 ある程度JAVAの文法等がわかって慣れてきたときに、オブジェクト指向を学習して下さい。 恐らくそのときに疑問は解決すると思います。

sioois
質問者

お礼

確かにまだはじめたばかりでオブジェクト指向に関しては何一つわからない状態です。 オブジェクト指向に関してはもう少し慣れて、簡単なプログラムが書けるようになってからじっくり学んでみたいと思います。

回答No.2

う~ん。ちょっと的外れかもしれませんがそのときはご指摘ください。 まず以下のサンプルを見ていただけますか? CatImpl cat = new CatImpl(); DogImpl dog = new DogImpl(); AnimalIF animal = (AnimalIF)dog; animal.run(); animal = (AnimalIF)cat; animal.run(); まず、実際のrunメソッドをコールするのはdogでもcatでもなくanimalだということです。 この場合、animalとcat,dogの関係上、runメソッドを実装しているというマニフェストが必要になります。 そこでスレッドについてですが、 なぜRunnableインターフェイスを実装していなければならないかというと、 上記のようにRunnableにキャストしてrunをコールしているからなのです。 これは、汎用設計のためrunというメソッドを自由に設計できるようにしているためです。 もちろん、runメソッドさえ実装していれば良いというのは理解できますし、間違いでもありません。 ですが、コールする手法が違うのです。 この場合では、runメソッドが必要というよりもRunnableというIFを実装していることが必要ということです。

sioois
質問者

補足

すみません・・・初心者なので、 AnimalIF animal = (AnimalIF)dog; の意味がよくわかりません(>_<)(右辺はどういう意味なのでしょう?下のサンプルコードの2行目と同じ?) 「やさしいJava」のサンプルコードでは、Threadクラスのオブジェクトを作成する際、 Car car1 = new Car("1号車"); Thread th1 = new Thread(car1); th1.start(); として、スレッドを起動していました。 この場合th1がRunnableにキャストしてrunをコールしている、ということでしょうか? また、そうだとしたら何でこれが「汎用設計」だといえるんでしょう? Threadクラスのメソッドがどういう処理をしているのかわかればもう少し理解できそうなんですが・・・

回答No.1

インタフェースは、本によっては「多重継承のようなことができる」というだけしか書かれていなかったりするので、メリットが判り辛いと思いまが、実際に大規模な開発に入ると、このメリットの恩恵というのは大きくなってきます。 とあるクラスを作成する際には、必ず同じ名前のメソッドを定義させなければいけない、と設計段階で決定した場合に、どのようにプログラマに徹底させたらいいでしょうか?ポリモーフィズムを利用するには、大文字や小文字まで区別されますから、インタフェースを用いることによって徹底できることになります。 スレッド関係のプログラムも同じで、「Runnableインタフェースを実装している=runメソッドが必ずある」という意味になるので、ただ単にrunメソッドがあるという以上の意味があるんですよ。

sioois
質問者

補足

なるほど。たしかに複数人にプログラマにメソッド名を同じにするように徹底させるようなときには一括で管理できて便利だということはわかりました。 しかし、スレッド関係の話では、「必ずある」ことの補償にはなるにせよ、実際にrunが存在するならインプリメントする必要はないのではないですか? なのに実装しなかったらコンパイル時でエラーが出ます。これは文法的に間違っているということですよね?Runnableを実装しているときといないときで具体的にどうrunメソッドに影響があたえられるのでしょうか?

関連するQ&A

専門家に質問してみよう