昔からJavaのインターフェースについて腹落ちしなかった。
それにはいくつか理由があるが、ひとつはインターフェースの実装クラスで記述したメソッドがどこで実行されているのかが掴みにくいことである。
なので、今更ながら簡単なプログラムで実行箇所を突き止めてみる。
初めて書いたプログラムはJavaのアニメーションで、そのときスレッドを使ったことが頭の片隅ある。あそこからやり直してみよう(Javaが使われる多くの現場はアニメーションといったナンパなものではないようなので、あまりJava的ではないかもしれない)。
スレッドそのものについては、あまり触れません。
本記事でも参考にした以下のページの説明はわかりやすいと思いました。
→ スレッドの利用
また、インターフェースを使用する利点についても触れません。
実体のない抽象メソッド(インターフェース)が
・どこで実装され、
・どのような段取りで、
・誰(どのオブジェクト)が
・どのタイミングで
・実行する
のかを確認します。
キーワード:
抽象メソッド、Strategyパターン(Strategy、ConcreteStrategy、Context、Client)、移譲(Delegate)、アップキャスト、実行オブジェクト
サンプルプログラムの内容
まともなアニメーションとなるとインターフェースに焦点が絞れないので、コンソールで“○”を少しずつ移動させながら出力するだけのプログラム。スレッド内の無限ループのなかで繰り返し“○”の前にスペースを追加し、それを出力している。
扱うクラス(インターフェース)は4つ
クラス、インターフェース | 内容 |
---|---|
Runnableインターフェース | Javaの標準インターフェースなのでソースコードの記載無し |
VerySimpleTextAnimationクラス | Runnableインタフェース実装クラス |
VerySimpleTextAnimationAppクラス | Mainメソッドを実行する |
Threadクラス | Javaの標準インターフェースなのでソースコードの記載無し |
/*
* Mainメソッドのみ
* Runnableインターフェースを実装したクラスのインスタンスを生成
* そのインスタンスをThreadクラスのコンストラクタに指定して、
* Threadのインスタンス(thread)を生成。
* threadのインスタンスメソッドstart()を実行。
*/
public class VerySimpleTextAnimationApp {
public static void main(String args[]){
/* 別スレッドとして動作させるオブジェクトを作成 */
VerySimpleTextAnimation verySimpleTextAnimation = new VerySimpleTextAnimation();
/* 別のスレッドを作成し、スレッドを開始する */
Thread thread = new Thread(verySimpleTextAnimation);
thread.start();
}
}
/*
* Runnableインターフェースの実装クラス。
* Run()メソッドを実装。
* ○が徐々に右に移動するアニメーション(のつもり)
*/
public class VerySimpleTextAnimation implements Runnable {
String x = "○";
public void run() {
while(true) {
x = " " + x;
System.out.println(x);
try {
/* 少し待機する */
Thread.sleep(500);
} catch(InterruptedException e) {}
}
}
}
どこで実装されているか
public void run() {
while(true) {
x = " " + x;
System.out.println(x);
try {
/* 少し待機する */
Thread.sleep(500);
} catch(InterruptedException e) {}
}
}
VerySimpleTextAnimationクラスのなかです。Runnableインターフェースで定義のみしているrunメソッド()の実装をしています。これはそんなに難しくないと思う。が、しかし、実装内容で以下のところは違和感を覚える。
Thread.sleep(500);
なぜスタティックメソッドなのか。この件については後で自分なりに考えてみる。
#Thread.sleep()について
どのような段取りとなっているのか
次にVerySimpleTextAnimationAppを見ていく。まず、VerySimpleTextAnimationクラス(Runnableインタフェース実装クラス)のインスタンスを作成している。先ほどみたrunメソッドを実装したクラス。
VerySimpleTextAnimation verySimpleTextAnimation = new VerySimpleTextAnimation();
ちなみに、ここは以下のように書いても動く。
Runnable verySimpleTextAnimation = new VerySimpleTextAnimation();
このような書き方をアップキャストと呼ぶ。よく目にするが、自分にとってはこれも躓きところだったので、以前記事に書いた。
アップキャストとスーパータイプ/サブタイプ
インターフェースの取っ付きにくさは、その利用場面ではいくつかの要素が複合的に組み合わされていることが多いからだとも思う。
Thread thread = new Thread(verySimpleTextAnimation);
これは、インターフェース実装クラスのインスタンスを変数として宣言せずに、直接引数の中で new クラス名 を記述することもできる。
Thread thread = new Thread(new VerySimpleTextAnimation());
ここではThreadクラスのインスタンスを生成。その際、コンストラクタの引数に先ほど作成したverySimpleTextAnimationを入れているが、そもそもこの引数の型は何なのか。
ということで、JavaのドキュメントでThreadクラスのコンストラクタについて見てみる。
public Thread(Runnable target)
パラメータ:
target - このスレッドの起動時に呼び出される run メソッドを含むオブジェクト。
引数にRunnable型のインスタンス(target)を取るように定義されている。また、パラメータの説明にrunメソッドを含むオブジェクトとある。今回、Threadのコンストラクタの引数に入れているのは、verySimpleTextAnimation。このインスタンスが含むrunメソッドはここで実装したアニメーションまがいのもの。
このコンストラクタのように、引数にインターフェース型を定義するメソッドにはかなり違和感を覚えた。抽象メソッドを引数にする意味がわからなかったし、そもそもインターフェース自体はインスタンス化されなかったはずである。これには先述したアップキャストが絡んでいる。メソッドの引数がインターフェース型になっている場合、それはそのインターフェースの実装クラス・インスタンスが引数になることが意図されている。このことに気づくのも時間がかかった。
thread.start();
ドキュメントでstart()メソッドについて見てみる
public void start()
このスレッドの実行を開始します。Java 仮想マシンは、このスレッドの run メソッドを呼び出します。
このスレッドの run メソッドを呼び出しますと書かれている。具体的な処理内容をrunメソッドに委ねる移譲(Delegate)というやつですね。
なので、run()メソッドの説明をみる。
public void run()
このスレッドが別個の Runnable 実行オブジェクトを使用して作成された場合、その Runnable オブジェクトの run メソッドが呼び出されます。
別個の Runnable 実行オブジェクトを使用して作成された場合というのは、コンストラクタでRunnable型のtargetを指定してインスタンスを作成した場合のはず。
start()メソッドでrun()が実行される。そのrun()メソッドの内容は、コンストラクタで入ってきたインスタンスで実装された内容ということになる。
ちなみに、Runnable実行オブジェクトという呼び方は興味深い。インターフェース実装クラスのインスタンスをそう呼ぶのか。
誰(どのオブジェクト)が
VerySimpleTextAnimationAppクラスが、
どのタイミングで
Mainメソッドでthread.start()を呼んだとき。
今回の例は実行タイミングを追いやすいが、イベント処理や非同期処理等、実行オブジェクトを預かったClient(Strategyパターンの表 参照)に良きタイミングを任せるというほうがありがたみはある。
その他
Strategyパターン
クラスの構成はStrategyパターン的にみると以下のようになる。
役割 | クラス、インターフェース | 内容 |
---|---|---|
Strategy(戦略) | Runnableインターフェース | Javaの標準インターフェースなのでソースコードの記載無し |
ConcreteStrategy(具体的戦略) | VerySimpleTextAnimationクラス | Runnableインタフェース実装クラス |
Context(状況判断) | VerySimpleTextAnimationAppクラス | Mainメソッドを実行する |
Client(利用者) | Threadクラス | Javaの標準インターフェースなのでソースコードの記載無し |
参考:Strategyパターン
本記事の内容はStrategy的なことは検討してしていないためStrategyパターンと呼べるかどうかは甚だ怪しいが、カタチとしては同じなので、そういうパターンがあるということが頭の片隅にあると応用しやすくなるかもしれません。
プログラムの内容を理解するためにはコードを追っていくことになる。直線的に深く深くコードを辿っていければ追いやすいのだが、慣れないうちは、あっちにいったりこっちにいったり引き戻されたりとたらい回しにされて集中力が削がれていく。それを克服するには結局量をこなし慣れていくしかない気がするが、デザインパターンを理解することで見通しは良くなるのだとは思う(なかなか掴みにくいが)。
Thread.sleep()について
JavaのThreadクラスのSleep処理について
この質問者の違和感は分かるし、自分としてはここでの回答では解消されない。スタティックメソッドの実行命令が個々のインスタンスごとの動作を制御することに違和感があるから。結局、はっきりとした理由はわからないのだけど、おそらくこのスタティックメソッドが呼ばれているのが個々のインスタンス(ここではVerySimpleTextAnimationのインスタンス)なので、どのThreadのインスタンスに対してのスリープ命令なのかが判断できるのだろうという気はする。わかる人がいたら教えてください。
昔は、Threadがアニメーションをしているというイメージを抱いていたのだけど、改めて処理の流れを追ってみるとアニメーションを実現しているのは、runメソッドのwhileループじゃないか。Thread.sleep()でフレームレートの調整はしているけど。
インターフェースのわかりにくさについて思うこと(まとまりなし、、)
インターフェースの実装はクラス名とインターフェース名の間にimplementsと書くだけだし、実装内容は他のメソッドを書くのと変わらない。本記事では既存のインターフェース(Runnable)を使ったけど、自作するにしてもクラスが書けるのであれば、特に難しくない。つまり、書き方が難しいわけではなく、むしろ簡単だ。
インターフェースという名前に表されているように、役割としてはオブジェクトの間をつなぐもので、実態のない規約のようなものである(Swiftでは似たような役割を担うProtocolと呼ばれるオブジェクトがある)。
実体がないので、インターフェースだけ見ても何もわからない。インターフェースを理解するためにはオブジェクトの間をイメージできなくてはならず、そのためは複数のオブジェクトの組み合わせをイメージすることが求められる。慣れない状態で複雑な組み合わせを考えるのは難しいので単純なもので考えると構成はシンプルだけどありがたみがわからず(本記事にも言えることだが、、)、ただめんどくさい書き方にしか見えなかったりする。実際の利用場面では組み合わせが複雑になったり、他の要素(ジェネリクスとか)も絡んできて、焦点が絞れなくなる。
あとこれはインターフェースというよりJavaという言語の位置付けによるものだと思うが、初心者にとってはあまり魅力的でない(と言うと怒られるだろうか)、SIer的な視点での説明が多いことも萎える。
JavaにはProcessingという子孫がいるので、ひと段落したら、その視点でインターフェースについて、また記事を書いてみたい。