広告

■□■□■□■□■□■□■□■□■□■□■□■□■□■□■
                      2012年02月29日

   Java総合講座 - 初心者から達人へのパスポート
                 2009年11月開講コース 060号

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


-------------------------------------------------------
・現在、このメールマガジンは以下の2部構成になっています。
[1] 当初からのコース:vol.xxx(xxxは番号)が振られています。
   これは現在、中級レベルになっています。
[2] 2009年11月開講コース:xxx号(xxxは番号)が振られています。
   これは現在、初心者向けのレベルになっています。
・このメールマガジンは、画面を最大化して見てください。
小さな画面で見ていると、不適切な位置で行が切れてしまう
など、問題を起すことがあります。
・このメールマガジンに掲載されているソース・コード及び
文章は特に断らない限り、すべて筆者が著作権を所有してい
ます。また、これらのソース・コードは学習用のためだけに
提供しているものです。
-------------------------------------------------------


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

さて、前回(「Java(文法等)解説」で)直列化(serialization)の
お話をしましたので、今回は直列化の機能を使って描いた絵をファイル
に保存したり、保存したファイルを開いたりする機能を追加してみま
しょう。

直列化の機能を使いますから、描いた図形のオブジェクトの情報が
ファイルに保管されることになります。

これらのオブジェクトには図形の位置や図形の種類、色の種類と
いったデータが保存されているため、このファイルはベクター・
グラフィックス(vector graphics)ということになります。

という訳で、このファイルの拡張子は勝手ながらvectorの先頭3文字
を取って.vecとすることにします。



ではまず、保管したいオブジェクトにSerializableをimplementsするところ
から始めましょう。

DrawingPanelで描いた図形のオブジェクトを保管したいわけですが、
これらの図形のオブジェクトはすべてDecoratedShapeのサブクラスに
しています。

したがって、DecoratedShapeにSerializableを指定すればこと足ります。
(スーパークラスがSerializableをimplementsしていればサブクラスも
Serializableをimplementsしていることになります。)

というわけで、DecoratedShapeのソース・コードを開いて、class文に
「implements Serializable」を指定して下さい。
(Serializableはjava.ioパッケージに入っています。)


念のため、「implements Serializable」を指定したDecoratedShapeの
ソース・コード全体を下に提示します。
--------------------------------------------------------
package jp.co.flsi.lecture.cg;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.io.Serializable;

public abstract class DecoratedShape implements Serializable {
   private static final long serialVersionUID = 1L;
   private Color color = Color.BLACK;
   private Stroke stroke = new BasicStroke(0.1f);
   private Shape shape = null;

   public Color getColor() {
      return color;
   }

   public Shape getShape() {
      return shape;
   }

   public Stroke getStroke() {
      return stroke;
   }

   public void setColor(Color clr) {
      color = clr;
   }

   public void setShape(Shape shp) {
      shape = shp;
   }

   public void setStroke(Stroke strk) {
      stroke = strk;
   }

   public void paintSelf(Graphics2D g2d) {
      g2d.setColor(getColor());
      g2d.setStroke(getStroke());
      g2d.draw(getShape());
   }

}
--------------------------------------------------------

(念のためserialVersionUIDも記述しておきましたが、これは前回お話した
ように、Ctrl+1キーを押し、「デフォルト・シリアルバージョンIDの追加」
を選択すれば自動的に挿入されるものです。シリアルバージョンIDをきちん
と管理したい人は、このデフォルトの1Lという数値ではなく、きちんと
クラスのバージョンごとに計画的に数値を決めて指定しましょう。)


このDecoratedShapeのサブクラスで表現される図形のオブジェクトは、
DrawingPanelで図形を描いていった際にすべてshapesというフィールド
に保持されていくようになっていますね。
(shapesフィールドにはArrayList型のオブジェクトが代入されており、
描いた図形はすべてこのArrayList型のオブジェクトの中に追加されて
いくようにプログラミングされていますね。)

したがって、このshapesフィールドの値(ArrayList型のオブジェクト)
をそのままファイルに保管してしまえばよいのです。

この保管の操作はDrawingToolFrameで(メニューの操作で)行うことに
しますので、DrawingToolFrameからDrawingPanelのshapesフィールドに
アクセスできるようにしておく必要があります。
(ファイルへの保管だけでなく、ファイルを開いてオブジェクトを取り出す
際にものshapesフィールドにアクセスする必要がある。)

そこで、このshapesフィールドにsetterメソッド、getterメソッドを用意
してやることにします。

では、DrawingPanelのソース・コードを開いて下さい。
(今回は「リファクタリング」メニューを使って行いましょう。)

(1) DrawingPanelのソース・コードの中で

   private ArrayList<DecoratedShape> shapes = new ArrayList<DecoratedShape>();

という行を探し、その中のshapesの中にカーソルを入れます。

(2) メニュー・バーから「リファクタリング」→「フィールドのカプセル化」
を選択します。

(3) 「フィールドのカプセル化」ウインドウにおいて、「アクセス修飾子」の欄
で「public」を選択し、「OK」ボタンをクリックします。

これで、自動的にsetter、getterのコードが生成されますが、念の
ため、それらのコードを下に提示しておきます。

--------------------------------------------------------
   public void setShapes(ArrayList<DecoratedShape> shapes) {
      this.shapes = shapes;
   }

   public ArrayList<DecoratedShape> getShapes() {
      return shapes;
   }
--------------------------------------------------------

(このリファクタリングの機能でアクセサー(setter、getter)のコードを自動生成
させると、これまでshapesフィールドを直接参照していた箇所もすべてアクセサーの
呼び出し(ここではgetShapes()の呼び出し)に置き換わりますので、ソース・コード
の中で確認してみて下さい。)

ただし、このsetShapes()メソッドを実行することによって、shapesに図形の
オブジェクトのArrayListを代入したときには、描画対象が置き換わったわけ
ですから、その内容で描画をし直す必要がありますから、このsetShapes()メソッド
の中にrepaint()メソッドの呼び出しを追加しておきましょう。すなわち、
--------------------------------------------------------
   public void setShapes(ArrayList<DecoratedShape> shapes) {
      this.shapes = shapes;
      repaint();
   }
--------------------------------------------------------
のように編集を加えて下さい。
(前回書き忘れていましたが、setJpegImage()メソッドの中でrepaint()メソッド
の呼び出しを行っているのも同じ理由です。)


続いて、このshapesのオブジェクトをファイルに保管したり、そのファイルを
開いたりするメソッドをDrawingToolFrameに追加しましょう。

DrawingToolFrameのソース・コードを開いて、下記のようなメソッドを追加
して下さい。

--------------------------------------------------------
   public void saveVectorGraphics(String fileName) {
      try {
         ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(fileName));
         out.writeObject(drawingToolPanel.getDrawingPanel().getShapes());
         out.close();
      } catch (FileNotFoundException e1) {
         e1.printStackTrace();
      } catch (IOException e1) {
         e1.printStackTrace();
      }
   }

   public void loadVectorGraphics(String fileName) {
      try {
         ObjectInputStream in = new ObjectInputStream(new FileInputStream(fileName));
         drawingToolPanel.getDrawingPanel().setShapes((ArrayList<DecoratedShape>)in.readObject());
      } catch (FileNotFoundException e) {
         e.printStackTrace();
      } catch (IOException e) {
         e.printStackTrace();
      } catch (ClassNotFoundException e) {
         e.printStackTrace();
      }
   }
--------------------------------------------------------

(ObjectOutputStream、FileOutputStream、ObjectInputStream、FileInputStreamは
いずれもjava.ioパッケージに入っています。)

上記のうち、saveVectorGraphics()というのがファイルに保管するメソッドで、
loadVectorGraphics()というのがファイルを開く(ファイルからオブジェクトを
取り出す)メソッドです。

これらのコードの内容は、前回の「Java(文法等)解説」で出て来たものの応用に
なっていますので、説明は省略します。

もし、不明な点などがありましたらAPI仕様などで調べ、それでも分からない人は
ご質問下さい。

なお、上記のコードのうち
(ArrayList<DecoratedShape>)in.readObject()
の部分に警告のマークが付き、そこにマウス・ポインターを持っていくと
「型の安全性:○○○○○○未検査キャスト」というようなメッセージが
表示されるはずですが、これは「コンパイラーではこのような複雑なキャスト
の安全性は検査できない」ということに過ぎず、プログラムの作成者が安全性
を把握できていれば問題はありません。


では次に、これらのメソッドを呼び出すメニュー項目を作り込んでいきましょう。
DrawingToolFrameのDesignビューを開いて下さい。


Paletteの「Menu」配下の「JMenuItem」を選択(クリック)し、
DrawingToolFrameのウインドウの「ファイル」メニューのところに
マウス・ポインターを持っていきます。「ファイル」メニューの下に
表示されるメニュー項目のリストのうち「印刷」というメニュー項目の
すぐ上にマウス・ポインターを持っていくと、そこに赤い横線が表示さ
れますので、そこでクリックしてJMenuItemを貼り付けます。

変数名(Properties欄のVariable)は
menuItemVectorGraphSave
にしておきましょう。

また、textプロパティーの値を
名前をつけてベクター保存
にしておきましょう。すると、メニュー項目に「名前をつけてベクター保存」
という文字列が表示されますね。


続いて前回と同じくFileFilterのサブクラスを作成しましょう。

jp.co.flsi.lecture.cgパッケージの中にVectorGraphFilterという
クラス名で作ってください。
--------------------------------------------------------
package jp.co.flsi.lecture.cg;

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

class VectorGraphFilter extends FileFilter {
   public boolean accept(File f) {
      String filename = f.getName();
      return filename.endsWith(".vec") || f.isDirectory();
   }
   public String getDescription() {
      return "*.vec";
   }
}
--------------------------------------------------------
先ほどお話したように、拡張子は.vecにしていますね。



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


(1) 先ほどのDrawingToolFrameのメニュー・バーの「ファイル」配下の
「名前をつけてベクター保存」と表示されたメニュー項目(menuItemVectorGraphSave)
を右クリックし、「Add event handler」→「アクション」→「actionPerformed」
を選択します。

(2) 次のコードが自動生成されましたね。
--------------------------------------------------------
      menuItemVectorGraphSave.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
         }
      });
--------------------------------------------------------

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

前回と同じく、FileChooserのshowSaveDialog()メソッドは、引数にfinal指定さ
れたJMenuItemインスタンスを要求しますので、今のままではエラーになりますね。
そこでその上にある
--------------------------------------------------------
      JMenuItem menuItemVectorGraphSave = new JMenuItem("\u540D\u524D\u3092\u3064\u3051\u3066\u30D9\u30AF\u30BF\u30FC\u4FDD\u5B58");
--------------------------------------------------------
というコードの先頭にfinalを指定して
--------------------------------------------------------
      final JMenuItem menuItemVectorGraphSave = new JMenuItem("\u540D\u524D\u3092\u3064\u3051\u3066\u30D9\u30AF\u30BF\u30FC\u4FDD\u5B58");
--------------------------------------------------------
というふうに編集して下さい。


これで、描いた絵の各オブジェクトをファイルに保管することができるように
なりました。



続いて、このファイルを開くためのメニュー項目を追加しましょう。
再びDrawingToolFrameのDesignビューを開いて下さい。


Paletteの「Menu」配下の「JMenuItem」を選択(クリック)し、
DrawingToolFrameのウインドウの「ファイル」メニューのところに
マウス・ポインターを持っていきます。「ファイル」メニューの下に
表示されるメニュー項目のリストのうち「名前をつけてJPEG保存」という
メニュー項目のすぐ上にマウス・ポインターを持っていくと、そこに
赤い横線が表示されますので、そこでクリックしてJMenuItemを貼り付けます。

変数名(Properties欄のVariable)は
menuItemVectorGraphOpen
にしておきましょう。

また、textプロパティーの値を
ベクターファイルを開く
にしておきましょう。すると、メニュー項目に「ベクターファイルを開く」という
文字列が表示されますね。

(現在、上から順番に「JPEGファイルを開く」、「ベクターファイルを開く」、
「名前をつけてJPEG保存」、「名前をつけてベクター保存」、「印刷」という
5つのメニュー項目が並んでいるはずです。
もし、間違った順番に貼り付けてしまった場合は(必ずこの順番にしなければ
いけない訳ではないが、順番を入れ替える方法を学ぶために)各メニュー項目
をドラッグすると順番を入れ替えられます。)



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

(1) 先ほどのDrawingToolFrameのメニュー・バーの「ファイル」配下の
「ベクターファイルを開く」と表示されたメニュー項目(menuItemVectorGraphOpen)
を右クリックし、「Add event handler」→「アクション」→「actionPerformed」
を選択します。

(2) 次のコードが自動生成されましたね。
--------------------------------------------------------
      menuItemVectorGraphOpen.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
         }
      });
--------------------------------------------------------

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

ここでもやはりFileChooserのshowOpenDialog()メソッドが、引数にfinal指定され
たJMenuItemインスタンスを要求しますので、今のままではエラーになりますね。
そこでその上にある
--------------------------------------------------------
      JMenuItem menuItemVectorGraphOpen = new JMenuItem("\u30D9\u30AF\u30BF\u30FC\u30D5\u30A1\u30A4\u30EB\u3092\u958B\u304F");
--------------------------------------------------------
というコードの先頭にfinalを指定して
--------------------------------------------------------
      final JMenuItem menuItemVectorGraphOpen = new JMenuItem("\u30D9\u30AF\u30BF\u30FC\u30D5\u30A1\u30A4\u30EB\u3092\u958B\u304F");
--------------------------------------------------------
というふうに編集して下さい。



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


では、テストしてみましょう。
「名前をつけてベクター保存」でファイルを保管するときは、拡張子.vecを
つけることをお忘れなく。

さて、ファイルに保管しようとするとエラーになりますね。
コンソールを見ると、

java.io.NotSerializableException: java.awt.BasicStroke

というメッセージが表示されているはずです。

これは、文字通りBasicStrokeがSerializableになっていないために発生する
エラーです。

なぜこのエラーが出るかというと、DecoratedShapeの中のstrokeフィールド
にBasicStrokeのインスタンスが代入されているからです。
DecoratedShapeのオブジェクトを保管しようとしてもそのstrokeフィールド
の値がSerializableでないためフィールドの値が保管できないのです。


この問題に対処するためには、strokeフィールドにSerializableなオブジェクト
もしくはデータを代入するようなやり方に変更する必要があります。

実は、そもそもこのstrokeフィールドは描画する線の太さの情報を持たせるため
だけに使っているので、わざわざStroke型にする必要はなく、float型などに変更
して単なる数値だけで表現してもかまいません。

前回お話しするのを忘れていましたが、floatなどのプリミティブ型は
オブジェクトではないのでSerializableをimplementsしているわけでは
ありませんが、しかしながら「プリミティブ型のデータは直列化可能」
です。

したがって、strokeフィールドをfloat型に変えてやれば、先ほどのエラー
は解決します。


というわけでDecoratedShapeの中のstrokeフィールドの定義、すなわち
--------------------------------------------------------
   private Stroke stroke = new BasicStroke(0.1f);
--------------------------------------------------------
という行を下記のように書き換えて下さい。
--------------------------------------------------------
   private float stroke = 0.1f;
--------------------------------------------------------

そうすると、strokeフィールドに対するアクセサーもfloat型に書き換える必要が
ありますね。
というわけで、getterメソッド
--------------------------------------------------------
   public Stroke getStroke() {
      return stroke;
   }
--------------------------------------------------------

--------------------------------------------------------
   public float getStroke() {
      return stroke;
   }
--------------------------------------------------------
に書き換え、setterメソッド
--------------------------------------------------------
   public void setStroke(Stroke strk) {
      stroke = strk;
   }
--------------------------------------------------------

--------------------------------------------------------
   public void setStroke(float strk) {
      stroke = strk;
   }
--------------------------------------------------------
に書き換えましょう。

すると、paintSelf()メソッド
--------------------------------------------------------
   public void paintSelf(Graphics2D g2d) {
      g2d.setColor(getColor());
      g2d.setStroke(getStroke());
      g2d.draw(getShape());
   }
--------------------------------------------------------
の中の関連する行がエラーになりますから、これを
--------------------------------------------------------
   public void paintSelf(Graphics2D g2d) {
      g2d.setColor(getColor());
      g2d.setStroke(new BasicStroke(getStroke()));
      g2d.draw(getShape());
   }
--------------------------------------------------------
のように書き換えましょう。
これでDecoratedShapeの中のエラーは無くなりますね。

ちなみに、直列化のときにはフィールドの値しか保管されませんから、
上のpaintSelf()メソッドのようにメソッドの内部でBasicStrokeを
使っていても問題はありません。


さて、現在DecoratedShapeの中にはエラーは無い状態ですが、他のクラスに
エラーが出ていますね。
DrawingPanelクラスの中のdrawStrokeフィールドにからんでいる所、つまり
--------------------------------------------------------
         aShape.setStroke(drawStroke);
--------------------------------------------------------
という行にエラーの赤いマークがついていますね。これはDecoratedShapeの
setStroke()メソッドの引数の型とdrawStrokeの型が食い違っているからですね。

という訳で、drawStrokeフィールドの型もfloatに変更して型を合わせましょう。
つまり、DrawingPanelの中の
--------------------------------------------------------
   private Stroke drawStroke = new BasicStroke(1f);
--------------------------------------------------------
という行を
--------------------------------------------------------
   private float drawStroke = 1f;
--------------------------------------------------------
に書き換えて下さい。

すると、drawStrokeフィールドに対するアクセサーも型を合わせて
変更しなければなりませんから、
--------------------------------------------------------
   public void setDrawStroke(Stroke s) {
      drawStroke = s;
   }
--------------------------------------------------------
というコードを
--------------------------------------------------------
   public void setDrawStroke(float s) {
      drawStroke = s;
   }
--------------------------------------------------------
に書き換えましょう。

すると、今度はDrawingToolPanelの中の以下の行
--------------------------------------------------------
switch (strokeList.getSelectedIndex()){
case 0:
   drawingPanel.setDrawStroke(new BasicStroke(1f));
   break;
case 1:
   drawingPanel.setDrawStroke(new BasicStroke(2f));
   break;
case 2:
   drawingPanel.setDrawStroke(new BasicStroke(5f));
   break;
}
--------------------------------------------------------
の関連個所にエラーが出ますね。そこで、これらを下記のように
書き換えましょう。
--------------------------------------------------------
switch (strokeList.getSelectedIndex()){
case 0:
   drawingPanel.setDrawStroke(1f);
   break;
case 1:
   drawingPanel.setDrawStroke(2f);
   break;
case 2:
   drawingPanel.setDrawStroke(5f);
   break;
}
--------------------------------------------------------

以上で変更作業は完了です。

慣れている人は、DecoratedShapeのstrokeフィールドに変更を加えた時点で
連鎖反応的に上記のような変更が必要になることは見通しが付きますが、
不慣れな人はソース・コードをよく読んでいかないと、何をやっているのか
よく分からないかも知れません。
しかし、もちろんこういった作業は単にエラーを無くせばよいというのではなく、
それぞれのコードの相互関係など、ソース・コードの内容をちゃんと理解して
行うようにして下さい。


では、ソースを保管して、DrawingToolFrameを実行してみましょう。
「名前をつけてベクター保存」と「ベクターファイルを開く」のメニュー項目
がちゃんと作動することを確認しましょう。
なお、ファイル名には.vecという拡張子を付けることをお忘れなく(この拡張子
は当アプリケーションのオリジナルです)。


今回はここまで。


(続く)


以上、今回は
┌───────────────────────────┐
・ベクター・グラフィックスのファイルの保存の仕方と開き方
 (直列化を使ったオブジェクトのファイルへの保存とファイル
  の開き方)
・「型の安全性:○○○○○○未検査キャスト」の意味
・NotSerializableExceptionへの対処方法
・「プリミティブ型のデータは直列化可能」
└───────────────────────────┘
などを学習しました。

では、今日はここまでにします。



========================================================
◆ 02.Java(文法等)解説 [スレッド(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.
上記のマルチスレッドのプログラム例を実際に作成して実行し、動作を確認し
てください。


(続く)



┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
★ホームページ:
      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) 2012 Future Lifestyle Inc. 不許無断複製