■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
                      2007年07月08日

    楽しいJava講座 - 初心者から達人へのパスポート
                  vol.061

                                セルゲイ・ランダウ
 バックナンバー: http://www.flsi.co.jp/Java_text/
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


[このメールマガジンは、画面を最大化して見てください。]


========================================================
◆ 01.グラフィックスのプログラミング
========================================================

今回は、描いた絵をファイルに保存する機能と保存したファイルを
開く機能を追加しましょう。

その前に画像のファイルの形式について簡単に説明しておきます。

画像のデータやファイルには、大きく分けて2種類の形式(format)
があり、ひとつはラスター・グラフィックス(raster graphics)と
呼ばれ、もうひとつはベクター・グラフィックス(vector graphics)
と呼ばれています。

ラスター・グラフィックスは、画像を色のついた点の集まりとして記憶
する形式です。写真などは通常、この形式のファイルとして保管されま
す。
(ちなみに、新聞や雑誌に掲載されている写真も虫眼鏡で拡大してみる
と点の集まりにすぎないことがわかりますね。テレビやディスプレイの
画像も虫眼鏡で拡大してみると点の集まりにすぎないことがわかります。)

一方、ベクター・グラフィックスは、これまでに図を描いてきたときの
ように図形の位置や大きさを示す数字や図形の種類、色の種類といった
データを記憶する形式です。
典型的な例としてはCAD(Computer Aided Design)で作成する製図の
図面などがこの形式のファイルとして保管されますが、その他のお絵描
きソフトでもこの形式でファイルを保管するものがあります。

このうち、今回はラスター・グラフィックスの形式を使用します。

なお、GraffitiPanelで図を描くときに使用する各図形のオブジェクト
(DecoratedLinesやDecoratedShapeの各サブクラス)はベクター・グラ
フィックスの形式でデータを(メモリー上に)記憶していますから、こ
れを利用してベクター形式のままファイルに保管することも容易にでき
ます。
しかし、ベクター形式のままファイルに保管する方法については、別の
機会にもっと高度なグラフィックス・アプリケーションの作り方をお話
しながら説明することにします。


さて、ラスター・グラフィックスのファイル形式には、Windows環境で
よく使用されるものとしてビットマップ(bitmap)があります。
このファイルは基本的には圧縮されていません(最近では圧縮されてい
るものもあるが圧縮率は低い)。
ビットマップのファイルは、拡張子に.BMPまたは.bmpがつきます。

その他、最近よく使われるようになってきたファイル形式には、JPEG
(Joint Photographic Experts Group)とPNG(Portable Network
Graphics)があります。

JPEGはラスター・グラフィックスのデータを大幅に圧縮することができ
る形式で、できるだけ小さいサイズのデータが好まれるインターネット
の世界ではよく使われています。
圧縮率を高くするとその分画質が劣化するため、一般のグラフィックス
のファイル保存には好ましくありませんが、写真画像では劣化が目立た
ないためよく使われます。(写真のための形式と考えると好都合です。)
JPEGのファイルは、拡張子に.JPEGまたは.jpegまたは.JPGまたは.jpgまた
は.JPEまたは.jpeがつきます。

PNGは元々インターネットの世界でよく使われていたGIF(Graphic
Interchange Format)という形式に対抗して(GIFにはライセンス料金と
いううるさい問題がつきまとっているので)作られた形式で、GIFと同じ
ように透明色を指定して背景イメージとの重ねあわせができるなどの機能
を持つ上に、GIFよりも圧縮率が高くなっています。
PNGのファイルは、拡張子に.PNGまたは.pngがつきます。

他にも、さまざまな形式がありますが、省略します。


これらの形式のうち、今回のアプリケーションではJPEGのファイルにして
保存することにします。

描いた絵をファイルに保存するには、ImageIOというクラスのwrite()メソ
ッドを使用します。
(ImageIOクラスはjavax.imageioパッケージにはいっています。)

描いた絵をJPEGのファイルにして保存するには、RenderedImageというイン
ターフェースを実装したオブジェクトに描画しておいて、それを
--------------------------------------------------------
ImageIO.write(RenderedImage im, "jpg", File outputFile)
--------------------------------------------------------
という形式のメソッド呼び出しによって、ファイルに書き込むことができ
ます。
(RenderedImageはjava.awt.imageパッケージにはいっています。)

ここで、Fileはファイルを表現するクラスで、java.ioパッケージにはいっ
ています。
(write()メソッドには他の型の引数を持つものもあります。)

このwrite()メソッドの第二引数には、作成したいファイルの形式に応じて
以下のような文字列が指定できます。

JPEG --> 第二引数は"JPEG"または"jpeg"または"JPG"または"jpg"を指定する。
PNG --> 第二引数は"PNG"または"png"を指定する。
ビットマップ --> 第二引数は"BMP"または"bmp"を指定する。

ただし、使用できる形式はプログラムの実行環境によっても異なりますので、
正確にはImageIO.getWriterFormatNames()を実行して調べる必要があります。

RenderedImageを実装したクラスとしてはBufferedImageというクラスがあり
ます。これはImageのサブクラスです。したがってImageのメソッドがそのまま
使えます。よってImageのgetGraphics()メソッドも使えます。
(BufferedImageはjava.awt.imageパッケージにはいっています。)

たとえば
--------------------------------------------------------
BufferedImage rendImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = rendImage.getGraphics();
--------------------------------------------------------
というふうにして生成したBufferedImageオブジェクトからGraphicsオブジェ
クトを取り出しておき、その後Graphicsオブジェクトに対して描画を行って
おきます。
そうすると、その後
--------------------------------------------------------
File file = new File("newimage.jpg"); // (newimage.jpgはファイル名)
ImageIO.write(rendImage, "jpg", file);
--------------------------------------------------------
のようにして、描いた絵をファイルに書き込むことができます。


では、さっそくプログラミング作業に入っていきましょう。
Eclipseを起動し、GraffitiPanelのエディターを開いてください。

GraffitiPanelのpaint()メソッドのソース・コードを見てください。
先頭の
--------------------------------------------------------
Image image = createImage(300, 200);
--------------------------------------------------------
によってImageオブジェクトを取り出した後、さらにそこからGraphicsオブジェ
クトを取り出して描画を行っていますね。
BufferedImageはImageのサブクラスなので、Imageの振る舞いも継承しています。
そこで、上記のImageオブジェクトに対して行っていた処理部分を抜き出して
BufferedImageオブジェクトに対して行わせるようにもできます。

はい、やってみましょう。

まずは、Imageオブジェクトに対して行っていた部分を抜き出して別のメソッ
ドにしましょう。

paint()メソッドの
--------------------------------------------------------
Graphics2D g2dImage = (Graphics2D) image.getGraphics();
--------------------------------------------------------
の行から最後のほうの
--------------------------------------------------------
for (DecoratedShape shape : shapes) {
   shape.paintSelf(g2dImage);
}
--------------------------------------------------------
の行までをいっきょに選択状態にして、メニュー・バーの「リファクタリング」
→「メソッドの抽出」を選択し、「メソッドの抽出」ウインドウの「メソッド名」
欄にpaintImageと入力し、「アクセス修飾子」はpublicを選択して、「OK」ボタ
ンをクリックしましょう。(paintImageの代わりに他の名前を入力してもよいが
わかりやすい名前にすべし。)

paintImage()メソッドが自動的に作られ、選択していた複数の行がこちらに移り
ましたね。


念のためにpaintImage()メソッドの全容を下に提示しておきます。
--------------------------------------------------------
public void paintImage(Image image) {
   Graphics2D g2dImage = (Graphics2D) image.getGraphics();
   g2dImage.drawImage(getJpegImage(), 0, 0, 300, 200, null);
     
   g2dImage.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

   DecoratedShape aShape = null;
   DecoratedLines aDecoLines = null;

   if(freeCurveFlag) {
      aDecoLines = new DecoratedLines();

      for(int loopIndex = 0; loopIndex < ptIndex - 1; loopIndex++) {
         aDecoLines.addLine(new Line2D.Float(pts[loopIndex].x, pts[loopIndex].y, pts[loopIndex+1].x, pts[loopIndex+1].y));
      }
      aDecoLines.setColor(lineColor);
      aDecoLines.setStroke(drawStroke);
   }
   else if(endPoint != null) {
      if (lineFlag) {
         aShape = new DecoratedLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y);
      }
      else {
         Point tempStartPoint;
         Point tempEndPoint;
         int drawWidth;
         int drawHeight;
         tempEndPoint = new Point(Math.max(endPoint.x, startPoint.x), Math.max(endPoint.y, startPoint.y));
         tempStartPoint = new Point(Math.min(endPoint.x, startPoint.x), Math.min(endPoint.y, startPoint.y));
         drawWidth = tempEndPoint.x - tempStartPoint.x;
         drawHeight = tempEndPoint.y - tempStartPoint.y;
         if(ellipseFlag) {
            aShape = new DecoratedEllipse(tempStartPoint.x, tempStartPoint.y, drawWidth, drawHeight);
         }
         else if(rectFlag) {
            aShape = new DecoratedRectangle(tempStartPoint.x, tempStartPoint.y, drawWidth, drawHeight);
         }
      }
      aShape.setColor(lineColor);
      aShape.setStroke(drawStroke);
   }

   if (aDecoLines != null) {
      if (!mousePressed) {
         freeCurves.add(aDecoLines);
         ptIndex = 0;
      }
      else {
         aDecoLines.paintSelf(g2dImage);
      }
   }
   if (aShape != null) {
      if (!mousePressed) {
         shapes.add(aShape);
      }
      else {
         aShape.paintSelf(g2dImage);
      }
   }

   for (DecoratedLines lines : freeCurves) {
      lines.paintSelf(g2dImage);
   }

   for (DecoratedShape shape : shapes) {
      shape.paintSelf(g2dImage);
   }
}
--------------------------------------------------------

また、元のpaint()メソッドのほうもずいぶん簡単なコードに変わったと思い
ますが、念のために下にそのソース・コードを提示しておきます。
--------------------------------------------------------
public void paint(Graphics g) {
Image image = createImage(300, 200);
paintImage(image);
g.drawImage(image, 0, 0, 300, 200, null);
}
--------------------------------------------------------


すると、paintImage()メソッドにBufferedImageオブジェクトを引数として
渡して呼び出してやれば、描かれた絵をファイルに書き出すことが可能に
なります。

では、GraffitiJFrameのほうにそのメソッドを用意しましょう。

GraffitiJFrameのソース・コードに以下のメソッドを追加してください。
--------------------------------------------------------
public void save(String fileName) {
   BufferedImage rendImage = new BufferedImage(300, 200, BufferedImage.TYPE_INT_RGB);
   getGraffitiPanel().paintImage(rendImage);
   File file = new File(fileName);
   try {
      ImageIO.write(rendImage, "jpg", file);
   } catch (IOException e) {
      e.printStackTrace();
   }
}
--------------------------------------------------------
なお、このImageIOのwrite()メソッドでは、もし既に同名のファイルが存在
していると、それを上書きしてしまいます。
勝手に上書きされては困る場合は、ファイルが既存であるかどうかを予めチェ
ックして、既存であれば上書きするかそれともキャンセルするかを聞いてくる
ようにロジックを組んでおく必要があります。ファイルが既存かどうかを確認
するにはFileのexists()メソッドが使用できます。余力のある人は、自分で
ロジックを組み込んでみてください。


次に、GraffitiJFrameの「ファイル」メニューに保存のメニュー項目を追加
しましょう。

パレットからjMenuItemを取り出し、「Java Beans」ビュー(最大化しておく)
の中のjMenuFileをクリックします。Bean NameはjMenuItemSaveにしておきま
しょう。
textプロパティーの値を「名前をつけてJPEG保存」にしておきましょう。


では、このメニュー項目からJFileChooser(vol.017でお話したFileDialogの
Swing版)を呼び出したあと、さきほどのsave()メソッドを呼び出すように
プログラミングしたいと思います。

JFileChooserはFileDialogと同じようにファイル名を入力させたり、リスト
からファイルを選択させたりするウインドウです。

このとき、JFileChooserのウインドウにリストされるファイルをJPEGのもの
(拡張子が.jpgのもの)だけに絞り込むようにしておきたいので、そのため
にFileFilterのサブクラスを作っておきましょう。
(JFileChooserもFileFilterもともにjavax.swing.filechooserパッケージの
中にはいっています。)

FileFilterは文字通りファイルのリストをフィルタリングするためのクラス
です。
このクラスは抽象クラスであり、必ずサブクラスを作ることによって使用する
必要があります。

以下のようなFileFilterのサブクラスをjp.co.flsi.lecture.cgパッケージの
中にJpgFilterというクラス名で作ってください。
--------------------------------------------------------
package jp.co.flsi.lecture.cg;

import java.io.File;
import javax.swing.filechooser.FileFilter;

class JpgFilter extends FileFilter {
   public boolean accept(File f) {
      String filename = f.getName();
      return filename.endsWith(".jpg") || f.isDirectory();
   }
   public String getDescription() {
      return "*.jpg";
   }
}
--------------------------------------------------------
ここで、accept()メソッドは引数に指定されたファイル(ディレクトリも含む)
がリストに表示すべきファイルかどうかを判断するためのメソッドです。
ここでは、拡張子.jpgがついているか、またはディレクトリー(フォルダー)の
場合のみリストに表示するようにプログラミングしています。
また、getDescription()はこのフィルタリングを説明する文字列で、Windowsの
ダイアログの場合は「ファイルタイプ(ファイルの種類)」欄に表示されるもの
です。


では、保存のメニュー項目からJFileChooserやさきほどのsave()メソッドを呼び
出せるようにプログラミングしましょう。

(1)「Java Beans」ビューの中のjMenuItemSaveを右クリックし、「Events」→
「actionPerformed」を選択します。

(2) getJMenuItemSave()メソッドの中に次のコードが自動生成されましたね。
--------------------------------------------------------
jMenuItemSave.addActionListener(new java.awt.event.ActionListener() {
   public void actionPerformed(java.awt.event.ActionEvent e) {
      System.out.println("actionPerformed()"); // TODO Auto-generated Event stub actionPerformed()
   }
});
--------------------------------------------------------

これを次のように編集しましょう。
--------------------------------------------------------
jMenuItemSave.addActionListener(new java.awt.event.ActionListener() {
   public void actionPerformed(java.awt.event.ActionEvent e) {
      JFileChooser fileChooser = new JFileChooser();
      fileChooser.addChoosableFileFilter(new JpgFilter());
      fileChooser.showSaveDialog(jMenuItemSave);
      File file = fileChooser.getSelectedFile();
      if (file != null) {
         String filePathName = file.getPath();
         save(filePathName);
      }
   }
});
--------------------------------------------------------

簡単に解説しておきましょう。
上のソース・コードを見ればわかることですが、さきほど作成したFileFilterの
サブクラスは、JFileChooserのaddChoosableFileFilter()メソッドを呼び出すこと
によってJFileChooserオブジェクトに登録します。
また、JFileChooserのウインドウのうちファイルの保存用のダイアログを表示する
にはshowSaveDialog()を使用します。
また、このダイアログにファイル名が入力された、または、リストから選択された
ファイルを取り出すには、getSelectedFile()メソッドを使用します。
また、FileオブジェクトのgetPath()メソッドによって、パス名(ディレクトリー名
とファイル名の組み合わせ)を取り出すことができます。


これで、描いた絵をJPEGファイルに保存することができるようになりました。



では、次に保存したファイルを開く機能をプログラミングしましょう。

今度は
--------------------------------------------------------
ImageIO.read(File inputFile)
--------------------------------------------------------
というメソッドを使ってファイルから画像を読み取ります。
このreadメソッドの戻り値はBufferedImageなので、そのBufferedImageオブジェクト
をGraphicsに描きだしてやる必要があります。そのためにはGraphicsのdrawImage()
メソッドを使用します。
ただし、毎回毎回paint()メソッドが呼び出されるたびにdrawImage()メソッドで
描画する必要がありますから、BufferedImageオブジェクトをフィールドにして
メモリー上に留めておきます。

というわけで、BufferedImageのフィールドを追加しましょう。
下記のフィールドをGraffitiPanelのソース・コードに追加してください。
--------------------------------------------------------
private BufferedImage jpegImage = null;
--------------------------------------------------------

このフィールドにアクセスするための次のsetter、getterを追加しましょう。
--------------------------------------------------------
public void setJpegImage(BufferedImage jpegImage) {
   this.jpegImage = jpegImage;
   repaint();
}

public BufferedImage getJpegImage() {
   if (jpegImage == null) {
      jpegImage = new BufferedImage(300, 200, BufferedImage.TYPE_INT_RGB);
      Graphics g = jpegImage.getGraphics();
      g.setColor(Color.WHITE);
      g.fillRect(0, 0, 300, 200);
   }
   return jpegImage;
}
--------------------------------------------------------
このgetter(getメソッド)では、jpegImageフィールドが空のときには真っ白に
塗りつぶした画像を入れています。これはこれから描く絵の背景になります。
(いままで絵を描いていたときには背景がなかったのです。)
逆にsetter(setメソッド)によって画像をセットし直すと、それが新しい背景に
なります。


次に、GraffitiPanelのpaintImage()メソッドの中の先頭の次の行、つまり
--------------------------------------------------------
Graphics2D g2dImage = (Graphics2D) image.getGraphics();
--------------------------------------------------------
の行の下に
--------------------------------------------------------
g2dImage.drawImage(getJpegImage(), 0, 0, 300, 200, null);
--------------------------------------------------------
という行を追加してください。
こうすると、paintImage()メソッドの中のしょっぱなでgetJpegImage()で取り出
した画像をg2dImageに描いているわけですから、これから描く絵はその上にかぶ
せられます。つまりgetJpegImage()で取り出した画像は背景になります。


では、今度はGraffitiJFrameのソース・コードのほうに、ファイルを開くための
作業を行うための次のメソッドを追加しましょう。

--------------------------------------------------------
public void load(String fileName) {
   try {
      getGraffitiPanel().setJpegImage(ImageIO.read(new File(fileName)));
   } catch (IOException e) {
      e.printStackTrace();
   }
}
--------------------------------------------------------


続いて、ファイルを開くためのメニュー項目を追加しましょう。

パレットからjMenuItemを取り出し、「Java Beans」ビュー(最大化しておく)
の中のjMenuFileをクリックします。Bean NameはjMenuItemOpenにしておきま
しょう。
textプロパティーの値を「JPEGファイルを開く」にしておきましょう。

では、このメニュー項目からJFileChooserやさきほどのload()メソッドを呼び
出せるようにプログラミングしましょう。

(1)「Java Beans」ビューの中のjMenuItemOpenを右クリックし、「Events」→
「actionPerformed」を選択します。

(2) getJMenuItemOpen()メソッドの中に次のコードが自動生成されましたね。
--------------------------------------------------------
jMenuItemOpen.addActionListener(new java.awt.event.ActionListener() {
   public void actionPerformed(java.awt.event.ActionEvent e) {
      System.out.println("actionPerformed()"); // TODO Auto-generated Event stub actionPerformed()
   }
});
--------------------------------------------------------

これを次のように編集しましょう。
--------------------------------------------------------
jMenuItemOpen.addActionListener(new java.awt.event.ActionListener() {
   public void actionPerformed(java.awt.event.ActionEvent e) {
         JFileChooser fileChooser = new JFileChooser();
         fileChooser.addChoosableFileFilter(new JpgFilter());
         fileChooser.showOpenDialog(jMenuItemOpen);
         File file = fileChooser.getSelectedFile();
         if (file != null) {
            String filePathName = file.getPath();
            load(filePathName);
         }
   }
});
--------------------------------------------------------


ちょっと、メニュー項目の順番が変ですね。直しておきましょう。
「Java Beans」ビューを最大化しておいてから、「Java Beans」ビューの中で
jMenuItemOpenをドラッグしてjMenuItemSaveの上にドロップ(マウスのボタン
を離す)してください。
次にjMenuItemPrintをjMenuItemSaveの下にドラッグしてドロップしてください。
以上で、メニュー項目が「JPEGファイルを開く」、「名前をつけてJPEG保存」、
「印刷」の順番に並ぶようになります。「Java Beans」ビューでの順番通りに
並ぶのです。


これでファイルに保存したりファイルを開いたりする機能が完成しました。


ところで、これまでに見てきたSwingのルック&フィール(Look & Feel:vol.036参照)
はJava独自のメタル・ルック&フィールと呼ばれるもの(これがデフォルト)です。
これはWindowsのルック&フィールとは異なるので、多くの人にはちょっと馴染み
にくいかもしれません。
その場合は、GraffitiJFrame()コンストラクターの最後などに
--------------------------------------------------------
try {
   UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
   SwingUtilities.updateComponentTreeUI(this);
} catch (ClassNotFoundException e) {
   e.printStackTrace();
} catch (InstantiationException e) {
   e.printStackTrace();
} catch (IllegalAccessException e) {
   e.printStackTrace();
} catch (UnsupportedLookAndFeelException e) {
   e.printStackTrace();
}
--------------------------------------------------------

というコードを加えてみてください。ルック&フィールがその環境のもの
(Windowsの環境ならWindowsのルック&フィール)に変わります。

とくにJFileChooserのウインドウなどはメタル・ルック&フィールでは見苦しい
(筆者の個人的な感覚ですが)と思うので、このようにルック&フィールを変えた
ほうがいいでしょう。


では、保管したあと、GraffitiJFrameを起動してテストしてみましょう。
絵を描いてファイルへ保存したり、保存したファイルを開いたりしてみてください。
なお、ファイルを保存するときは、ファイル名には必ず.jpgという拡張子をつけて
ください。


どうですか。うまくいきましたか。
何か、わからないところがありましたら、下記のWebページまで質問をお寄せ
ください。


今回はここまで。


(続く)


以上、今回は
┌───────────────────────────┐
・ラスター・グラフィックスのファイルの保存の仕方と開き方
 (特にJPEGに保存する方法と、ファイルを開いて画像表示する方法)
・JFileChooserとFileFilterの使い方
・メニュー項目の順番の並べ替え方
・Swingのルック&フィールを環境に合わせる方法
└───────────────────────────┘
を学習しました。
では、また来週。



========================================================
◆ 02.文法解説 [スレッド(Thread)]
========================================================

[マルチスレッドとスレッド(Thread)]

通常、プログラムの中に書かれた命令は一つずつ順番に実行されてい
きます。これは、あたかも命令が一本の糸に数珠状に連なっていて、
糸をたどって一つずつ実行しているようなイメージになります。

ところが、ある時点から同時に複数の糸をたどって複数の命令を実行
していきたい場合があります。
つまり、あたかも糸が途中から2本あるいは複数本に分かれていて、
そこから先は同時に複数の糸をたどって複数の命令を並行して実行し
ていくというようなイメージです。
このような実行形態をマルチスレッド(multi-thread)といいます。
multiは「複数」の意味で、threadは「糸」の意味です。
そしてマルチスレッドにおける命令の糸一本をスレッド(thread)と
いいます。

ちなみに、マルチスレッドでない普通のプログラムをシングルスレッド
(single thread)と呼ぶことがあります。

なお、マルチスレッドというのは、あくまで一つのプログラムの中での
話です。

複数のプログラムが同時に実行されることは、マルチタスク(multitasking)
とかマルチプロセス(multi-process)という別の言葉で呼びます。

一般に、マルチスレッドはマルチタスクよりもはるかに少ないオーバーヘ
ッド(overhead=余計な負荷=ここではメモリーやCPUを余計に使用すること)
で複数の命令を同時に実行することができます。

┌補足─────────────────────────┐
ハードウエアのレベルで言うと、通常の一つのCPUしか持たない
コンピューターでは、物理的には一時点に一つの命令だけしか
実行できません。
その場合でも、時間を細かく分割して一瞬一瞬の間にスレッドや
プログラムを素早く交互に切り替えて実行することにより、同時
に複数のスレッドやプログラムを実行しているように見せかける
ことができます。
マルチタスクやマルチスレッドといった場合も通常はこの方法で
実行されます。
└───────────────────────────┘


Javaではスレッドを表現するThreadというクラスが用意されており、マルチ
スレッドのプログラムを簡単に作ることができます。
Javaでマルチスレッドのプログラムを作るには以下の2種類の方法があり
ます。

(1) Runnableインターフェースをimplementsする。

Runnableインターフェースはrun()というメソッドを持っています。
Runnableインターフェースを実装したクラスを作り、Threadのコンストラ
クターの引数にそのクラスのインスタンスを指定してThreadオブジェクト
を生成し、そのThreadオブジェクトのstart()メソッドを呼び出すと、その
クラスのrun()メソッドが別のスレッドで実行されることになります。

たとえば
--------------------------------------------------------
public class Runner implements Runnable {

   public void run() {
      for (int i = 0; i < 10; i++) {
         try {
            Thread.sleep(100);  // 100ミリ秒休止する
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System.out.println("私はスレッド1ランナーです。現在" + (i + 1) + "0mに達しました。");
      }
      System.out.println("スレッド1ランナーはゴールインしました。");
   }

   public static void main(String[] args) {
      Thread thread1 = new Thread(new Runner());
      thread1.start();
      for (int i = 0; i < 10; i++) {
         try {
            Thread.sleep(150);  // 100ミリ秒休止する
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System.out.println("私はスレッド0ランナーです。現在" + (i + 1) + "0mに達しました。");
      }
      System.out.println("スレッド0ランナーはゴールインしました。");
   }

}
--------------------------------------------------------
というようなプログラムを作って実行すると、「私はスレッド0ランナー
です。・・・・」というメッセージがforループで10回繰り返し書き出さ
れるのと同時並行して、「私はスレッド1ランナーです。・・・・」という
メッセージもforループで10回繰り返し書き出されることになります。
(上記のプログラムの中のsleep()というメソッドは引数に指定した時間
(ミリ秒単位)だけプログラム(スレッド)の実行を休止するもので、
sleep()はThreadのstaticメソッドです。)

(2) Threadのサブクラスとしてクラスを作成する。

ThreadもRunnableインターフェースをimplementsしており、run()メソッド
を持っています。
Threadのサブクラスを作ってrun()メソッドをオーバーライドし、そのサブ
クラスの複数のインスタンスを生成してstart()メソッドを呼び出すと、そ
のサブクラスのrun()メソッドが別のスレッドで実行されることになります。

たとえば
--------------------------------------------------------
public class RunnerThread extends Thread {
   private String runnerName = "";
   private long restTime = 0;
  
   public RunnerThread(String name, long restTime) {
      runnerName = name;
      this.restTime = restTime;
   }
   public void run() {
      for (int i = 0; i < 10; i++) {
         try {
            Thread.sleep(restTime);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System.out.println("私は" + runnerName + "です。現在" + (i + 1) + "0mに達しました。");
      }
      System.out.println(runnerName + "はゴールインしました。");
   }

   public static void main(String[] args) {
      RunnerThread runner1, runner2;
      runner1 = new RunnerThread("スレッド1ランナー", 150);
      runner2 = new RunnerThread("スレッド2ランナー", 200);
      runner1.start();
      runner2.start();
      for (int i = 0; i < 10; i++) {
         try {
            Thread.sleep(100);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System.out.println("私はスレッド0ランナーです。現在" + (i + 1) + "0mに達しました。");
      }
      System.out.println("スレッド0ランナーはゴールインしました。");
   }

}
--------------------------------------------------------
というようなプログラムを作って実行すると、(1)のプログラムと同様なこと
ができます(ただし、今度はランナーを3人に増やしました)。

この方法ではスーパークラスがThreadとなるため、他のクラスをスーパークラス
にしたいときには使用できません。その場合は、(1)のRunnableインターフェース
をimplementsする方法を用います。


(続く)



================================================
◆ 03.演習問題
================================================

1.
上記のマルチスレッドのプログラム例を実際に作成して実行し、動作を確認し
てください。


2.
今回までに作成した、GraffitiJFrameのアプリケーションでは、描画に使って
いるImageオブジェクトのサイズ(幅300ピクセル、高さ200ピクセル)と
GraffitiJFrameに貼り付けたGraffitiPanelのサイズを合わせていませんでし
た。(GraffitiPanelのサイズのほうが小さくなっているはず。)
そのため、保存したJPEGファイルを他のソフトで開いて見ると、GraffitiJFrame
で見たときと違って、右や下のほうに余白ができていることでしょう。

余力のある人は、これらのサイズを一致させるようにプログラムを改良してみて
ください。
もっと余力のある人は、さらに、サイズをユーザーが変更できるようにプログラ
ムを改良してみてください。



┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
★ホームページ:
      http://www.flsi.co.jp/Java_text/
★このメールマガジンは
     「まぐまぐ(http://www.mag2.com)」
 を利用して発行しています。
★バックナンバーは
      http://www.flsi.co.jp/Java_text/
 にあります。
★このメールマガジンの登録/解除は下記Webページでできます。
      http://www.mag2.com/m/0000193915.html
★このメールマガジンへの質問は下記Webページにて受け付けて
 います。わからない所がありましたら、どしどしと質問をお寄
 せください。
      http://www.flsi.co.jp/Java_text/
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Copyright (C) 2007 Future Lifestyle Inc. 不許無断複製