最終更新日 |
[関連] 「Java入門1(Application基礎)」 「Java入門2((Application応用)」
「応用1(イベント処理)」 「Java応用2(イベント処理2)」
タートルグラフィックスとフラクタル (アプレット1) マンデルブロート・カオス (アプレット2)
これまで、Java入門1,Java入門2 は、アリケーション・プログラムとして、コマンドプロンプトによるコンパイル、実行操作や整数(int)などの基本データ変数や配列,forによる繰り返し、if文による条件分岐といった基礎的な例題を中心にして、IntegerやStringクラスのメソッドも一部紹介してきた。
この入門3はアプレット・スレッド ホームページ上でグラフィックスなどを動かすことのできる、Javaのプログラムを紹介する。オブジェクトの3大特徴の一つとされる「継承」 の機能をフルに活用する。
「Java入門1」の1-4-1で紹介した Java2 SE5.0 API仕様 は、引き続き必要。
[ブラウザソフトのJava対応]
アプレットを見るには、Java対応、すなわち、JavaのVMをそなえたブラウザソフトでないといけない。マイクロソフトのIEは、Java1.1という古いシステムをサポートしていたが、XPのSP1以降、サポートをうちきった。その後は、各メーカが独自に対応しているが、統一はされていない。もし、このページのアプレット画面が見られない場合は、ブラウザソフトのJava対応 を参照されたい。
Javaのプログラミング2ーApplet,Swing(学生向けにまとめたページ(暫定版)。
[2006/12/23 追加・修正] 2-3 今日の日付、コメント2と7,8を明快に 2-4 カレンダー、途中過程の出力 3 Thread に、前文をつけて、ときに更新ボタン操作の必要性など記載 3-3 点の移動で円を描く、Thredにおけるthisキーワードの考察と代替事例 3-7に、動く同心円追加。 その他、2-2,3-3など、ずいしょにAPIがらみの記述を追加。 [2010/9/21 追加・修正] jdk1.6.0_21 をインストール API Java 6.0 3-1-2 に、円を動かすを追加。
単純に、表題のような図形を描かせて、ホームページ上に表示するものである。 (「コアJava2 Vol1.1基礎編」P343をApplet向けにアレンジした)。 [プログラム] import java.applet.Applet; import java.awt.Graphics; /* <applet code="Circle.class" width=300 height=300> </applet> */ public class Circle extends Applet { public void paint(Graphics g) { int x0 = 50; int y0 = 75; int width = 200; int height = 150; int x1 = x0 + width; int y1 = y0 + height; g.drawRect(x0, y0,width, height); g.drawLine(x0, y0,x1, y1); g.drawOval(x0, y0,width, height);//ellipse g.drawOval(25,25,250,250); //circle } } [実行結果][コメント] 1 import java.applet.Applet; java.appletパッケージに属するAppletクラスを利用すると宣言している。 同様に、 import java.awt.Graphics; java.awtパッケージのGraphicsクラスを利用する。 2 Javaはパッケージを使ってクラスをグループ化している。どのようなパッケージがあるか、 APIを参照されたし。 3 いままでのプログラムに、import文がなかったが、それはJavaの中核的な機能が含まれている java.langパッケージに属するクラスを利用していたから。java.langパッケージは 自動的にimportされる。 4 public class Circle extends Applet java.applet.Appletが、アプレットのスーパークラスで、Circleクラスは Appletクラスを拡張 しないといけない。extendsが拡張を意味している。 CircleはAppletクラスのサブクラスであるともいう。 5 public void paint(Graphics g) { アプリケーションでmainメソッドが必須だったように、アプレットでは、その出力を生成する paintメソッド が多用される。Graphicsオブジェクト を引数として受け取る。 Graphicsクラスは、java.awtパッケージに含まれている。 AWT(Abstract Window Toolkit)パッケージは、 GUI(グラフィカル ユーザ インターフェイス) を作成するものである。 6 アプレット画面の座標原点(x=0,y=0)は、左上にあって、x,y座標とも整数である。 7 drawRectは四角形を描くもので、例題では(x0,y0)に四角形の左上が配置され、幅widthと 高さheightを持つ。 8 drawLineは、座標x0, y0とx1,y1を結ぶ直線を描く(この場合はx1,y1が四角形の右下の座標 になるので対角線となる)。 9 drawOvalは、四角形に内接する楕円を描く。幅と高さが等しい場合は円になる。 10 javacでコンパイルすることはアプリケーションの場合と同じだが、コンパイルしてで きた classファイルを実行するのは、appletviewer Circle.java のように、appletviewer という Javaシステムを使う。 これをサポートするhtmlの<applet>タグがJavaに対するコメントの形ではいっている。 重要なのは、code というパラメータで、ここにclassファイルファイルを指定する。 11 またはこの部分を含むhtmlファイルを作成して([実行結果]の右側に簡単な例を示す)ダブルク リックして実行するか、appletviewer Circle.html とする。この場合は上記コメント部分は不要。
<html>
<head>
<title>円・楕円・四角形・対角線</title>
</head>
<body>
<applet code="Circle.class" width=300 height=300>
</applet>
</body>
</html>実行画面 Circle.html
[プログラム] import java.applet.Applet; import java.awt.Graphics; import java.awt.Color; /* <applet code="Koma.class" width=300 height=300> </applet> */ public class Koma extends Applet { public void paint(Graphics g) { Color[] colors= {Color.magenta, Color.blue, Color.cyan,Color.green, Color.yellow,Color.red,Color.white,Color.black}; int x0 = 20; int y0 = 20; int width = 300; int height = 300; for (int i=0; i < colors.length; i++) { width -= 30; height -= 30; g.setColor(colors[i]); g.fillOval(x0,y0,width,height); //circle } } } [実行結果] 1-3と並べて表示する。 [コメント] 1 java.awtパッケージに属するColorクラス には、Color.redなど、Colorクラス の定数が13色あり、ここでは、gray,darkGray,lightGray,orange,pinkを除く8色で、 Colorオブジェクトの配列 colors を作成している。 2 setColorメソッドで色指定して、fillOval で円内を塗りつぶしている。このとき、内接する四角形 のwidth(同じ値のheight)を順次減らしながら、forで繰り返していく。 3 同心円を描くつもりだったが、始点を同じにしたため、別の塗りつぶし図形になった。コマのよう にみえて面白いかもしれない。
[プログラム] import java.applet.*; import java.awt.*; /* <applet code="Doshinen.class" width=300 height=300> </applet> */ public class Doshinen extends Applet { public void paint(Graphics g) { Color[] colors = {Color.magenta, Color.blue, Color.cyan,Color.green, Color.yellow,Color.red,Color.white,Color.black}; Dimension d = getSize(); int xc = d.width/2; int yc = d.height/2; int radius=xc; for (int i=0; i < colors.length; i++) { radius -= 15; g.setColor(colors[i]); g.fillOval(xc-radius,yc-radius,2*radius,2*radius); } } } [実行結果][コメント] 1 APPletクラスの上位にあるjava.awtのComponentクラスのgetSize()メソッドでアプレットサイズを 取得し、xc,ycに中心座標を設定する。 2 円の半径もxcとして、それを15ずつず減らしながら、色を変えて、同心円を描いている(「独習Java」P384を参考にした)。 3 なお、同一クラスや上位クラスのメソッドを利用するときには、単にgetSize()としているように、 クラス名は不要。 4 import java.awt.* と、あるパッケージに属する個々のクラスを指定するのではなく、そのすべてを * で指定することもできる。 5 学生に、小さな黒丸しか出ないとのエラーが案外多かった。 radius -= 1 (radius = radius-1の省略した書き方) 15ずつ減らしながら繰り返しているが、このマイナス記号を落としたため。 6 繰り返しの終わりに、途中経過をみるための下記の文をいれてみた。appletviewerで見る。 System.out.println("i = " + i + " radius = " + radius + " colors" + colors[i]); コマンドプロンプトに下記のように出る(ブラウザソフトでは、ツールーJavaのコンソールで)。 Color.blackなどは16進10進表記に変えられているが、print文 はappletの中でも使える。 i = 0 radius = 135 colorsjava.awt.Color[r=255,g=0,b=255] i = 1 radius = 120 colorsjava.awt.Color[r=0,g=0,b=255] i = 2 radius = 105 colorsjava.awt.Color[r=0,g=255,b=255] i = 3 radius = 90 colorsjava.awt.Color[r=0,g=255,b=0] i = 4 radius = 75 colorsjava.awt.Color[r=255,g=255,b=0] i = 5 radius = 60 colorsjava.awt.Color[r=255,g=0,b=0] i = 6 radius = 45 colorsjava.awt.Color[r=255,g=255,b=255] i = 7 radius = 30 colorsjava.awt.Color[r=0,g=0,b=0]
ホームページの冒頭におく歓迎メッセージのつもりだが、次の章では、threadを使ってこの文字列 を動かしてみる。 [プログラム] import java.applet.Applet; import java.awt.*; /* <applet code="Welcome.class" width=680 height=30> </applet> */ public class Welcome extends Applet { //Graphicsクラス の参照変数 g public void paint(Graphics g) { //字種をSerif、太字、フォントサイズ 30ポイントを指定 Font f=new Font("Serif", Font.BOLD, 30); g.setFont(f); g.setColor(Color.red); // x=100,y=30の位置から文字列を描画する g.drawString("よくいらっしゃいました。大歓迎!!",40,25); } } [実行結果] 2-2のところにおいた。 [コメント] 1 文字列の描画はアプリケーションのときほど簡単ではない。ただし、java.awtのFontクラスを使って、 文字の種類(フォント)、スタイル(太字とか斜体文字)、サイズなどを指定してきれいに描かせること ができる。 2 それが Font f=new Font("Serif", Font.BOLD, 30); というFontクラス のインスタンスオブジェクト。 コンストラクタとnew演算子を使って作る。 fがその参照変数。 Serifはフォント名。Font.BOLDは太字、30はボイント数で文字の大きさ。 3 次の行のg.setFont(f)でFontを設定するが、次の例題のように、この2行を1行にまとめること もできる(参照変数fを省略)。
[プログラム] import java.applet.Applet; import java.awt.*; /* <applet code="PaiDemo" width=280 height=30> </applet> */ public class PaiDemo extends Applet { public void paint(Graphics g) { // 文字列の描画 g.setFont(new Font("Serif", Font.BOLD, 30)); g.setColor(Color.blue); // 数学のMathクラスの静的変数Math.PIをDoubleクラスにした後、 //Stringオブジェクトに変換 Double pd=new Double(Math.PI); String pi=pd.toString(); //String pi=new Double(Math.PI).toString(); g.drawString(pi,10,25); } } [実行結果][コメント] 1 2-1のように普通の文字列の表示にはさほど苦労しないが、あえて基本データdouble型の値 を出力するMath.PIをとりあげる。 2 文字を描くg.drawString()は、Stringオブジェクトを引数にとる。そのため、doubleの値を Doubleクラスのオブジェクトに変換する。Double pd=new Double(Math.PI); このオブジェクト参照変数pdを介して、DoubleクラスのStringオブジェクト変換のための メソッド toString()を使っている。String pi=pd.toString(); 3 アプリケーション・プログラムのSystem.out.printlnでは、こんなに苦労しなかった。 printlnメソッドが、暗黙のうちに各種の基本データ型をString型に変えてくれるおかげである。
GregorianCalendarクラスを利用して、今日の日付を取得して表示する。 (「コアJava2 Vol1.1基礎編」P147をApplet向けにアレンジした)。 [プログラム] import java.awt.*; import java.util.*; import java.applet.Applet; /* <applet code="Today.class" width=200 height=30> </applet> */ public class Today extends Applet { public void paint(Graphics g) { GregorianCalendar d = new GregorianCalendar(); Integer year = new Integer(d.get(Calendar.YEAR)); Integer month = new Integer(d.get(Calendar.MONTH)+1); Integer day = new Integer(d.get(Calendar.DAY_OF_MONTH)); Integer hour = new Integer(d.get(Calendar.HOUR_OF_DAY)); Integer minute = new Integer(d.get(Calendar.MINUTE)); int weekday= d.get(Calendar.DAY_OF_WEEK); String youbi; switch (weekday) { case 1: youbi="日"; break; case 2: youbi="月"; break; case 3: youbi="火"; break; case 4: youbi="水"; break; case 5: youbi="木"; break; case 6: youbi="金"; break; default: youbi="土"; } String s=year.toString() + "/" + month.toString() + "/" + day.toString() + "(" + youbi + ") " + hour.toString() + ":" + minute.toString(); g.setFont(new Font("Serif", Font.BOLD, 20)); setBackground(Color.yellow); g.setColor(Color.blue); g.drawString(s, 5, 25); } } [実行結果]右図 及び Javaのプログラミング1ーApplication に表示。[コメント] 1 new GregorianCalendar() で、現在の日付と時間で初期化されたカレンダーを取得。 オブジェクト参照変数をdとする。 2 getメソッド は、GregorianCalendarクラスが継承しているCalendar クラスにあり、 引数にCalendar.YEAR,Calendar.MONTH,Calendar.DATEというcalendarFieldで指定して、年、月、日を 整数で取得する(YEARなどすべて大文字であることに注意)。 3 整数 intの値をStringオブジェクト(文字列)に変換するため、new Integer(d.get(Calendar.YEAR))で、 いったん、Integerオブジェクトにする(2-2では new Double(Math.PI)1としていた)。 4 int,doubleなどの基本データに、いずれもそれらをラップする、IntegerやDoubleというクラス があり、このような変換のときに有用である(「Java入門2」7章 参照)。 5 月の取得に1を足しているのは、Calendar.MONTHが0から始まるため。 6 Calendar.DAY_OF_WEEKは、1〜7までの整数で、1が日曜になるため、switch文で、日、月.....土に変換している。 7 setBackground(Color.yellow)で、背景色を黄色にしている。なぜ、これには限定子がいらないのだろうか? setBackgroundメソッドは、Componentクラスにあるが、Appletクラスは、APIをみると下記のように上位 クラスを継承している。 java.lang.Object java.awt.Component java.awt.Container java.awt.Panel java.applet.Applet このToday.classがAppletクラスを継承、その上位のクラスのすべてのメソッドは限定子なしで使える のだろう。 8 g.setColor(Color.blue)で、文字の色をブルーで表示する。java.awt.Graphicsクラスは、Appletに継承され ていない。Graphicsクラスのオブジェクト参照変数gを限定子として使う必要がある。 [参考] 独習Java」P288には、Calendarクラスを使った例があったので、紹介しておく。 [プログラム] import java.awt.*; import java.util.*; import java.applet.Applet; /* <applet code="Date.class" width=110 height=30> </applet> */ public class Date extends Applet { public void paint(Graphics g) { Calendar cal = Calendar.getInstance(); Integer year=new Integer(cal.get(Calendar.YEAR)); Integer month = new Integer(cal.get(Calendar.MONTH)+1); Integer day = new Integer(cal.get(Calendar.DATE)); String s=year.toString() + "/" + month.toString() + "/" + day.toString(); g.setFont(new Font("Serif", Font.BOLD, 20)); setBackground(Color.yellow); g.setColor(Color.blue); g.drawString(s, 5, 25); } } [実行結果]右図 及び 今日は?[コメント] 1 Calendar.getInstance() で、現在の日付と時間で初期化されたカレンダーを取得する ところが違うくらいか?
2-3を一歩進めて、今月のカレンダーを作成する。(「コアJava2 Vol1.1基礎編」P147 のアレンジ)。 [プログラム] import java.awt.*; import java.util.*; import java.applet.Applet; /* <applet code="MyCalendar.class" width=300 height=160> </applet> */ public class MyCalendar extends Applet { public void paint(Graphics g) { GregorianCalendar d = new GregorianCalendar(); Integer year = new Integer(d.get(Calendar.YEAR)); int month = d.get(Calendar.MONTH); Integer month1 = new Integer(month+1); String s=year.toString() + "年" + month1.toString()+ "月"; g.setFont(new Font("Serif", Font.BOLD, 20)); setBackground(Color.yellow); g.setColor(Color.red); g.drawString(s, 100, 20); g.setColor(Color.black); g.drawString("日 月 火 水 木 金 土 ", 30, 50); g.setColor(Color.blue); int line=1; d.set(Calendar.DAY_OF_MONTH, 1); int weekday; weekday = d.get(Calendar.DAY_OF_WEEK); while (d.get(Calendar.MONTH) == month) { int day = d.get(Calendar.DAY_OF_MONTH); Integer day1 = new Integer(day); //System.out.println("line=" + line + " weekday=" + weekday); if (day <10) g.drawString(day1.toString(),36 * weekday, 50 + 20 * line); else g.drawString(day1.toString(),34 * weekday, 50 + 20 * line); d.add(Calendar.DAY_OF_MONTH, 1); weekday = d.get(Calendar.DAY_OF_WEEK); if (weekday == Calendar.SUNDAY) line++; } } } [実行結果] 上右の図 及び Javaのプログラミング1ーApplication にも表示。 [コメント] 1 今月の表示を年号も含めて表示した後、曜日を表示しておく。 2 d.set(Calendar.DAY_OF_MONTH, 1);で、Calendar.DAY_OF_MONTH に、1をセットする。 そして、d.get(Calendar.DAY_OF_WEEK)で、その曜日を取得する。(2005/11なら、1日は火曜日) 3 最初に、印字する1の位置は、曜日によって異なる。g.drawStringの中で、横に36ないし34ずつずらしていく。 この値は試行錯誤で決めた。 4 d.add(Calendar.DAY_OF_MONTH, 1)で、2,3,,,,,,と増やしては順次印字。 5 曜日が日曜になった時点で、縦を下げるため、line++していく。 6 カレンダー作成のための繰り返しに、whileを使うが、月が変わると終わるようにしている。 (whileについては、3-1 コメント6参照) 7 任意の年度、月のカレンダーにすることもできる。2003年4月に、ローマ などへ旅行し.ホームページにしてある。、 4/17が木曜日だったことが分っているので、2003年4月のカレンダーにしてみる。GregorianCalendarクラスのコンストラクタと new演算子からオブジェクトを作る際、下記のよ うに、年月日 を引数とするコンストラクタを使 えばよい。 GregorianCalendar d = new GregorianCalendar(2003,3,1); あるいは、GregorianCalendar d の次に、下記、 2行を追加する方法もある。 d.set(Calendar.YEAR, 2003); d.set(Calendar.MONTH, 3); 8 Integer month1 = new Integer(month+1); の前に、下記をいれる (2-2に述べたように、printlnの中では数値は自動的に文字列に変換される)。 System.out.println(d.get(Calendar.YEAR) + "年" + (month+1)+ "月"); また、繰り返し、whileの中に、下記をいれた結果を示す。 System.out.println("day=" + day + " weekday=" + weekday +" line=" + line); 2003年4月 day=1 weekday=3 line=1 day=2 weekday=4 line=1 ......... day=6 weekday=1 line=2 day=7 weekday=2 line=2 ......... day=13 weekday=1 line=3 day=14 weekday=2 line=3 ......... day=20 weekday=1 line=4 day=21 weekday=2 line=4 ......... day=27 weekday=1 line=5 day=28 weekday=2 line=5 day=29 weekday=3 line=5 day=30 weekday=4 line=5 9 これらのprintln文をいれて分かったのだが、8の出力は2度 出る。なぜ? 繰り返し、while が終了したところに、destroy() を入れてみたが、変わらなかった。2-5 バスカルの三角形(二次元配列)
Javaによる数学的例題 の2章に移す。
3 Thread
このページ冒頭、歓迎メッセージを動かしてみたが、そのようなことを実現するのがThred である。また、1-3 の同心円を 時間差をつけて描いてみたのが、3-7 動く同心円 である。
3-3,4は、Mathクラスの sin,cosメソッドを使って、その角度を少しずつ変えて円を描いている。
なお、直接、このページにはりつけたもの、3-4,5,7は、白い枠しか見えない場合もあるが、その場で更新ボタン を押していただくと、動きが見える。3-1 文字列を動かす
2-1でとりあげた、ホームページ来訪へ歓迎メッセージを動かしてみる。 [プログラム] //appletviewer StringMove.java (これでも実行できる) //appletviewer StringMove.html import java.awt.*; import java.util.*; import java.applet.Applet; /* <applet code="StringMove.class" width=750 height=50> </applet> */ public class StringMove extends Applet implements Runnable { int x=750; String s ="よくいらっしゃいました。Javaコーヒの香りはいかが!!"; public void init() { Thread t = new Thread(this); t.start(); } public void run() { try { while(x > 5) { repaint(); Thread.sleep(300); x -= 40; } } catch (InterruptedException e){ } } public void paint(Graphics g) { setBackground(Color.yellow); g.setFont(new Font("Serif", Font.BOLD, 28)); g.setColor(Color.red); g.drawString(s,x,30); } } [実行結果] このページの冒頭 及び Javaのプログラミング1ーApplication に表示。 [コメント] 1 コンピュータの中では、複数のプロセスがそれぞれ固有のアドレス空間で動作しながら、独立し たスケデュールで実行される。スレッドとは、そのプロセス内の実行の流れである。 2 一つのスレッドを制御して、作業を一定時間中断させ(sleep)、次には描画内容や位置を変えて 再開させる。アニメ的な動作である。 3 アプレットの初期化をになうinitメソッドで、new演算子とThreadクラスのコンストラクタで、 Threadクラスのインスタント・オブジェクト tを生成 し、startメソッドで、実行可能状態とし、 runメソッドを呼び出す。 4 runメソッドでは、paintメソッドに対して再描画を求めるrepaint を実行した後、300ミリ秒実行 を中断し(sleep)、最初、750に設定したx座標の位置を40ピクセル減少させては、xが5以上の間は 再描画、中断を繰り返す。 5 結局「よくいらっしゃいました。...」という歓迎メッセージが、右から左に、40ずつ減少した 位置から描かれることになり、文字が流れるようにみせることになる。 6 while はforとならんでよく使われる繰り返しのコマンド。繰り返し回数がはっきりしている場合はfor、 ある条件を満たしている間は繰り返すという場合はwhile。 7 なお、sleepメソッドで投げられる可能性があるInterruptedExceptionをcatchする必要があって、 try ....catch という例外処理の構文が使われている。3-1-2 円を動かす
[2010/9/21 追加]
Javaから離れて、3、4年たった、2010年9月に、Javaに関する依頼とを受け、閉鎖したままだった大東文化大のときのページを開いた。また、質問を受け、円が消えないというプログラムについて質問を受けた。忘れているし、もう、無理かと思いながら、トライしたら、数時間で形になった。待てという指示がないため、消えないように見えるだけ、そのためには、やはり、threadかとやってみたら、うまくいった。3-1 の応用である。 [プログラム] //cd c:\Java\JavaTry2010 //javac CicleRepaint.java //appletviewer CicleRepaint.java import java.awt.*; import java.util.*; import java.applet.Applet; /* <applet code="CicleRepaint.class" width=500 height=300> </applet> */ public class CicleRepaint extends Applet implements Runnable { int x =0; public void init() { Thread t = new Thread(this); t.start(); } public void run() { try { while(x <180) { repaint(); Thread.sleep(100); x=x+5; } } catch (InterruptedException e){ } } public void paint(Graphics g) { g.setColor(Color.red); g.drawOval(x,90,190,190); } }3-2 Clock
サンマイクロのTutorialのThreaのページにあった例そのまま、1秒ごとに時刻を表示するもの。 Implementing the Runnable Interface にあったものだが、urlが変わったか?[プログラム] import java.awt.*; import java.util.*; import java.text.DateFormat; import java.applet.Applet; /* <applet code="Clock.class" width=100 height=40> </applet> */ public class Clock extends Applet implements Runnable { private Thread clockThread = null; public void start() { if (clockThread == null) { clockThread = new Thread(this, "Clock"); clockThread.start(); } } public void run() { Thread myThread = Thread.currentThread(); while (clockThread == myThread) { repaint(); try { Thread.sleep(1000); } catch (InterruptedException e){ // the VM doesn't want us to sleep anymore, // so get back to work } } } public void paint(Graphics g) { // get the time and convert it to a date Calendar cal = Calendar.getInstance(); Date date = cal.getTime(); // format it and display it DateFormat dateFormatter = DateFormat.getTimeInstance(); g.setFont(new Font("Serif", Font.BOLD, 20)); // x=100,y=50の位置から文字列を描画する g.setColor(Color.blue); g.drawString(dateFormatter.format(date), 10, 25); } // overrides Applet's stop method, not Thread's public void stop() { clockThread = null; } }[実行結果] 上記及びJavaのプログラミング1ーApplication にも表示。 [コメント] 1 スレッドの作成と管理には2つの方法があり、ひとつはThreadクラスを拡張 して使う方法、もう 一つはRunnableインターフェイス を実装したクラスを作る方法である。 2 アプレットのように、すでにAppletクラスを拡張している場合、上位クラスは一つに限る という Javaの制限のため、Threadクラスの拡張は使えない。 3 そこで、インターフェイスを使ってクラス間の継承問題を解決するのが、巧妙な手段であり、一般 的に使われる方法である。 implements Runnable がインターフェイスの実装。 4 Runnableのメソッドはrun()しかない。しかも、拡張できないというThreadクラスのコンストラクタ から、そのインスタンスを生成している。 5 clockThread = new Thread(this, "Clock") (3-1では、引数が1ケだけのThread t = new Thread(this)) このthisキーワードについては、次の3-3で述べる。 6 paintメソッドでは、CalendarクラスのgetInstance()で現在時刻を取得するところは、2-3と同じだが、 その後は、DateやDateFormatクラスを使っている。 7 3-1,3-2とも、単一のスレッドの実行をコントロールしたが、複数のスレッドをたてて、同時に複数 の作業をさせているようにみせることもできる。下記に3通りのソート のプログラムを同時に走ら せて、実行の速さの違いをみせる、サンマイクロのTutorialのページにある面白い例があったが、 urlが変わってしまったようだ。 Lesson: Threads: Doing Two or More Tasks At Once3-3 点の移動で円を描く
[プログラム] import java.applet.*; import java.awt.*; /* <applet code="MovingCircle" width=300 height=300> </applet> */ public class MovingCircle extends Applet implements Runnable { Thread t; double radians = 0; int xc,yc; int lastx,lasty; public void init() { // ウィンドウの中心を計算する Dimension d = getSize(); xc = d.width/2; yc = d.height/2; lastx = xc + 120; lasty = yc; t = new Thread(this); t.start(); } public void run() { try { while(radians < 2*Math.PI) { // 再描画を要求する repaint(); Thread.sleep(25); // 角度ラジアンの値を増やす radians += Math.PI/180; } } catch (Exception e) { } } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { // ドットの場所を計算する int x = xc + (int)(120 * Math.cos(radians)); int y = yc - (int)(120 * Math.sin(radians)); g.setColor(Color.red); g.drawLine(lastx, lasty,x, y); lastx=x; lasty=y; } } [実行結果] 次の3−4の左側の図に同じ [コメント] 1 x,y座標にcosとsinを使い、その角度を次第に増やして半径120の円を描かせる。 2 repaintメソッドから、paintメソッドを呼び出すと、前の描画を背景色で塗りつぶしてしまうので、 それをさせないように、updateメソッドを使う。 3 g.drawLineで直前の座標とを直線で結び、近似的に円を描いているようにみせる。 4 initメソッドの中のxcやycをpaintにいれては、g.drawLineが無効になり、塗りつぶしのように なってしまう。 5 なお、paintメソッド、updateメソッドはContainerクラス、repaintメソッドはComponentクラスにあり、 いずれもAppletクラスが継承しているので、制限子なしで使える(2-3参照)。 [thisキーワードについて] 1 initメソッドの中で、Threadクラスのオブジェクトを生成するとき、 t = new Thread(this); のように、コンストラクタ Threadの引数に、必ず、thisキーワードが使われている。 2 thisキーワードは 「カレントオブジェクトを参照するもの」とか 「実行中のオブジェクトを指す」(「独習Java」P135) とある。 カレントオブジェクトとは、呼び出されているメソッドやコンストラクタが属するオブジェクト。 3 APIで、ThreadのConstructorをみると、 Thread() Allocates a new Thread object. Thread(Runnable target) Allocates a new Thread object. Thread(Runnable target, String name) Allocates a new Thread object. とあり、引数なしのものは、extends ThreadとThreadクラスを拡張した場合のもの。 インターフェイス実装のときは、Runnable targetという引数が必要になる。これを簡単に、 thisキーワード ですませているのである。 4 thisキーワードにより、Runnableを実装したクラス自身がターゲットになり、Threadはそのターゲット から、runメソッドを取得している。runメソッドには実態がなく、プログラムで処理内容をオーバーライド (書き換え)している。 5 以上のように整理してみたが、どうも、thisキーワード が釈然としないので、 「独習Java」P262を参考にして、下記のMovingCircle4で、thisを使わずimplements Runnableを 実装した別クラスのインスタンス・オブジェクト を引数にして確かめてみた。 [プログラムー点の移動で円を描く、別解] public class MovingCircle4 extends Applet { double radians = 0; int xc,yc,lastx,lasty; public void init() { Dimension d = getSize(); xc = d.width/2; yc = d.height/2; lastx = xc + 120; lasty = yc; Thread t = new Thread(new MC4()); t.start(); } // 以下、update(Graphics g) とpaint(Graphics g) は3-3に同じ。 class MC4 implements Runnable { //内部クラス public void run() { try { while(radians < 2*Math.PI) { repaint(); Thread.sleep(25); radians += Math.PI/180; } } catch (Exception e) { } } } } //class MovingCircle4全体を閉じるカッコ 6 class MC4はMovingCircle4クラスで定義した変数、radiansを使うので内部クラスとした。 したがって、class MovingCircle4 の全体を閉じるカッコは最後の行になる。上記、プログラムの最後の カッコに「class MovingCircle4を閉じる」とコメントをつけたのはそのため。 (なお、別クラスが自由に変数を使えるために、内部クラスとすればいいことにはじめ て気付いた のは、「Java応用2(イベント処理2)」6章 である)。 Thread(Runnable target) を忠実にすると、このように大変になる。Thread(this) と、 簡略化できるのは大きい。 なお、このプログラムは、点の移動で円を描く、別解のプログラム にある。3-4 sin曲線と円を描く
[プログラム] import java.applet.*; import java.awt.*; /* <applet code="CircleSin" width=600 height=300> </applet> */ public class CircleSin extends Applet implements Runnable { public double radians = 0; public int xc1,yc1,xc2,yc2; public int lastx1,lasty1,lastx2,lasty2; public void init() { Dimension d = getSize(); xc1 = d.width/4; yc1 = d.height/2; //円の半径を120とする lastx1 = xc1 + 120; lasty1 = yc1; //sin(x)の最大値も120とする xc2 = d.width/2; yc2 = d.height/2; lastx2 = xc2 ; lasty2 = yc2; // スレッドを起動する Thread t = new Thread(this); t.start(); } // run() とupdateは3−3に同じ public void paint(Graphics g) { //Sin int x2 = xc2 + (int)(40 * radians); int y2 = yc2 + (int)(-120 * Math.sin(radians)); g.setColor(Color.blue); if(lasty2 != -yc2) { g.drawLine(lastx2,lasty2,x2, y2); } lastx2=x2; lasty2=y2; // Circle int x1 = xc1 + (int)(120 * Math.cos(radians)); int y1 = yc1 - (int)(120 * Math.sin(radians)); g.setColor(Color.red); if(lasty1 != -yc1) { g.drawLine(lastx1,lasty1,x1, y1); } lastx1=x1; lasty1=y1; } } [実行結果][コメント] 1 下記もいれて、マルチスレッドの例にするつもりだったが、この例では不要。 t2 = new Thread(this,"Sin"); t2.start(); 2 System.out.println("Sin" + " radians = " + radians + " x2 = " + x2 + " y2 =" + y2); などをいれて、2が平行して動いていることを確認。 3 Sinのy2 に-符号をつけたのは、sinの値が+の場合は、アプレットのY座標が減る方向に描くから。 Circleの方は、x,y座標のsinとcosに同じ現象があって、相殺される。3-5 渦巻き
3-3の円のプログラムを少し変形して、渦巻きを描かせてみる。角度だけでなく、半径の値も少しずつ変化させる。なお、タートルグラフィックスとフラクタル に、もっとスマートな手法を提示している。
[プログラム・実行結果] import java.applet.*; import java.awt.*; /* <applet code="Uzu" width=300 height=300> </applet> */ public class Uzu extends Applet implements Runnable { Thread t; double radians = 0; int r,xc,yc,lastx,lasty; public void init() { Dimension d = getSize(); r = 0; xc = d.width/2; yc = d.height/2; lastx = xc+1; lasty = yc-1; t = new Thread(this); t.start(); } public void run() { try { while(r < 140) { repaint(); Thread.sleep(25); r = r + 1; radians += 15*Math.PI/180; } } catch (Exception e) { } } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { int x = xc +(int)(r * Math.cos(radians)); int y = yc - (int)(r * Math.sin(radians)); g.setColor(Color.red); if(lasty != -yc) { g.drawLine(lastx,lasty,x, y); } lastx=x; lasty=y; } }3-6 ランダムに発生させた点を結ぶ
乱数とアプレット画面の幅と高さによって決まる位置に、小さな4角形塗りつぶしの点を描き、直前の 点とを結ぶ。[プログラム・実行結果] import java.applet.*; import java.awt.*; /* <applet code="ConnectedDots" width=300 height=300> </applet> */ public class ConnectedDots extends Applet implements Runnable { Thread t; int lastx = -1; int lasty = -1; int count=0; public void init() { t = new Thread(this); t.start(); } public void run() { try { while(count < 100) { repaint(); Thread.sleep(200); } } catch (Exception e) { } } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { // ウィンドウ内の無作為な場所を選択する Dimension d = getSize(); int x = (int)(Math.random() * d.width); int y = (int)(Math.random() * d.height); // ドットを描画する g.fillRect(x - 2, y - 2, 5, 5); // 直前のドットと最新のドットを線で結ぶ if (lastx != -1) { g.drawLine(lastx, lasty, x, y); } // xlastとylastの値を更新する lastx= x; lasty = y; count++; } }[コメント] 1 g.fillRect(x - 2, y - 2, 5, 5)で、5ピクセルの大きさの小さな正方形を塗りつぶして、点を描画 している。 2 100ケの点を打つと終了するように、countしている。 3 if (lastx != -1) は、lastxが-1でなければ、以下の処理をして、直線で結べといっている。 最初の点は、結ぶべき相手がないので、ありえない-1をこの場合のlastxとして、初期設定して いるので、if文の対象外、直線で結ぶことを除外している。3-7 動く同心円
1-3 の同心円を 時間差をつけて描いてみる。 [プログラム] import java.applet.*; import java.awt.*; /* <applet code="MovingDoshinen.class" width=300 height=300> </applet> */ public class MovingDoshinen extends Applet implements Runnable { Thread t; int xc,yc,radius; int i; Color[] colors = {Color.magenta, Color.blue, Color.cyan,Color.green, Color.yellow,Color.red,Color.white,Color.black}; public void init() { // ウィンドウの中心を計算する Dimension d = getSize(); xc = d.width/2; yc = d.height/2; radius=xc-15; t = new Thread(this); i=0; // スレッドを起動する t.start(); } public void run() { try { while(i < colors.length) { // 再描画を要求する repaint(); Thread.sleep(1000); // 半径の値を減らし、配列の添え字を増やす。 radius -= 15; i++; } } catch (Exception e) { } } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { g.setColor(colors[i]); // チェックのため System.out.println("i = " + i + " radius = " + radius + " colors" + colors[i]); g.fillOval(xc-radius,yc-radius,2*radius,2*radius); } } [コメント・実行結果] 1 1-3 では、繰り返し、for の中で、半径、radius の大きさやColorクラスの配列で与 えた色を変え ていた。 2 それを runメソッドの中で、Thread.sleep(1000) としてから、順次実行していく。 まずは、マゼンタ色で円の中すべてが塗られ、続いてブルーで上塗りされる様子がよく分かる。3 なお、チェックのためにいれた、System.out.println からは、 下記の出力が出る。 1-3 のときと変わらない。 i = 0 radius = 135 colorsjava.awt.Color[r=255,g=0,b=255] i = 1 radius = 120 colorsjava.awt.Color[r=0,g=0,b=255] i = 2 radius = 105 colorsjava.awt.Color[r=0,g=255,b=255] i = 3 radius = 90 colorsjava.awt.Color[r=0,g=255,b=0] i = 4 radius = 75 colorsjava.awt.Color[r=255,g=255,b=0] i = 5 radius = 60 colorsjava.awt.Color[r=255,g=0,b=0] i = 6 radius = 45 colorsjava.awt.Color[r=255,g=255,b=255] i = 7 radius = 30 colorsjava.awt.Color[r=0,g=0,b=0]