Home >> Lecture 11


第11回 総合的なクラスの定義(5/29)


11−1.Java2D

■Java2D用のグラフィックスコンテキスト

★グラフィックスコンテキストに変換するための操作

public void paint( Graphics g ) {
	Graphics2D  g2 = (Graphics2D) g;
}

あるいは、アプレットやウィンドウ(Frame)にて、

Graphics2D g = getGraphics( );

★グラフィックスコンテキストにおいて使える主要なメソッド

メソッド名機能
draw( Shape s )sが示す形の枠を描画する
fill( Shape s )sが示す形を塗りつぶす
translate( double dx, double dy )
あるいはtranslate( int dx, int dy )
原点の移動
scale( double sx, double sy )座標の拡大縮小
rotate( double theta )座標軸の回転
rotate( double theta, double x, double y )回転の中心を指定した、座標軸の回転
shear( double shx, double shy )座標のシアー変形

★グラフィックスコンテキストの描画属性に関するメソッド

メソッド名機能
setPaint( Paint paint )描画のためのペイントの設定
setComposite( Composite comp )コンポジションの設定
setStroke( Stroke s )線描画のための設定
setBackgound( Color color )背景色の設定
setTransform( AffineTransform t )座標軸のアフィン変換の設定

★直接描画をするメソッド

Graphicsクラスと共通のメソッドも使えます。ただし、Graphics2Dで機能拡張されているものもあります。

メソッド名機能
draw3DRect前後に飛び出して見えなくもない四角形を描きます
drawArc弧を描きます
drawGlyphVector字形を描きます
drawImage画像を描きます
drawOval楕円を描きます
drawRect四角形を描きます
drawRoundRect角丸四角形を描きます
drawString文字列を描きます
fill3DRect塗りで前後に飛び出して見えなくもない四角形を描きます
fillArc円弧を塗ります
fillOval楕円を塗ります
fillRect四角形を塗ります
fillRoundRect角丸四角形を塗ります

■Shape オブジェクト

import java.awt.geom.*;

★Shapeクラスのオブジェクト達

Shapeクラス(抽象クラス:インターフェースの形で定義されている)
→Polygon, Rectangle, Line2D, CubicCurve2D, Area, GeneralPath, QuadCurve2D
→RectangularShape
→→Arc2D, Ellipse2D, Rectangule2D, RoundRectangle2D

★Shapeクラスの共通メソッド

メソッド名機能
boolean contains( double x, double y ) 指定された座標が、形の中にあるかどうか判定する
Rectangle getBounds( )その形が入る矩形領域を得る。

★Line2D

サブクラスにLine2D.FloatとLine2D.Doubleがある。どちらかのサブクラスを使います。 また、Point2Dを両端の点の座標に使いますが、Point2Dのサブクラスも、 Point2D.Float、Point2D.Double、そして、整数が使えるPointがあります。 このうちのどれかを使って指定します。

	Point p1 = new Point( 10, 10 );
	Point p2 = new Point( 100, 100 );
	Line2D   line = new Line2D.Double(  p1, p2 );
	g2.draw( line );
	

★QuadCurve2D

3つの点(1つは制御点)で、曲線を描画するもので、同じように、DoubleとFloatクラスのものがあります。

	QuadCurve2D  qc = new QuadCurve2D.Double( 0.0, 0.0, 10.0, 10.0, 0.0, 20.0 );
	g2.draw( qc );
	

★CubicCurve2D

4つの点(2つは制御点)で、曲線を描画するものもので、同じように、DoubleとFloatクラスのものがあります。

	CubicCurve2D  qc = new CubicCurve2D.Double( 0, 0, 0, 100, 100, 100, 200, 200 );
	g2.draw( qc );
	

★Arc2D

	Arc2D arc = new Arc2D.Double(10, 10, 100, 100, 45, 270, Arc2D.PIE );
		

タイプとしては以下のものがあります

★Elliipse2D

	Ellipse2D  el = new Ellipse2D.Double( 10, 10, 100, 100 ); 
		

★Rectangle2D

	Rectangle2D  rect = new Rectangle2D.Double( 10, 10, 100, 100 );
		

★RoundedRectangle2D

	RoundRectangle2D rr2 = new RoundRectangle2D.Double( 10, 10, 100, 100, 20, 20 ); 
		

★GeneralPath

パスの描画のために以下のようなメソッドが用意されている。基本的には、PostScriptと同じで一筆書き。

メソッド名機能
moveTo( float x, float y )(x, y)に単に移動
curveTo( float x1, float y1, float x2, float y2, float x3, float y3 )(x1, y1), (x2, y2)を方向線として(x3, y3)に至る線を描き、移動
lineTo( float x, float y )(x, y)に至る線を描き、移動
quadTo( float x1, float y1, float x2, float y2 )(x1, y1)を経由ポイントとして、(x2, y2)に至る曲線を描き、移動する

エッフェル塔を描いてみます。

	GeneralPath	eiffel = new GeneralPath( );
	eiffel.moveTo( 20.0f, 0.0f );
	eiffel.lineTo( 30.0f, 0.0f );
	eiffel.quadTo( 30.0f, 30.0f, 50.0f, 60.0f );
	eiffel.lineTo( 35.0f, 60.0f );
	eiffel.curveTo(35.0f, 40.0f, 15.0f, 40.0f, 150.0f, 60.0f );
	eiffel.lineTo( 0.0f, 60.0f );
	eiffel.quadTo( 20.0f, 30.0f, 20.0f, 0.0f );

★Area(複合Shape)

Area 変数名 = new Area( Shapeクラスのオブジェクト );
	Area  a = new Area( new Ellipse2D.Double( 10, 10, 100, 100 ) );
	
メソッド機能
add(Area other) このエリアに相手のエリアの部分も追加します
exclusiveOr(Area other) このエリアに相手のエリアの部分も追加しますが、交差部分は取り去ります
intersect( Area other )相手のエリアとの交差部分を、このエリアに設定します
subtract(Area other) このエリアから、相手のエリアとの交差部分を取り去ります
transform(AffineTransform t) このエリアを指定されたアフィン変換で変換します

■アフィン変換

	import java.awt.geom.*;
	
	new AffineTransform( ) 
	
メソッド機能
rotate( double theta ) 回転変換します
rotate( double theta, double anchorx, double anchory ) 指定した座標周りで回転変換します
scale( double sx, double sy ) 拡大縮小変換します
shear( double shx, double shy ) シアーリング変換します
translate( double tx, double ty )移動変換します
	// そこまでの変換指定を保存しておく
	AffineTransform  reserved = g2.getTransform( ) ;
	
	// g2に対して別の変換指定を行なう
	
	g2.setTransform( reserved ); // 元に戻す
	

■描く属性を決める

★Strokeクラス(BasicStrokeクラス)

枠や線の属性を決定する

以下のようなコンストラクタが用意されています。

コンストラクタ名機能
BasicStroke( )
BasicStroke( float width )
BasicStroke( float width, int cap, int join )
BasicStroke( float width, int cap, int join, float miterlimit )
BasicStroke( float width, int cap, int join, float miterlimit, float dash [ ] , float dash_phase )
	new BasicStroke( );
	new BasicStroke( 0.3f );
	new BasicStroke( 2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL );
▼端点Capの定数
CAP_BUTT, CAP_ROUND, CAP_SQUARE
▼結合点Joinのための定数 JOIN_MITER, JOIN_ROUND, JOIN_BEVEL
▼描画のために現在のストロークをセットするメソッド setStroke( ストローク )
	Graphics2D g2 = (Graphics2D) g;
	Stroke  s = new BasicStroke( 0.5f );
	g2.setStroke( s );

そのストロークを持ったシェイプを作る例を以下に示します。

	Stroke  stroke = new BasicStrock(  3f  );
	Shape  strokedshape = stroke.createStrokedShape(  shape );

★Paintクラス

Color, GradientPaint, TexturePaintがあります。また透明度も設定できます。

Colorクラスでは、単精度浮動小数点を使って、RGB(各0.0〜1.0)カラーを設定する。また、4番目に透明度(0.0〜1.0)を設定することもできる。

	Paint  p = new Color( 0.4f, 0.3f, 0.5f, 0.8f );
	

GradientPaintクラスでは、グラデーションを設定できます。 循環状(cyclic)、線状(acyclic)のグラデーションを設定できるコンストラクタが用意されています。 以下のコンストラクタで、cyclicの値が省略された場合は、線状のグラデーションになります。

コンストラクタ名
GradientPaint( float x1, float y1, Color c1, float x2, float y2, Color c2, booelan cyclic )
GradientPaint( Point p1, Color c1, Point p2, Color c2, boolean cyclic )
	Paint p =new GradientPaint( 0f, 0f, Color.red, 100f, 0f, Color.blue, true );
	

TexturePaintでは、イメージを元に塗りつぶしのためのパターンの設定ができる。 以下のコンストラクタで、anchorは、パターンの繰り返しのための、 四角形の位置と大きさを決定するために用いられます。

コンストラクタ名
TexturePaint( BufferedImage b, Rectangle2D anchor )
	Rectangle2D r = new Rectangle2D( ); 
	r.setRect( 1f, 1f, 100f, 100f );
	Paint  p = new TexturePaint( b,  r );
	
▼描画のためのペイントの設定 setPaint( ペイント )
	Graphics2D g2 = (Graphics2D) g;
	g2.setPaint(  Color.blue );
	

11−2.画像・音声・アニメーション

■例外とtry〜catch構文

例外(Exception)とは、プログラムを実行している最中に発生する何らかのエラーのことを指しています。例えば、次のような場合に例外が発生します。これらの例外は、実行最中でないと起こるかどうかわからないものです。

ユーザが強制的に入力を終了させた
指定したファイルや画像、音声ファイルが存在しない
ゼロで割った(整数の場合)
配列でインデックスがサイズを超えた
指定されたカラーが存在しない

Javaではプログラム自身で、例外発生時の制御をすることができます。もし例外が発生した場合、次善の措置を記述することができます。次のようなtry〜catch構文で記述します。

▼例外発生対処のtry〜catch構文の簡単な書式
try {
 // 何かの処理

} catch ( Exception e ) {
 // 問題が起こったときにする処理
}

この構文の動作は、「何かの処理」を行なっている最中に、もし例外が発生したら、そこで処理を中断して、「問題が起こったときにする処理」に制御を移すためのものです。例外が発生しなければ、そのまま処理を継続することになります。例外発生時には、変数eに例外の内容を示すオブジェクトが代入されています。

図14-1 例外とtry〜catch構文

この構文を使って、例外を発生させるような文を記述してみましょう。次の例は、整数除算で、分母に0を持ってきた場合です(注1)。

	try {
			int  x = 0;
			int  y  = 10 / x;
		}
		catch ( Exception  e ) {
			System.err.println( "整数は0で割らないでください" );
		}

「問題が起こったときにする処理」としては、上の例のようにエラーメッセージを端末に表示するのが妥当な方法でしょう。通常はSystem.out.printlnや、System.out.printを用いて文字列を表示していますが、これはシステム(Javaを実行している環境)が用意しているSystem.outという標準出力端末(通常は端末画面)に文字列を表示させるものです。System.errはSystem.outと同様に用いることができるのですが、特にエラー情報を中心に表示させるという形で役割分担がされています。

	try {	int  x = 0;   int  y  = 10 / x;	}
	catch ( Exception  e ) {	System.err.println( e );	}

エラーが発生した際の例外(Exception)は、変数eに代入されますが、この例外は、文字列に変換可能である(printlnメソッドなどの場合は自動的に文字列に変換される)。文字列に変換されたときは、短いエラーメッセージになります。上の例では、これをエラー情報として表示しています。この場合には、次のようなメッセージが端末に表示されることになります。

java.lang.ArithmeticException: divided by zero

また、例外がどこで起こったかをエラー情報として表示させることもできます。この場合には、printStackTraceという名前のメソッドを使用します。

	try {      int  x = 0;   int  y  = 10 / x;     }
	catch ( Exception  e ) {   e.printStackTrace( );	}

なお、例外が代入される変数は、次のように別の名前でも構いません。ただし、例外に対処する部分で同じ名前の変数名を使い続けるようにしてください。

	try {      int  a[ ] = new  int[ 5] ,  x = 8;    a[ x ]  = 10;   }
	catch ( Exception  error    ) {     error.printStackTrace( );       }	// errorという変数名で統一

従来のプログラムの方法では、何かのエラーが発生した際は、メソッド呼出しの戻り値をif文などを持ちいて調べる必要がありました。これをJavaでは例外処理として、直接記述できるようになったのです。

※注1 実数の場合は、0で割るとNaN(Not a Number)という無限大を表す特別な数になります。

☆例外とその対処

例外が発生したときに何もしないで、ただ単に次の処理に進みたいときは、次のように、「問題が起こったときにする処理」の部分に何も書かなくても構いません。

try { 何かの処理 } catch ( Exception e ) { } // エラーが起こっても何も対処しない

しかしながら、エラーが発生したら、そのエラーは後の処理に影響をもたらします。例えば、画像ファイルが存在しない場合のに、画像ファイルを読み込むときに最初の例外が発生しますが、続けて、その画像を表示させたりするときにも例外が発生します。例外が発生しても、上のように記述されていればJavaプログラムは処理を続けます。

Applet Viwerなどでも、例外が起こったことだけを伝えて処理を続行させるようにしています。たとえば、カラーの指定方法が間違っていた場合には、次のようにどこで例外が発生したかを例外の種類、短いエラーメッセージと共に出力しながらも、処理は続行されます。この場合は、例外の種類がIllegalArgumentExceptionで、緑(Green)成分の色指定の値が範囲を超えていたことを示しています。ColorQueryというクラスのpaintメソッドで例外が発生したことを示しています。

java.lang.IllegalArgumentException: Color parameter outside of expected range: Green
	at java.awt.Color.testColorValueRange(Color.java:180)
	at java.awt.Color.(Color.java:187)
	at ColorQuery.paint(ColorQuery.java:24)
	at java.awt.Component.update(Component.java:1083)

より高度なプログラミングを行なう場合や、企業商品として使われるアプリケーションやアプレットであれば、想定されるエラーに対して、それによって起こる障害回復を最小限に抑えるように処理を記述する必要があるでしょう。たとえば、画像ファイルがなければ、ユーザにファイルを指定してもらうように処理するようにしなければいけません。

★try〜catch構文と変数のスコープ

注意しなければならないのは、try〜catch構文を用いたときの変数のスコープです。tryのブロックの中で宣言して導入した変数は、try構文が終わってしまうと存在しませんので、参照することはできません。たとえば、次のような断片では、コンパイル時にエラーが発生していまいます。

try  {
	Image  melo = getImage( new URL( "http://www.melodaisuki.com/melo.gif" ) );
} catch ( Exception e  ) {
	System.err.println( e );
}
g.drawImage( melo, 0, 0, this );		// 変数meloは既にここでは存在しない

もしtry〜catch構文以降も変数を参照したい場合は、次のように変数を構文の外で宣言する必要があります。

Image melo;				//  変数meloをブロックの外側で定義した
try  {
	melo = getImage( new URL( "http://www.melodaisuki.com/melo.gif" ) );
} catch ( Exception e  ) {
	System.err.println( e );
}
g.drawImage( melo, 0, 0, this );

☆例外の種類

Javaでは例外もクラスとして定義されていますので、例外にも継承関係があり、細かく分類されています。一番の大もとは、Exceptionです。実行時に言語的に起こり得る例外は、RuntimeExceptionとして分類されていますし、AWTなどのクラスライブラリで起こるものはAWTException、入出力関係で起こるのはIOExceptionというように分類されています。代表的なわかりやすい例外の幾つかを以下の表に階層関係を示しながら挙げておきます。

class java.lang.Throwable 例外のための仕様を定めたクラス
class java.lang.Exception 例外全体
→class java.awt.AWTException AWT関係
→class java.io.IOException 入出力関係
→class java.lang.InstantiationException オブジェクトを作るとき
→class java.lang.InterruptedException スレッド関係
→class java.lang.RuntimeException 実行時の例外
→→class java.lang.ArithmeticException 算術演算
→→class java.lang.ArrayStoreException 配列の要素
→→class java.lang.IllegalArgumentException 引数の間違い
→→class java.lang.IndexOutOfBoundsException 配列や文字列のインデックス
→→class java.lang.NegativeArraySizeException 配列のサイズが負の場合
→→class java.lang.NullPointerException オブジェクトがない
図14-2 代表的な例外

このため、より細かな例外から、対処を記述したいという場合があります。このような記述法がtry文の中には用意されています。

▼複数の例外クラスに対する書式
try {
何かの処理
} catch ( より細かな例外のクラス e ) {
問題が起こったときにする処理
} catch ( より大きめな例外のクラス2 e ) {
問題が起こったときにする処理
} finally {
必ず実行される処理
}

★例外を発生させるメソッド

メソッドの処理内容によっては、例外を発生させる可能性があるときは、そのような指定がされています。たとえば、以下はBufferedReaderクラスに定義されていますreadLineというメソッドの仕様なのですが、これにはこのメソッドを利用した場合、IOExceptionを発生させる可能性があるという指定がついています。

public String readLine( )  throws IOException

例外を発生させる可能性のあるメソッドを用いるときは、必ずtry〜catch構文の中で使う必要あります。たとえば、上のreadLineでしたら、次のように構文を記述します。

try  {   br.readLine( );  }  catch (  IOException  except )  {  System.err.println( except );  }

catchの部分には、IOExceptionという指定がされているのですが、これは例外の種類のクラス階層から、単にExceptionと記述しても構いません。Exceptionならば、すべての例外の種類が含まれているからです。

■画像イメージの表示

AWTクラスライブラリには、GIF、PNGやJPEG形式の画像ファイルを扱うことができるImageクラスが用意されており、画像をアプレットの中に表示させることができます。画像の読込みと表示は、次のような3段階で行ないます。まず、変数を宣言し、その変数に対して読込みを行ないます。最後に、変数を使って表示します。

▼画像の読込みと表示
 1. イメージ型の変数を宣言する
Image   イメージ型の変数;

 2. イメージを読み込む
イメージ型の変数 = getImage( URLの指定 );

 3. イメージを表示する
g.drawImage( イメージ型の変数 , x座標 , y座標 , this );

宣言と読込みは同時に行なうことができますので、2段階にすることもできますし、getImageをdrawImageメソッドの引数に入れてしまえば、読込みと表示を1行で表現することもできます。drawImageの引数の4番目のthisは、アプレット自身を示しています。ここにはImageObserverクラスのオブジェクトがはいるのですが、通常はアプレットがImageObserverになっていますので、thisを指定するのが一般的です。

☆イメージを表示させてみる

ここで表示させるイメージは、Javaのdukeという画像です。クラスファイルと同じフォルダに、duke.gifという画像ファイルがあるとします。次のようにアプレットのプログラムを記述します。

import   java.awt.*;
import   java.applet.*;

public class DukeViwer extends Applet {
	public void paint( Graphics g ) {
		Image    melo = getImage(  getCodeBase( ), "duke.gif" );
		g.drawImage( melo, 0, 0, this );
	}
}
duke.gifに保存されている画像

☆背景画像と前面の画像の組合せ

GIF画像やPNG画像は、作画の際に透明という指定をすることができます。 JPEG形式などで撮影された背景画像の上に、透明色を持っているGIFやPNG画像を描画すると、 重ねた形で表示させることができます。 描画の際は、最初に描画された画像が重なり的に下側に表示されます。

import	java.awt.*;
import	 java.applet.*;

public class ImageComposer  extends Applet {
	public void paint( Graphics g ) {
		Image    back = getImage(  getCodeBase( ), "back.jpg" );  // 背景画像
		g.drawImage( back, 0, 0, this );
		Image    person = getImage(  getCodeBase( ), "person.png" );  // 透明色を持つ画像
		g.drawImage( person, 50, 50, this );
	}
}

■画像ファイルの場所の指定について

アプレットではセキュリティの問題から禁止されているのですが、アプリケーションなどで、 画像ファイルを読み込む際には、ローカルに(アプレットの置かれているWebサーバ側に) 画像ファイルを置いておき、次の2つのいずれかを指定します。

☆classファイルからの相対的な位置指定をする場合

Appletが保存されているサーバあるいはアプリケーションを動かしているコンピュータ上で、 Javaの実行形式であるクラスファイルが保存されているフォルダからの相対的な位置をパスで指定する方法です。 クラスファイルのフォルダの位置については、次のメソッドを用いて指定します。

getCodeBase( )

このメソッドと、相対的な画像ファイルへのパスを文字列で指定することによって、画像ファイルを読み込むことができるようになります。以下の例は、クラスファイルが置かれているフォルダの親フォルダ上にあるpicturesという名前のフォルダの中にあるmoon.gifという名前の画像ファイルを指定しています。

getImage(  getCodeBase( ),   "../pictures/moon.gif" );

相対的な指定をするときは、もともとUnix OSで使われていた、相対的なパスを指定をするために使われる次のような記号が用いることができます。

. ピリオド そのフォルダを示します
.. 2つのピリオド 親フォルダを示します
/ スラッシュ フォルダの区切りを示します

★HTMLファイルからの相対的な位置指定をする場合

この方法は、セキュリティ的な問題から、現在はほとんど使えなくなってしまったのですが、 アプレットを表示させているHTMLファイル(Webページ)が保存されているサーバ上での、 HTMLファイルが保存されているフォルダからの相対的な位置をパスで指定する方法です。 HTMLファイルのフォルダの位置については、次のメソッドで指定します。

getDocumentBase( )

たとえば、アプレットを表示するためのHTMLファイルが置かれているフォルダに imagesという名前のサブフォルダがあり、そのフォルダの中にalto.gifという画像ファイルがあるとします。 そのときは、次のように指定します。

	getImage(  getDocumentBase( ), "images/alto.gif" );
	

☆画像を表示する位置と幅と高さ

drawImageメソッドでは、次のような2つのタイプが用意されています。x座標、y座標は画像を矩形と考えて、その左上の角の位置になります。

drawImage( イメージオブジェクト, x座標 , y座標 , イメージオブザーバ );
drawImage( イメージオブジェクト, x座標 , y座標 , 高さ , 幅 , イメージオブザーバ );

2つ目の方のdrawImageメソッドでは、画像の幅と高さを指定することができます。この場合は、元の画像をその幅と高さに合うように自動的に拡大・縮小が行なわれます。たとえば、次の記述は幅も高さも128ピクセルに収まるように画像を表示させます。

	g.drawImage( duke, 50, 50, 128,  128,  this );

読み込んできた画像の元々の幅と高さを得るためには、 Imageクラスのオブジェクトに用意されているgetWidthメソッドとgetHeightメソッドを利用します。

イメージオブジェクト.getWidth( イメージオブザーバ );
イメージオブジェクト.getHeight( イメージオブザーバ );

これらのメソッドは、引数にイメージオブザーバを必要とします。 通常は、アプレット自身がイメージオブザーバになっているので、thisを 指定します。 次の例は、変数widthとheightに画像の幅と高さを求めています。変数の宣言と代入とを同時に行なっています。

	Image    duke = getImage(  getCodeBase( ), "duke.gif" );
	int  width  = duke.getWidth( this );
	int  height  = duke.getHeight( this );
	System.out.println( "Image width: " + width + "   height: " + height );

次の例は、幅・高さとも、半分のサイズで表示させるための記述になっています。

	g.drawImage( duke, 0, 0, duke.getWidth( this ) /2,  melo.getHeight( this ) /2,  this );

☆マウスにあわせて表示を変えていくアプレット

マウスをクリックしたら次の画像を表示するようなアプレットを作ってみましょう。 画像ファイルは9つあり、それぞれduke1.jpg〜duke9.jpgというファイル名がついているとします。 画像ファイルはWebサーバ上のクラスファイルが置かれているフォルダ上のサブフォルダである imagesフォルダの下に格納されているとします。 画像の読込みは、initメソッドで予め行なっておきます。 画像のために配列を用意します。boypicsはImageクラスの配列で、9つの要素を持ちます。 変数currentは、現在配列の何番目の画像を表示すればよいのかを示しています。 マウスがクリックされたら、この変数の値を1増やして、再描画させます。

import	java.awt.*;
import	java.awt.event.*;
import	 java.applet.*;

public class DukeAnime extends Applet  {
	Image 	boypics [ ] = new Image[ 9 ];
	int	current;

	public void init(  ) {
		addMouseListener( new MyMouseAdapter( ) );
		for ( int   i=0;  i<boypics.length ; i++ ) {
			boypics[ i ] = getImage( getCodeBase( ),  "images/boy" + (i+1) + ".gif"  );
		}
	}

	public void paint( Graphics g ) {
		g.drawImage( melopics[ current ], 0, 0, this );
	}
	
	class MyMouseAdapter extends MouseAdapter {
		public void mouseClicked( MouseEvent e ) {
			current = (current + 1) % 9;
			repaint( );
		}
	}
}
       
画像ファイルboy1.gif〜boy9.gif

☆メディアトラッカーを使う

アプレットでは、しばしば画像ファイルが大きすぎて読込みに時間が掛かり、実際の表示が間に合わないことがあります。それを避けるために、AWTクラスライブラリには画像が読み込まれたかどうかをチェックするためのオブジェクトが用意されています。それはメディアトラッカー(Media Tracker)と呼ばれています。これを利用するには、まずアプレット上でMediaTrackerクラスのオブジェクトを生成します。

MediaTracker  mt = new  MediaTracker( this );

次に、画像を示すイメージオブジェクトをaddImageメソッドを使って、このメディアトラッカー・オブジェクトに登録していきます。パラメータの番号には、適当な整数値を与えてください。後で画像がロードされたかどうかチェックするのに用います。

mt.addImage(   イメージオブジェクト,   番号 ) ;

指定されたイメージオブジェクトがロードされたかどうかは、checkIDメソッドを使います。

mt.checkID( 番号, フラグ );
フラグの部分には、次のような論理値を指定します。
true ロードがされていなければロードを開始させる
false ただ単にロードされているかどうか調べるだけ

このcheckIDメソッドは、論理値を返し、trueならば、ロードが完了したことを示し、falseならばロードが完了していないことを示します。そのため、大抵はif文などの条件として用いられます。次の記述は、3番の画像がロードされているかどうかチェックしています。

if  (  mt.checkID(   3,  true  )  )  {	// もし3番の画像がロードされていた場合は、、
		.....			//  ただし、ロードされていなければロードを開始させる

登録されたすべての画像がロードされたかどうかを確かめる場合には、checkAllメソッドを使います。checkIDメソッドと同様にフラグを指定します。メソッドの返り値もcheckIDと同じです。

if  (  mt.checkAll( true  )  )  {	//  もしすべての画像がロードされたら、、、

他にも、指定された画像がロードされるまで待つwaitForIDメソッドや、登録されたすべての画像がロードされるまで待つwaitForAllメソッドがあります。たぶん、こちらの方を良く使うのではないでしょうか。フラグがないだけで、使い方はcheckIDあるいはcheckAllと同じです。なお、waitForIDとwaitForAllは例外を発生する可能性がありますので、try〜catch構文の中にいれておく必要があります。

if ( mt.waitForAll( ) ) { // すべての画像のロードを待ち、それが完了したら、、、、

例えば、先ほどの9つの画像がロードされるのを待つようにするために、プログラムの一部を修正してみました。 initメソッドの該当部分を書き換えます。

インスタンス変数の宣言:
Image 	boypics [ ] = new Image[ 9 ];
MediaTracker  mt = new  MediaTracker( this );

initメソッドの該当部分の修正:
for ( int   i=0; i<boypics.length; i++ ) {
	boypics[ i ] = getImage( getCodeBase(  ),  "images/boy" + (i+1) + ".jpg"  );
	mt.addImage( boypics[ i ],  i );
}
mt.waitForAll( );

メディアトラッカーは、画像をロードしている最中には、その旨を示すような文字列を替わりに表示させるときに用いられることが多いでしょう。たとえば、先ほどの画像ファイルを読み込んで表示するアプレットで、読み込んでいる間は、Loading...とメッセージを出したい場合は次のように修正します。while文でcheckIDを使ってロードが終わったかどうか確認しています。Thread.sleepメソッドは、try文の中でしか使えないのですが、与えられた整数値をミリ秒とみなして、その間アプレットの実行を止め、他の処理をさせるようにさせています。Thread.sleep( 300 );は0.3秒アプレットが時間経過を待つことを意味しています。

import	java.awt.*;
import	java.applet.*;

public class  WaitingDuke  extends Applet {
	public void paint( Graphics g ) {
		try {
			MediaTracker  mt = new  MediaTracker( this );
			Image    duke = getImage(  getCodeBase(  ),  "images/duke.gif"  );
			mt.addImage( duke, 1 );
			while (  mt.checkID(  1,  true  )  == false  )  {
				g.drawString( "Loading...",  40, 40 );
				Thread.sleep( 300 );
			}
			g.drawImage( duke, 0, 0, this );
		} catch( Exception e ){ System.err.println( e ); }
	}
}

☆Java2Dでのイメージとフィルタリング

Java2Dでは、従来のImageクラスに替わり、BufferedImageクラスを用いて作業することが多くなります。 画像の各画素にアクセスするためです。 まず以下のようなimport文が必要になります。

	import   java.awt.image.* ;		// 必要になる
	

次のような形でBufferedImageを作成しておきます。

	BufferedImage  bi = new BufferedImage( 幅, 高さ, イメージの型 );
	

上記で指定するイメージの型としては、次のようなものがあります。

BufferedImage.TYPE_INT_RGB
BufferedImage.TYPE_INT_RGBA // 透明度も含む場合

用意したBufferedImageを描画領域として、通常の画像を描き込むことができます。

	Graphics2D  g2 = bi.createGraphics( );	// BufferedImageに対応した描画領域を作る
	g2.drawImage( image,  x, y, this );  // 画像イメージを表示
	

このような画像に対して、各画素をアクセスすることが可能になります。 1ピクセルごとに色の値を得てみましょう。TYPE_INT_RGBの場合は以下のようになります。

	int pixel = bi.getRGB( x, y );	 // x, y座標のRGB値を整数で
	bi.setRGB( x, y, pixel );		// x, y座標のRGB値を設定する
	

たとえば、上記のような形でpixel変数に画像が求められた場合、それぞれの成分は以下のようにして 分解して求めることができます。

	int red = (pixel & 0xFF0000) >> 16; 
	int green = ( pixel & 0xFF00) >> 8;
	int  blue = pixel & 0xFF;
	

逆にsetRGBで、各色の成分から画素として設定する値を以下のように作ることができます。

	pixel = (red <<16 ) & 0xFF0000 | (green<<8) & 0xFF00 | blue & 0xFF;
	

次の例は、すべての画素の赤成分だけをマスクしています。また、AffineTransformOpを利用して 画像を回転させています。

import java.applet.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import java.awt.image.* ;

public class ImageEffecter extends Applet {
	BufferedImage  bi;
	Image    back;
    
	public void init( ) {
		back = getImage( getCodeBase( ), "back.jpg" );
		bi = new BufferedImage( 512, 512, BufferedImage.TYPE_INT_RGB );
		try {
			MediaTracker  mt = new  MediaTracker( this );
			mt.addImage( back, 1 );
			mt.waitForAll( );
		} catch( Exception e ){ System.err.println( e ); }

		Graphics2D  g2 = bi.createGraphics( );
		g2.drawImage( back, 0, 0, this );
		for ( int y =0; y < 512; y++ ) {
			for ( int x =0; x < 512; x++ ) {
				int pixel = bi.getRGB( x, y );
				bi.setRGB( x, y, pixel | 0xff0000 );	
			}
		}
	}

	public void paint( Graphics g) {
		g.drawImage( bi, 0, 0, this );
		Graphics2D g2 = (Graphics2D)g;
		AffineTransform t = new AffineTransform( );
		t.translate( -256, -256 );
		t.rotate( Math.toRadians( 45 ) );
		g2.drawImage( bi, new AffineTransformOp( t, AffineTransformOp.TYPE_BICUBIC ), 768, 256 );
		g.drawImage( back, 0, 256, this );
	}
}
	

■サウンドを鳴らす

Java2から、次のような一般的な音声ファイルをサポートすることができるようになりました。このため、多くの音声ファイルは変換する必要はありません。また、MIDI形式のファイルも次のようなファイルをサポートしています。

音声ファイルの形式: AIFF, WAVE, AU
音声ファイルのコーディング方法: PCM(リニア), μ-law,その他
音声ファイルの標本周波数: 8kHz 〜 48kHz
音声ファイルの 量子化ビット数: 8 または 16 bit
音声ファイルのチャンネル数: Mono (=1)または Stereo(=2)

MIDI形式(楽譜形式)ファイル:
MIDI Type 0, MIDI Type 1, RMF

Javaでは音声ファイルの再生は、標準では16ビットのステレオで、22kHzの周波数で行なわれます。ただし、実行されるコンピュータで、この品質で再生できないときは、8ビットのモノラル、8kHzでの再生になってしまいます。MIDIファイルに関しては、内蔵のデジタルシンセサイザー音源を使って再生されます。

☆アプレット上で直接演奏させる

アプレットでは、これらの形式のサウンドをplayメソッドによって再生することができます。このメソッドは例外を発生する可能性がありますので、try〜catch構文の中に入れておく必要があります。

▼アプレット上で1回だけ演奏させる書式
 try {
  play( URLの指定 );
 } catch( Exception e ){
  System.err.println( e );
 }

URLの指定については、画像と同じように次のような2種類の指定の仕方ができます。 これらはWebサーバ上の音声ファイルを利用するものですが、 getCodeBaseメソッドを使ったクラスファイルからの相対的な指定をする方が良いでしょう。

play( getCodeBase( ), "ファイル名の指定 " );
play( getDocumentBase( ), "ファイル名の指定 " );

☆アプレットに用意されているオーディオ用のクラスAudioClipのオブジェクトを使う

Appletでは、音声ファイルを再生するためのAudioClipクラスが用意されています。 複数の音声ファイルを同時に再生させることができます。 AudioClipに関しては次のようなメソッドが用意されています。

▼サウンドファイルをロードする
getAudioClip( URLの指定 );

アプレットで、音声ファイルをロードしてAudioClipクラスのオブジェクトを用意してくれるものです。 URLの指定は、playメソッドと同様に、 Webサーバ上の音声ファイルを指定する2種類の指定をすることができます。たとえば、次のように記述します。

	AudioClip	au = getAudioClip(  getCodeBase( ),  "spacemusic.au" );

AudioClipクラスのオブジェクトには再生のために次の3つのメソッドが用意されています。

▼サウンドファイルを再生する
play( ) 1回だけ再生する
loop( ) 繰り返し何回も再生する
stop( ) 再生を止める

たとえば、上記の変数auが指し示すAudioClipクラスのオブジェクトを使った場合は、次のように記述します。

	au.play( );   au.loop( );  au.stop( );
	

Spacemusic.auファイルへのリンク

☆ボタンで再生を制御するアプレット

Webドキュメントのあるフォルダのサブフォルダにある、 "sounds/spacemusic.au"というAUファイルを読み込んで、 それを繰り返し再生するようなアプレットを作ってみましょう。 ボタンを1つ用意しておき、最初は"Stop"ボタンにしておきます。 もし、ボタンがおされたら再生を中止して、setLabelメソッドをボタンのラベルを"Start"に変えます。 次にボタンが押されたら、今度は再生を開始して、ボタンのラベルを"Stop"ボタンに変えます。 ラベルに設定されている文字列をgetLabelメソッドを使って取り出していますが、 その判定にequalsメソッドを使っています。

import 	java.awt.*;  
import	java.awt.event.*;
import	java.applet.*;

	public class AudioTester extends Applet implements ActionListener {
	AudioClip    mysound;
	Button b;

	public void init( ) {
		b = new Button( "Stop" ); 
		b.addActionListener( this ); 
		add( b );
		mysound = getAudioClip(  getCodeBase(  ),  "sounds/spacemusic.au"    );
	mysound.loop( );
	}

	public void actionPerformed( ActionEvent   e ) {
		if ( b.getLabel( ).equals( "Stop" ) ) {	mysound.stop( );      b.setLabel( "Start" );  }
		else {				mysound.loop( );      b.setLabel( "Stop" );  }
		repaint( );
	}
}

■スレッドとアニメーション

スレッド(Thread)とは、アプレットやアプリケーションと同時に動く別のプログラム(プロセス)であり、変数などを共通して用いることができるようなものです。たとえば、アプレットにおいて一定時間ごとにアニメーションが動き、「それとは別に」何らかのアクション(ユーザからの入力)に対応するためには、スレッドを用意しなければなりません。このような場合には、アプレットとスレッド側で次のように動作を分ける設計がよく行われます。

アプレット側 アクションに対応する・スレッドを開始させたり、終了させたりする スレッド側 アニメーションを動かす

アプレットでスレッドを使うときは、スレッドの開始・終了を制御するため、アプレット側にはstartメソッドとstopメソッド、およびスレッド側には、runメソッドを用意します。スレッド側にはstartメソッドだけが用意されています。スレッドのrunメソッドは、スレッド側のstartメソッドが呼ばれるとすぐにも呼ばれるようになります。スレッド側は参照している変数をnull値に設定すると実行されなくなります。

start → run → → → → → → → → → → → → → →終了

Runnableインターフェースを用いて、一つのクラスがアプレットとスレッドの両方を兼ねる場合が通例です。そこで、インスタンス変数で、スレッド制御用のThreadクラスの変数を作ります。

Thread   runner;

アプレットのstartメソッドでは、スレッドを新たに作りスタートさせます。

public  void  start( ) {
	if ( runner == null ) {	// nullはスレッドが実行されていないことを示す
		runner = new Thread( this  );
		runner.start( );
	}
}

アプレットのstopメソッドでは、スレッドに終了を告げます。

public  void  stop( ) {
	if ( runner != null ) {     runner = null;  }
}

runメソッドでは、スレッド側で実行したいことを行います。ただし、アプレットのstopメソッドによって実行中止の例外が発生するので例外処理をしておきます。また、頻繁に処理をしないときは、Thread.sleepメソッドを使って休憩し、アプレットに処理を回すようにしておきます。

▼runメソッドの一般的な書き方
public void run( ) { try { while( runner == Thread.currentThread( ) ) { //したいこと Thread.sleep( ミリ秒数 ); }
} catch( Exception e ) { System.out.println( e ); } }

☆スレッドを用いて時刻を刻々と変化させて表示させる

スレッドは、runメソッドを見るとわかるように、1秒単位で時間を更新しながら表示していくものです。アプレット自身は、マウスがクリックされたときの処理だけを行なっています。マウスがクリックされた場所に、時間を表示し直しています。

import    java.awt.*; 
import   java.applet.*;  
import   java.util.*;
import	java.awt.event.*;

public class DateModifier extends Applet implements Runnable {
	Thread	runner;  
	Date   date;
	int    curx = 100, cury = 100;

	public void init( ) { addMouseListener( new MyMouseAdapter( )  );  }
	public void start(  ) {  if ( runner == null ) {  runner = new Thread( this );  runner.start( ); } }
	public void stop(  ) {  if ( runner != null ) {  runner = null;  }  }

	public  void  paint( Graphics g ) {  g.drawString( date.toString(), curx, cury ); }

	public void run( ) {
		try {  while (  runner  == Thread.currentThread( ) ) {
			date = new Date(  );		//  現在時刻を得ます
			repaint(  );    Thread.sleep( 1000 );  }
		} catch( Exception e ) {  }
	}
	
	class MyMouseAdapter extends MouseAdapter {
		public  void mousePressed(  MouseEvent  e ) {
			curx=e.getX( ); cury=e.getY( ); repaint( );
		}
	}
}

☆画像を一定間隔でアニメーションのように見せる

次の例は、非常によく使われる例だと考えられます。最初にメディアトラッカーを使って、画像ファイルを何枚か読みこんでおき、その後に、一定間隔としてアニメーションとして見せる例題です。さきほどの、クリックしたら動くメロの画像を、アニメーションとして記述し直してみました。

import   java.awt.*;
import   java.applet.*;

public class BoyAnimetor extends Applet implements Runnable {
	Image 	boy [ ] = new Image[ 9 ];
	Thread	runner;
	int	current = 0;

	public void init(  ) {
		try {
			MediaTracker  mt = new  MediaTracker( this );
			for ( int   i=0; i<boypics.length ; i++ ) {
				boy[ i ] = getImage( getCodeBase( ),  "images/boy" + (i+1) + ".jpg"  );
				mt.addImage( boy[ i ], i );
			}
			mt.waitForAll( );
		} catch(  Exception e ){ System.err.println( e ); }

	}

	public void start(  ) {  if ( runner == null ) { runner = new Thread( this );  runner.start( ); } }
	public void stop(  ) {  if ( runner != null ) {  runner = null;  }  }
	public void paint( Graphics g ) {  g.drawImage( melopics[ current ], 0, 0, this );  }

	public void run( ) {
		try {  while (  runner  == Thread.currentThread( ) ) {
			current = (current + 1) % 9;
			repaint(  ); 
			Thread.sleep( 1000 );  }
		} catch( Exception e ) {  } 
	}
}

☆スレッドを用いた時間計測

前に紹介しましたCalendarクラスのオブジェクトには、1970年の1月1日0時0分0秒からの経過時間を求めるgetTimeInMillisメソッドがあります。経過時間の型はlongの整数型でミリ秒単位で求められます。下の代入文では、Calendarクラスのオブジェクトを指す変数calに対して更にgetTimeInMillisメソッドを呼び出して、その経過時間を求めています。2つの時刻に対して、この方法を使って経過時間を求め、その差分を出せば、どの程度時刻が過ぎたかわかります。また、求められた時間差に対して、1000で割ると秒数が、更にその結果を60で割ると分数(更にその結果を60で割ると時間)を得ることができます。

long	millisecond = cal.getTimeInMillis( );	

次のアプレットプログラムは、1/10秒ごとに時間差を表示をするものです。ボタンを用意して、ボタンが押されたら計測を開始します。開始時間に対する経過ミリ秒を変数starttimeに求めています。また、刻々と変わる時間の経過ミリ秒を変数currentに求めています。この2つの変数の差を1000で割った時間差が経過秒で変数secondに計算しています。また、同じ差を1000で剰余を求めて、100で割ったものは、1/10秒の部分になります。スレッドの開始と停止をボタンで制御しているところにも注意してください。

import 	java.awt.*;
import	java.applet.*;
import 	java.awt.event.*;
import 	java.util.*;

public class TimerApplet extends Applet implements ActionListener, Runnable {

	Thread 	runner = null;
	String 	message ="";
	Button	 b;
	long	starttime, current;
	Font	font = new Font("Serif", Font.BOLD, 36);
	
	public void init( ) {
		b = new Button( "Start" );
		b.addActionListener( this );
		add( b );	
	}
	
	public void paint (Graphics g) {
		g.setColor(Color.blue);
		g.setFont(font);
		int  second = (int)((current-starttime)/1000);
		int  decisec = (int)((current-starttime)%1000/100);
		g.drawString(second+"."+decisec, 120, 80);
	}
	
	public void run( ) {
		try {  while ( runner == Thread.currentThread( ) ) {
				Calendar   now =
				   new GregorianCalendar( TimeZone.getTimeZone("JST") );
				current = now.getTimeInMillis( );
				repaint( );
				Thread.sleep( 100 );
			}
		} catch ( Exception err ) { }
	}
	
	public void actionPerformed( ActionEvent   ae ) {
		if ( b.getLabel( ).equals( "Start" ) ) {
			Calendar   origin =
			       new GregorianCalendar( TimeZone.getTimeZone("JST") );
			starttime = origin.getTimeInMillis( );
			runner = new Thread( this );
			runner.start( );
			b.setLabel( "Stop" );
		} else {
			runner = null;
			b.setLabel( "Start" );
		}
	}
}
	

☆アプリケーションで音声を取り扱う

Appletクラスにクラスメソッドとして、newAudioClipメソッドが用意されました。これを用いてアプリケーションでも音声を演奏することができます。以下の記述はネットワーク上の音声データを5秒間演奏するものです。

	try {
		URL url = new URL( "http://www.anysoundhouse.com/java/sounds/spacemusic.au" );
		AudioClip   ac  = Applet.newAudioClip(  url );
		ac.loop(  );
		Thread.sleep( 5000 );
		}  catch ( Exception e ) { System.err.println( e ); }

☆アプリケーションで画像を扱う場合

URLで画像ファイルの位置を指定します。Toolkitクラスのオブジェクト(getToolkitメソッドで得ることができます)のgetImageメソッドを使って画像ファイルを読み込んでくることができます。次のサンプルプログラムは、ローカルな画像ファイル(ユーザのフォルダにあるものとします)を指定して、表示させています。

import	java.awt.*; 
import	java.awt.image.*;  
import	java.net.*;

public class ImageSample extends Frame {
	Image	boyimage;
	ImageSample(  ) {
		super( "Image Sample" );
		try {
			URL url = new URL( "file:///" + System.getProperty( "user.dir" )+"/boy1.gif" );
			boyimage = getToolkit( ).getImage( url );
		}  catch( Exception e ) {
			System.err.println( e );
		}
		setSize( 300, 300 );
		setVisible( true );
	}
	public void paint( Graphics g ) {  g.drawImage( boyimage, 0, 0, this ); }
	public static void main( String [ ] args ) { new  ImageSample( ); }
}

11−3.ファイル入出力

アプリケーションでユーザから入力されたデータを保持しておきたい、あるいはいろいろな情報を外部からプログラムに入力したいというような場合があります。特に、これまではプログラム上で定数値として表していた情報を外部から入力するようにしておけば、後でそれらの値が変わっても、いちいちプログラムを再コンパイルする必要がなくなります。このようなデータとプログラムの分離は、プログラムのデータ独立(Data Independency)として提唱されてきました。これは、1つのプログラムが、一定のデータだけではなく、別のデータにも適用できることを意味しています。Javaの場合にはユーザからの入力を得るのと同じように、外部環境とのデータのやりとりには、一貫してストリーム(Stream)を用いることになっています。ストリームとは、指定された単位でアプリケーションにデータを入力したり、データを書き出したりするオブジェクトのことです。

また、ファイルからデータを入力する場合に、あらかじめどれくらいの件数が必要なのかわかりません。そのために、最初に要素の個数が固定されてしまう配列では対処できないことになります。ここでは、Javaのクラスとして用意されている要素の個数を増やしていけるArrayListを紹介します。

☆ReaderとWriter

Javaではプログラムに対して入力として与えられるストリームのことをReader、プログラムの出力としてのストリームのことをWriterと呼んでいます。これらのストリームは国際化対応になっていて、Unicodeベースの文字を入出力の単位として扱うことができます。Readerストリームを使えば、ファイルやWebページあるいは端末からのユーザの入力をプログラムに与えることができる。また、Writerストリームを用いれば、ファイルやユーザの端末にプログラムの処理結果を出力することができます。

Reader/Writerストリーム

これらのストリームに対応して、java.ioパッケージの中には、次のようなクラスが用意されています。他にも一杯クラスはあるのですが、ここでは説明に必要なクラスだけを列挙します。ここで挙げた6つのクラスは、いずれもデータをテキストファイルとして扱うものです。

Reader         入力のための抽象クラス
 InputStreamReader  1文字単位で読めるクラス
 BufferedReader    1行単位で読めるクラス
 FileReader       ファイルから読めるクラス

Writer         出力のための抽象クラス
 BufferedWriter    1行単位で出力するクラス
 FileWriter      ファイルに出力するクラス
 PrintWriter      文字列を出力するクラス

出力用のクラスは、BufferedWriterは、1行分を貯めてから出力するクラスです。 1文字単位で出力するよりも効率的になります。 2進数を直接扱うバイナリデータなどをファイルに出力するときは、FileWriterクラスを用います。 テキストデータを出力するときは、printlnやprintなどのお馴染のメソッドを持ち、 かつ日本語などにも対応しているPrintWriterに一旦変換してからファイルに出力ことができます。 入力用のクラスのBufferedReaderも、1行分をまとめて文字列として読み込むためのクラスになっています。 InputStreamReaderやFileReaderからクラスを変換する形で、利用します。

ファイルを使うときの手順では、次のように使う前にオープンし、使った後はクローズするのが一般的です。これは、コンピュータに現在どのファイルを使っているかを管理してもらうために行なうものです。オープンは、大抵の場合は、ファイルを表現するストリームクラスのオブジェクトを作成することによって実現します。

▼ファイルを扱うときの手順
1.ファイルをオープンする
2.ファイルに対して、読む(入力)あるいは書く(出力)
3.ファイルをクローズする

☆アプレットとストリーム

アプレットはその性質上、入出力の対象となるファイルは、クライアント上のファイルではなく、 Webサーバー上に置かれているファイルになります。しかしながら、アプレットではセキュリティ的な制約から、 任意のWebサーバーに対してのファイルへの入出力は許可されていません。 Webサーバー上の内部のファイルを読み書きできると、 第三者がアプレットを使ってWebサーバーに侵入することができるからです。

アプレットで可能なのは、元々そのアプレットが置かれていたWebサーバ上のWebページを読み出すことだけです。 これは、画像ファイルや音声ファイルのロードと同様に、 サーバ上のファイルを相対的に指定して行なうことができます。

★アプレットでWebページから入力を貰う

アプレットでは、URLを指定して、そこからWebページのデータをプログラムへ入力させることができます。 アプレットで、ファイルに出力することはできませんが、 Webサーバーにおいてある情報ファイルをデータ入力のために読み込むことは頻繁にあるでしょう。 Webページからのデータ入力の場合は、次のように行なっていきます。

1.URLを指定する。
2.指定したURLに対してストリームをオープンする
3.データを入力する
4.ストリームをクローズする

URLの指定の仕方ですが、セキュリティ上の問題からクラスファイルが置かれている Webサーバだけのアクセスに限りたいと思います。 そのため、一般的なURLの指定ではなく、Appletのクラスファイルからの相対的な指定の方法だけを 使うようにします。

URL urladdress = new URL( getCodeBase( ), "project/infodata" ); // クラスファイル相対

ストリームのオープンは、URLクラスのオブジェクトは、openStreamメソッドを使うことによって行なうことができます。

InputStream istream = urladdress.openStream( );

上記の記述のように、openStreamメソッドは、System.inと同じInputStreamのオブジェクト(ストリーム)を返してくれます。これをBufferedReaderにするには、一旦InputStreamReaderのオブジェクトを介して変換します。これを、openStreamメソッドと一緒に行ないますと、次のような記述となります。

BufferedReader br = new BufferedReader( new InputStreamReader( urladdress.openStream( ) ) );

もし、読込むテキストファイルのエンコードを指定する場合には、以下のようにエンコードを文字列で 指定します。

BufferedReader br = new BufferedReader( new InputStreamReader( urladdress.openStream( ), "エンコード名" ) );

エンコード名としては、次のような名前を用いることができます。 詳しくは、IANAのページに定義されています。

名前意味
US-ASCII7 ビット ASCII
ISO-8859-1ISO Latin Alphabet No. 1
UTF-88 ビット UCS 変換形式
UTF-1616 ビット UCS 変換形式
ISO-2022-JPJIS X0208コード
Shift_JISJIS X0208に基づくShift JISコード
EUC-JPJIS X0208に基づくEUCコード

後は、次のような3つのメソッドを利用することができます。

シグネチャ機能
String readLine( )一行読み込み、文字列として返す
boolean ready( )まだストリームからデータを貰えるかどうか返す
close( )ストリームを閉じる

アプレットの置かれているWebサーバ上からテキストデータを読み出すには、 BufferedReaderクラスのオブジェクトを作り、readLineメソッドを使って、データ入力し、 入力し終わったら、closeメソッドでストリームを閉じるという方法を採ります。 なお、入出力関係のメソッドは、例外を発生させますので、try〜catch構文の中で使います。 また、URLクラスはjava.netパッケージに、BufferedReaderクラスはjava.ioパッケージクラスにありますので、 import文を使ってパッケージのクラスを使うことを示す必要があります。

★ファイルの内容をテキストエリアに読みだす

次のアプレットプログラムは、テキストエリアに、infoというファイル名のファイルから読み込んだ内容を文字列として、表示するものです。1行読み出しては、appendメソッドを使って、テキストエリアに内容を追加していきます。追加するときに、改行も同時に挿入していることに注意してください。

import java.awt.*;
import java.applet.*;
import java.net.*;
import java.io.*;

public class FileViewerApplet  extends Applet  {
	TextArea    area;
        
	public void init() {
		area = new TextArea( 20, 40 );
		area.setFont( new Font( "SansSerif", Font.PLAIN, 18 ) );
		add( area );
		try {
			URL   addr = new URL( getCodeBase( ), "info" );
			BufferedReader   br = new BufferedReader( new InputStreamReader( addr.openStream( ), "UTF-8" ) );
			while ( br.ready( ) ) {
				String  line = br.readLine( );
				area.append( line + "\n" );
			}
			br.close( );
		} catch( Exception  exc ) {
			exc.printStackTrace( );
		}
	}
}

★情報ファイルを配列に読み込む

さまざまなデータから構成される情報ファイルを扱います。 まず情報ファイルに書かれている内容を配列に読み込んでみましょう。 ここでもファイルはテキストファイルで保存されているとします。 ファイル(ファイル名をinfoとします)の内容が次のような名前とその人の給与、 という2行で1組になっているデータの連続で構成されているとしましょう。

▼情報ファイルの構成の例
	Robin Milner			// 1行目には人の名前が書いてある
	350000				//  2行目にはその人の給料が書いてある
	Brian Smith			//  同じように次の人の名前が書いてある
	420000				// 次の人の給与
	   :				//  これが延々と続いていく

2行単位でファイルが構成されていると仮定しまして、これを配列に読み込んでみます。 情報ファイルの1レコードの内容を直接表わすためのクラスを定義し、 ArrayListに読み込むためのプログラムの断片は、次のようになります。 給与の方は、文字列として1行を読み込んだ後、Interger.parseIntメソッドを呼び出して整数に変換しています。

//  従業員クラスの定義(情報ファイルの2行分の内容を表わす)
class Employee  {
	String	name;
	int	salary;
}

//  アプレットのインスタンス変数として用意するもの
ArrayList<Employee> staff = new ArrayList<Employee>;	// 情報を保持するArrayList

// 実際に読み込む部分だけを記述
BufferedReader   br = new BufferedReader( new InputStreamReader( addr.openStream( ), "UTF-8" ) );
for ( int count = 0;   br.ready( ) ;  count++  ) {
	Employee person = new  Employee( );
	person.name = br.readLine();
	person.salary = Integer.parseInt( br.readLine( ) );
	staff.add( person );
}
br.close( );

★テキストフィールドを2つ用意して、そこに情報ファイルから読み込んだ内容を表示させる

2行を1人の情報として構成されているinfoという名前の情報ファイルが、 Webサーバー上のHTMLファイルと同じフォルダ(ディレクトリ)にあるとしましょう。 このファイルを読み込んで、最初の人物の情報(名前とその給与)を表示するようなアプレットを以下に示します。 2つのテキストフィールドを用意して、1人分の名前と給与を表示しています。

import	java.awt.*;
import	java.awt.event.*;
import	java.util.*;
import	java.applet.*;
import	java.net.*;
import	java.io.*;

class Employee  {
	String	name;
	int	salary;
}

public class InfoViewer extends Applet implements ActionListener {

	ArrayList<Employee> staff = new ArrayList<Employee>( );
	TextField  namefield;
	TextField  salaryfield;
	Button b;
	int current = 0;
	
	public void init(  ) {
		try {
			URL  url=  new URL(  getCodeBase( ), "info" );
			BufferedReader br = new BufferedReader( new InputStreamReader(  url.openStream( ), "UTF-8"  ) );

			while(  br.ready( )  ) {
				Employee person = new  Employee( );
				person.name = br.readLine();
				person.salary = Integer.parseInt( br.readLine( ) );
				staff.add( person );
			}
			br.close( );

			namefield =  new TextField( 30 );
			salaryfield =  new TextField( 10 );
			b = new Button( "Next" );
			b.addActionListener( this );

			namefield.setText( staff.get( current ).name );   
			salaryfield.setText(  staff.get( current ).salary+"" );
			add( namefield );
			add( salaryfield  );
			add( b );
		} catch( Exception  e ) {
			System.err.println( e ); 
		}
	}
	
	public void actionPerformed( ActionEvent ae ) {
		current = (current + 1) % staff.size( );
		namefield.setText( staff.get( current ).name );   
		salaryfield.setText(  staff.get( current ).salary+"" );
		repaint( );
	}
}
アプレットによる情報ファイルの表示例

■アプリケーションでファイルを取り扱う

アプリケーションの場合は、ローカルなコンピュータで実行されますので、 セキュリティ的な制約がありません。 そのため、読み書きの権限が与えられているファイルに対して、読み書きが自由に行なえます。 ファイルの読み込みは、バイナリファイルにも可能ですが、ここではテキストファイルに限定して紹介します。 ファイルの読み込みにはFileReaderとBufferedReaderクラスのオブジェクト、 ファイルの書き込みにはFileWriterとPrintWriterクラスのオブジェクトを使います。

★ファイルの読込み

読む場合は、以下のように指定をします。エンコードを指定する場合は、FileInputStreamクラスを 用いて、一度InputStreamReaderを介してBufferedReaderを作成します。

new BufferedReader( new FileReader( "ファイル名" ) )
new BufferedReader( new InputStreamReader( new FileInputStream( "ファイル名" ), "エンコード名" )

たとえば、ローカルなtestという名前のファイルを開くには、次のように記述します。 このファイルは、実行されるクラスファイルと同じフォルダにあるものとします。 下の方の記述は、エンコードを指定する場合です。

BufferedReader  br = new BufferedReader( new FileReader( "test" ) );
BufferedReader  br = new BufferedReader( new InputStreamReader( new FileInputStream( "test" ), "UTF-8" ) );

FileReaderは、標準のファイル入出力のエンコーディングを使います。ローカライズされたBlueJでは、UTF-8を 標準にしています。 また、次のような設定を行なうことによって、 アプリケーションでの標準の入出力のエンコーディングを指定することができます。 ただし、アプレットビューワでの標準エンコーディングは指定できないようです。

	System.setProperty( "file.encoding", "UTF-8" );

読み込みモードでファイルをオープンした後は、 BufferedReaderクラスのreadLineメソッドを使って1行ずつ読み込むことができます。

★ファイルへの書込み

ファイルの書込みのために、ファイルを開く方法は次のように指定できます。

new PrintWriter( "ファイル名", "エンコード名" )
new PrintWriter( new FileWriter( "ファイル名", true ) ) // 追記のとき

以下の記述では、共に、infoという名前のファイルにデータを書き出すことができるのですが、 FileWriterに2番目のパラメータを追加してtrueにした場合は、 ファイルの最後にデータを追加します。 その場合は、元のファイルのエンコードにあわせてくれると思います。 エンコードがシステム標準のものでよければ(日本語ではShift_JISになる可能性が高い)、 エンコードの指定を省略することができます。

PrintWirter  pw = new PrintWriter(  "info" );
PrintWirter  pw = new PrintWriter(  new FileWriter( "info", true ) );  // ファイルの最後に追加する
PrintWirter  pw = new PrintWriter(  "info", "UTF-8" ); // エンコードを指定

PrintWriterクラスのオブジェクトは、printやprintln、あるいはprintfという名前のメソッドを持っており、 これらはSystem.out.printlnや、System.out.printfなどで使ってきた使い方と同じです。

pw.print( "Number " );
pw.printf( "%04d: ",  637 );
pw.println(  "Sample output data for using local file."  );
pw.println(  "data: " +  5 * 40  + " mm " );
pw.close( );

なお、アプリケーションでは、取り扱うファイルを選択するためのダイアログを出すこともできます。 これも、java.awtパッケージのクラスで、読み込み用と書き込み用で2つのタイプが用意されています。 以下の指定でそのアプリケーションがウィンドウを持たない場合は、 new Frame( )などでダミーの親ウィンドウを指定します。 タイトルは、文字列で与えます。 Mac OS Xの場合は、裏に余分なダイアログが重複して表示される場合がありますが、手前の ダイアログで入力して、後ろのダイアログは、「キャンセル」ボタンを押して下さい。

new FileDialog( 親ウィンドウ, タイトル, FileDialog.LOAD ); // ファイル読み込み用
new FileDialog( 親ウィンドウ, タイトル, FileDialog.SAVE ); // ファイル書き込み用

3番目のパラメータが省略されたときは、読み込み用になります。 ファイルダイアログを表示するためには、setVisible( true )というメソッドを用います。 更に、このクラスのオブジェクトには、次のようなフォルダとファイル名を返すメソッドがあり、 両方を使うことで、選択されたファイルの絶対パスを得ることができます。

String getDirectory( ) // そのファイルのあるフォルダへの絶対パスを返します
String getFile( ) // そのファイルの名前を返します

以下のプログラムは、ユーザに指定されたファイルに、データを書き出すものです。 もし、既存のファイルがあれば、最後に追加する形でデータを書き出します。

import   java.io.*;
import   java.awt.*;

public class DataWriter {
	public static void main( String args [ ] ) {
		String	name[ ] =  { "James Gosling", "Bill Joy", "Guy Steele" };
		int	salary[ ] = { 75000, 99000, 54000 };
		FileDialog   fd = new FileDialog( new Frame( ),  "Select File",  FileDialog.SAVE );  
		fd.setVisible( true );
		String	directory =  fd.getDirectory( );
		String	filename =  fd.getFile( );
		try {
			PrintWriter  pw = new PrintWriter(  new FileWriter( directory+filename , true ) );
			for ( int i=0; i < name.length;  i++ ) {
				pw.println(  name[ i ]  );
				pw.println(  salary[ i ] );
			}
			pw.close( );
		} catch( Exception  e ) {   System.err.println( e );   }
	}
}