Author: switch over

A big outdoor sports lover & A IT engineer.

11.例外

11章 例外
1.例外とは
 例外とは予期しなかった処理のことをいう。例えば、配列の中身の平均値を求めるメソッドを作ろうとした場合、かきのようになる。
class class Test{
    public static void main(String args[ ] ){
            int array[ ] = new int[5];

           array[0] = 10;
           array[1] =  5;
           array[2] =  2;
           array[3] =  4;
         array[4] =  9;
 
             String.out.println("average="+average(array));
      }

    public static int average(int[ ] array){
            int total = 0;
          for( int i = 0 ; i < array.length ; i++ )
               total += array[ i ];

          return total / array.length;
    }
}
   

C:\java>javac Test.java

C:\java>java Test
average=6

C:\java>

ところが、配列の長さが0だった場合はaverageメソッドの中で「0で割る」という計算が発生してしまいエラーになってしまう。
class class Test{
    public static void main(String args[ ] ){
            // int array[ ] = new int[5];
         
int array [ ] = new int [0];
/*
           array[0] = 10;
           array[1] =  5;
           array[2] =  2;
           array[3] =  4;
         array[4] =  9;

/* 
             System.out.println("average="+average(array));
      }

    public static int average( int[ ] array ){
            int total = 0;
          for( int i = 0 ; i < array.length ; i++ )
               total += array[ i ];

          return total / array.length;
    }
}
   

C:\java>javac Test.java

C:\java>java Test
Exception in thread "main" java.lang.ArithmeticException: / by zero
          at Test.average(Test.java:20) 
          at Test.main(Test.java:12)

C:\java>

 このように予期せぬエラーが発生するとJavaではそのスレッドが終了してしまう。この例のように単一スレッドの場合はプログラムが終了してしまう。 
 このエラーメッセージにある「Exception」というのが例外をあらわしており、「in thread "main"」がスレッドをあらわしている。そしてその後の「java.langArithmeticException:/ by zero」が例外の内容をあらわしていて、ここではゼロで割ったという例外であることがわかる。次の行からどの部分で例が起ったかをあらわしている。Test.javaの20行目、Testクラスのaverageメソッドで起っていることがわかり、さらに次の行を見ると、Test.averageメソッドはTest.javaの12行目(Testクラスのmainメソッド)から呼び出されていることがわかる。
 
2.例外が起きたら
 例外が起きた場合に、アプリケーションが終了してしまっては困る。そこでプログラマーは例外を予測して、例外が起きた場合に適当な処理をさせる必要がある。
 例えば平均を求めるメソッドの場合、引数で与えられた配列がnewし忘れたなどで、配列の実体がなかった場合などは、 NullPointerExceptionという名前の例外が発生する。平均を求めるメソッドでは、この2つの例外が発生する可能性があるので、例外が起きた場合専用の処理をするようにする。
public class Test{
    public static void main(String args[ ] ){
            // int array[ ] = new int[5];
         
int array [ ] = new int [0];

          System.out.println("average="+average(array));
    }

    public static int average( int[ ] array ){
            try {
                int total = 0 ;
                for ( int i = 0 ; i < array.length ; i++ )
                      total += array[ i ];

                return total / array.length;
          }catch(NullPointerException e){
                System.out.println("newし忘れてませんか?");
          }catch(ArithmeticException e){
                System.out.println("配列が空です。");
          }
      return 0;

     }
}

C:\java>javac Test.java

C:\java>java Test
配列が空です 
average=0

C:\java>

 上記のように、例外が発生すると思われる部分を
try{
    // 例外が発生する可能性のあるブロック
}catch(例外の種類1 param1){
   // 例外の種類1が発生した場合に実行される部分
}catch(例外の種類2 param1){
   // 例外の種類2が発生した場合に実行される部分
}finally{
   // 例外が起きても起きなくても実行される部分
}
 上記の構文を用いた場合、Jvmはtryブロックを実行する。もしtryブロックの中で例外が発生しなければ、catchブロックは実行せずに、finallyブロックを実行する。 
もし、tryブロック実行中に例外が発生したら、発生した場所から例外の内容に応じてcatchブロックが実行される。この場合、tryブロックはすべてが実行されるわけではなく、例外が発生した部分よりも下の部分は実行されずにcatchブロックにジャンプする。上記の例で、averageメソッドの引数にnullが渡された場合、for分のarray.lengthでNullPointerExceptionという例外が発生する。したがって、forの中身は実行されずにNullPointerExceptionをキャッチしているブロックに処理が移行する。
 なお、finallyブロックは省略することができる。catchブロックは1つ以上必要になる。
 
3.例外の種類
ClassNotFoundException  呼び出そうとしたクラスのクラスファイルが無かった場合
CloneNotSupportedException オブジェクトを複製するためにObjectクラスのcloneメソッドが呼び出されたが、そのオブジェクトのクラスがCloneableインターフェイスを実装していないことを示すためにスローされる。
DataFormatException  データ形式エラーが発生したことを通知する
IOException  なんらかの入出力例外の発生を通知するシグナルを発生させる。
サブクラスとしてEOFException,FileNotFoundExceptionなどがある。
PrinterException 印刷システムに例外的な状態が発生したことを示すために使用される。
サブクラスとしてPrinterAbortException,PrinterIOExceptionがある。
RuntimeException  JVMの通常の処理でスローすることができる各種の例外のスーパークラス。
ArithmeticException  RuntimeExceptionのサブクラス。
算術計算で例外的条件が発生した場合にスローされる。例えば、「ゼロで除算される」整数はこのクラスのインスタンスをスローする。
ArrayStoreException RuntimeExceptionのサブクラス。
不正な型のオブジェクトをオブジェクトの配列に格納しようとしたことを示すためにスローされる。
ClassCastException RuntimeExceptionのサブクラス。
あるオブジェクトを継承関係にないクラスにキャストしようとしたことを示すためにスローされる。
IllegalArgumentException  RuntimeExceptionのサブクラス。
不正な引数、または不適切な引数をメソッドに渡したことを示すためにスローされる。
IndexOutOfBoundsException  RuntimeExceptionのサブクラス。
ある種のインデックス(配列、文字列、ベクトルなど)が範囲外であることを示すためにスローされる。サブクラスにArrayIndexOutOfBoundsException,StringIndexOutOfBoundsExceptionがある。
NegativeArraySizeException RuntimeExceptionのサブクラス。
負のサイズを持った配列をアプリケーションが作成しようとした場合にスローされる。
NoSuchElementException RuntimeExceptionのサブクラス。
この列挙にそれ以上の要素がないと、EnumerationのnextElementメソッドによってスローされる。
NullPointerException RuntimeExceptionのサブクラス。
オブジェクトが必要な場合に、アプリケーションがnullを使おうとするとスローされる。
SecurityException RuntimeExceptionのサブクラス。
セキュリティマネージャによってスローされ、セキュリティ違反を示す。
サブクラスにAccessControlException,RMISecurityExceptionがある。
NumberFormatException IllegalArgumentExceptionのサブクラス。
アプリケーションが文字列を数値型に変換しようとしたとき、文字列の形式が正しくない場合にスローされる。
 
4.catchブロックの検索
 tryブロック内で例外が発生した場合は、tryブロックの次に記述されているcatchブロックに対応する例外処理が書かれているかを検索する。しかし、対応する例外処理が書かれていなければ(対応する例外をキャッチするcatchブロックがない場合)、そのtryブロックを囲っているさらに上のtryブロックにかかれているcatchブロックを検索する。つまり、例外処理が起きたメソッドを呼び出している呼び出し元のtry〜catchブロックなどを探す。そこにも発生した例外をキャッチするcatchブロックが無ければ、さらに呼び出し元を検索する。
 可能な限りさかのぼってもキャッチされない場合は、そのスレッドはJVMによって強制的に終了されてしまう。もし単一スレッドであった場合は、アプリケーションが終了する。
public class Test{
    public static void main(String args[ ] ){
          try{
            a( );
       }catch (NullPointerException e){
            System.err.println(e);
                }

            System.out.println("アプリケーションは正常に終了しました");
          }

          public static void a( ){
              try{
                    b( );
          }catch(ArithmeticException e){
                  System.err.println(e);
          }
  }

      public static void b( ){
             int array[ ] = new int[2];
           // ArrayIndexOutOfBoundsExceptionを発生
           array[3] = 10;
          }
  }
 

C:\java>javac Test.java

C:\java>java Test
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
           at Test.b(Test.java:22)
           at Test.a(Test.java:12)
           at Test.main(Test.java:3)
           
C:\java>

 例えば、NullPointerExceptionはRuntimeExceptionのサブクラスで、RuntimeExceptionはExceptionのサブクラスなので下記のように記述する。 
public class Test{
    public static void main(String args[ ] ){
          try{
            int.array[ ] = null;
            array[0] = 10;
        }catch(NullPointerException e{
            System.err.println("NullPointerException");
        }catch(RuntimePointerException e{
            System.err.println("RuntimeException");
        }catch(Exception e){
            System.err.println("Exception");
           }
     }
 
C:\java>javac Test.java

C:\java>java Test
NullPointerException
           
C:\java>

 ところが、下記の例では、スーパークラスのExceptionから記述しようとしたため、すべての例外は最初のExceptionクラスのcatchブロックでキャッチされてしまう。キャッチブロックは一回の例外で一度しか処理しないため、その下のRuntimeExceptionやExceptionやNullPointerExceptionではキャッチされない。したがって、RuntimeExceptionやNullPointerExceptionのキャッチブロックは処理されることがないため、コンパイルエラーになる。
public class Test{
    public static void main(String args[ ] ){
          try{
            int.array[ ] = null;
            array[0] = 10;
        }catch(Exception e{
            System.err.println("Exception");
        }catch(RuntimeException e{
            System.err.println("RuntimeException");
        }catch(NullPointerException e){
            System.err.println("NullPointerException");
           }
     }
}
 
C:\java>javac Test.java
Test.java:8 catch not reached.
           }catch(RuntimeException e){
            ^
Test.java:10: catch not reached.
           }catch(NullPointerException e){
            ^           
2 errors

C:\java>

 
5.throw
 例外は受け取るだけでなく、呼び出し元に例外処理を任せることもできる。これを「例外を投げる」という。例えば、そのキャッチブロックで例外処理をしただけではダメで、呼び出しもとのキャッチブロックでも例外処理をしなければならないような場合に使う。
 下記の例ではaメソッド内で例外処理をした後、throwステートメントを用いて、例外を上に投げている。
public class Test{
  public static void main(String args[ ] ){
        try{
              a( );
        }catch(RuntimeException e){
            System.err.println("mainメソッド内で例外を処理しました("+e+")");
        }
        System.out.println("アプリケーションは正常に終了しました");
    }

  public static void a( )
        try{
             
int array[ ] = new int[2];
           // ArrayIndexOutOfBoundsExceptionを発生
           array[3] = 10;
        }catch(RuntimeException e){
            System.err.println("aメソッド内で例外を処理しました("+e+")");
         throw e;
        }
   }
}
   
C:\java>javac Test.java

C:\java>java Test
aメソッド内で例外を処理しました(java.lang.ArrayIndexOutOfBoundsException)
aメソッド内で例外を処理しました(java.lang.ArrayIndexOutOfBoundsException)
アプリケーションは正常に終了しました

C:\java>

 上記の例では、生成されたRuntimeExceptionのインスタンスをthrowで上に投げている。throwステートメントは生成されたExceptionを投げるだけでなく、プログラマーが明示的にnewした例外などを上に投げることもできる。投げることができるのは、Throwableクラスのサブクラスだけである。Stringクラスなどのインスタンスは投げることができない。ThrowableクラスのサブクラスはExceptionクラス(すべての例外のスーパークラス)と、Errorクラスである。
public class Test{
  public static void main(String args[ ] ){
        int array[ ] = new int[2];
        try{
              a(array, 10);
        }catch(RuntimeException e){
               System.err.println("mainメソッド内で例外を処理しました("+e+")");
        }
    }

  public static int a(int array[ ],   int i ){
        if( i < 0 || i >= array.length ){
               System.out.println("ArrayIndexOutOfBoundsExceptionを投げます");
           throw new ArrayIndexOutOfBoundsException( );
        }

        System.out.println("aメソッドは正常に終了します");
         return array[ i ];
   }
}
   

C:\java>javac Test.java

C:\java>java Test
ArrayIndexOutOfBoundsExceptionを投げます
mainメソッド内で例外を処理しました(java.lang.ArrayIndexOutOfBoundsException)

C:\java>

 
6.throws
 例外を投げる可能性があるメソッドを作成した場合は、他のプログラマーが見てもすぐに例外が投げられる可能性があることがすぐわかるように、明示しておくべきである。明示するためには、例外を投げる可能性があるコンストラクタやメソッドの宣言時にthrowステートメントを用いる。
public class Test{
  public static void main(String args[ ] ){
        int array[ ] = new int[2];
        try{
              a(array, 10);
        }catch(RuntimeException e){
               System.err.println("mainメソッド内で例外を処理しました("+e+")");
        }
    }

  public static int a(int array[ ],   int i ) throws ArrayIndexOutOfBoundsException{
        if( i < 0 || i >= array.length ){
               System.out.println("ArrayIndexOutOfBoundsExceptionを投げます");
           throw new ArrayIndexOutOfBoundsException( );
        }

        System.out.println("aメソッドは正常に終了します");
         return array[ i ];
   }
}
   

C:\java>javac Test.java

C:\java>java Test
ArrayIndexOutOfBoundsExceptionを投げます
mainメソッド内で例外を処理しました(java.lang.ArrayIndexOutOfBoundsException)

C:\java>

 投げる例外が複数ある場合は、カンマ区切で列挙する。
 throwステートメントは記述しなくても問題ないが、特に複数人でプログラミングする場合など、自分以外の人にわかりやすくするために記述することが好ましい。また、1人でプログラムしていても、しばらくたつと忘れてしまう場合もあるため、記述することを勧める。
 
7.エラーと例外
 throwできるクラスはThrowableクラスのサブクラスだけである。ThrowableクラスにはErrorクラスとExceptionクラスの2つがある。一般にアプリケーションの使い方をユーザーが間違えた場合、ファイルをセーブしようとしたのにディスクがいっぱい・通信の失敗など一般に予期することができるようなエラーはJavaではエラーではなく例外として処理する。逆にJVMの重大なエラーを示す。例えばメモリー不足、スタックオーバー、内部エラー、不正アクセスなど、プログラマーやユーザーのせいではないエラークラスとする。したがって、一般にはエラークラスのサブクラスを作成することはできるが一般にはしない。
 Throwableクラス、Errorクラス、Exceptionクラスのコンストラクタとメソッドを紹介する。これらのサブクラスを作る方法は、必要なメソッドをオーバーライドする必要がある。
Throwableクラスのメソッドの概要
Throwable( )
 エラーメッセージ文字列としてnullを指定してThrowableを新しく構築する
Throwable(String message )
 指定されたエラーメッセージを持つ、Throwableを新しく構築する 
Throwableクラスのメソッドの概要 
Throwable fillInStackTrace( )
 実行スタックトレースを埋め込む。
String getLocalizedMessage( )
 Throwableの地域対応された記述を作成する。
String getMessage( )
 このThrowableの地域対応された記述を作成する。
Void printStackTrace( )
 Throwableとそのバックトレースを標準エラーストリームに出力する。
Void printStackTrace(PrintStream s)
 Throwableとそのバックトレースを指定された印刷ストリームに出力する。
Void getStackTrace(PrintWriter s)
 このThrowableとそのバックトレースを指定されたプリントライターに出力する。
String toString( )
 Throwableオブジェクトの短い記述を返す。
Errorクラスのコンストラクタの概要
Error( )
 詳細メッセージを指定しないでErrorを構築する。
Error(String s)
 指定された詳細メッセージを持つErrorを構築する。 
Errorクラスのコンストラクタの概要
Exception( )
 詳細メッセージを指定しないでExceptionを構築する。
Exception(String s)
 指定された詳細メッセージを持つExceptionを構築する。  
Throwable,Error,Exception 

public Throwable( )
public Error( )
public Exception( )

 エラーメッセージ文字列としてnullを指定してThrowable,Error,Exceptionを新しく構築する。

Throwable,Error,Exception 

public Throwable(String message)
public Error(String message)
public Exception(String message)

 指定されたエラーメッセージを持つ、Throwable,Error,Exceptionを新しく構築する。
 パラメータ:message-エラーメッセージ。エラーメッセージはgetMessage( )メソッドによる取り出しのために保存される。

getMessage 

public string getMessage( )

 このThrowableオブジェクトのエラーメッセージ文字列を返す。
 戻り値:エラーメッセージ文字列突きで作成された場合は、このThrowable,Error,Exceptionオブジェクトのエラーメッセージ文字列。エラーメッセージなしで作成された場合はnull。

toString 

public String toString( )

 Throwable オブジェクトの短い記述を返す。このThrowable,Error,Exceptionオブジェクトがエラーメッセージ文字列付きで作成された場合、結果は次の3つの文字列を連結したものとなる。
 ・オブジェクトの実際のクラス名
 ・": "(コロン、スペース)
 ・オブジェクトに対するgetMessage( )メソッドの結果
 Throwable,Error,Exceptionオブジェクトがエラーメッセージ文字列なしで作成された場合は、オブジェクトの実際のクラス名が返される。
 戻り値:
    このThrowable,Error,Exceptionの文字列表現
 オーバーライド:
    クラスObject内のtoString

printStackTrace
public void printStackTrace( )

 Throwable,Error,Exceptionとそのバックトレースを標準エラーストリームに出力する。
       java.lang.ArithmeticException: / by zero
                            at Test.average(Test.java:20)
                           at Test.main(Test.java:12)

8.独自の例外
 Exceptionクラスを継承して独自の例外クラスを作成することもできる。これはアプリケーション特有の例外を作成したい場合などに使用される。
 独自のエラークラスを投げるメソッドでは、必ずキャッチするか、throwsステートメントで、独自の例外を投げる可能性があることを明示しなければならない。
// 独自の例外クラス
class MyException extends Exception{
                public MyException( ){
                               super( );
              }
}

public class Test{
       public static void main(String args[ ]) {
                try{
                 a( );
               }
     }

       public static void a( ) throws MyException{
              throw new MyException( );
          }
}

C:\java>javac Test.java

C:\java>java Test
mainメソッド内で例外を処理しました(MyException)
           
C:\java>

 上記の例で、throw MyExceptionやmainメソッド内のcatchを忘れると、コンパイルエラーになる。
 ただし、独自の例外がRuntimeExceptionのサブクラスの場合は、この制限は適用されない。
// 独自の例外クラス
class MyException extends Exception{
                public MyException( ){
                               super( );
              }
}

public class Test{
       public static void main(String args[ ]) {
                try{
                 a( );
               } catch(MyException e){
                 System.err.println("e");
            }
  }

       public static void a( ){
              throw new MyException( );
          }
}

// 独自の例外クラス
class MyException extends RuntimeException{
                public MyException( ){
                               super( );
              }
}
public class Test{
     public static void main(String args[ ]){
          try{
               a( );
              } catch(MyException e ){
       System.err.println(e);
              }
       }

     public static void a( ){
          throw new MyException( );
       }
}

C:\java>javac Test.java
Test.java:11: Exception MyException is never thrown
in the body of the corresponding try statment.
            }catch(MyException e){
           
Test.java:17: Exception MyException must be caught,
or it must be declared in the throws clause of this method.
          throw new MyException( );

2 errors

C:\java>

C:\java>javac Test.java

C:\java>

 
 
 
 
 
 
 

9.インターフェース

9章 インターフェース
1.インターフェースとは
 ひし形クラスと長方形クラスの両方の特徴を継承した正方形クラスのようなものを多重継承という。しかし、Javaでは多重継承の概念は取り入れられていません。これを解決するためにインターフェースという機能をサポートしている。
 インターフェースとは、定数とメソッドの宣言だけを集めたものである。宣言だけで実際のメソッド本体などは実装しない。あるクラスを作ろうとした場合に、「インターフェースをインプリメントする」と、そのクラスはインターフェースで宣言したメソッドと変数を必ず実装しなければならなくなる。
 つまり、MyInterfaceというインタフェースを作り、その中で「func1」というメソッドを宣言したとする。そして、MyClassを作ろうとした場合にMyInterfaceをインプリメントした場合、MyClassの中にはかならずfunc1というメソッドを実装する必要がある。
 一般的に継承は継投を表し、インターフェースは機能を表す。しかし、具体的な機能を決めるものではなく、このような機能があることを明示するためのものである。したがって、インターフェースはJavaのポリモーフィズムを実現する1つの手段であるといえる。
 ここで、例を示す。犬クラスは哺乳類クラスのサブクラスである。犬にも秋田犬クラス、ゴールデンレトリバークラスやブルドッククラスなどがサブクラスとして存在するかもしれない。これらは動物の種類の系統でサブクラスを作ってきている。また犬には、盲導犬や麻薬捜査犬、救助犬といった分類もあるが、これらはそれぞれ、盲人を誘導する機能を有するとか麻薬を見つけるとか救助するといった機能を表す。つまり、犬の種類とは関係ない。したがって、これらの機能)(インタフェース)をインプリメントした犬クラスが盲導犬クラスというように定義できるのである。
 今の世の中では盲導象や盲導猫というのは存在しないが、もしかすると将来盲導猫というものができたとすれば、猫クラスのサブクラスで、盲導インターフェースをインプリメントして盲導猫クラスを作ることができる。  
  
2 インターフェースの宣言 
interface interfaceName{
    type name = value
    type name = value                                           // 変数
    type method( paramType name… );           // メソッド
}
   

interface Shape2D{
     // 面積を計算する
  double getArea( );
}

 上記のようなShepe2Dインターフェースをインプリメントしたクラスは、必ずgetAreaメソッドを実装しなければならない。下記にその使用例を示す。 
 // 図形クラス
abstract class Shape{
    abstract void Display( );
}

// 円クラス
class Circle extends Shape implements Shape2D{
    int x;
    int y;
    int r;

    Circle ( int x, int r ){
        this.x = x;
        this.x = y;
        this.x = r;

    }

    void Display( ){
         System.out.println("x=" + x + ", y=" + y + ", r=" + r);
    }

    // 面積を計算する
    public double getArea( ){
         return r*r*Math.PI ;    // Math.PI:円周率
    }
}

    // 長方形クラス
class Rect extends Shape implements Shape2D{
        int x1;
       int y1;
       int x2;
       int y2;

     Rect ( int x1, int y1, int y2 ){
               this.x1 = x1;
               this.y1 = y1;
               this.x2 = x2;
               this.y2 = y2;
       } 

       void Display( ){
              System.out.println("x1=" + x1 + ", y1=" + y1 + ", x2=" + y2 + ", y2=" + y2);
       }

       // 面積を計算する
       public double getArea( ){
            // Math.abs : 絶対値を求めるメソッド
              return Math.abs(x1-x2) * Math.abs(y1-y2);
       }
}

 Shapeクラスは図形クラスであり、図形というからには何らかの形で表示する必要がある。表示方法は円や三角形、長方形など図形によって異なる。そこで、Shapeクラスでは実装できずサブクラスで実装する必要があるため、abstractになっている。
 Shapeクラスのサブクラスとして、ここではCircleクラス(円クラス)とRectクラス(長方形クラス)を定義している。ここでは、そのクラスの機能として面積を計算するためのShape2Dインターフェースをインプリメントしている。
  
3 インターフェースの参照 
Test.java
public class Test{
      public static void main(String args[ ]){
         Shape2D array[ ] = new Shape2D[5];

          array[0] = new Circle(5,3,2);
          array[1] = new Rect(5,3,20,7);
          array[2] = new Circle(3,1,1);
          array[3] = new Rect(0,0,3,3);
          array[4] = new Circle(-5,2,2);
 
          double area = 0;    // 面積 
          area += array[0].getArea( );
          area += array[1].getArea( );
          area += array[2].getArea( );
          area += array[3].getArea( );
          area += array[4].getArea( );
  

          System.out.println("面積の合計="+area);
      }
}
  

C:\java>java Test
面積の合計=97.27433388230814

C:\java>

  
4 インターフェースと修飾子 
 クラスにはpublic,protected,privateといった修飾子がある。インターフェースにもこれらの修飾子を使うことが可能である。もちろん、インターフェースに含まれる変数やメソッドにも用いることも可能である。
public interface Shape2D{
     // 面積を計算する
   public double getArea( );
}
  
5 インターフェースの継承 
 クラスには継承という機能がある。インターフェースにも継承という機能がある。クラスは多重継承ができないが、インターフェースは多重継承をすることができる。その場合、extendsの後に継承したいインターフェースをカンマで区切って記述する。
public interface Interface extends Interface1,Interface2{
     // 面積を計算する
   public double method( );
}
  
6 インターフェースとinstaceof演算子
 クラスはinstanceof演算子で、インスタンスがそのクラスのインスタンスか否かを判定することができる。インターフェースも同様にインスタンスが指定されたインターフェースをインプリメントかどうかをinstanceof演算子を用いて調べることができる。
 下記の例は、面積の合計を求める先ほどのサンプルをinstanceof演算子を用いて、円の面積の合計と長方形の面積の合計をそれぞれ算出するように書き換えたものである。
Test.java

public class Test{
   public static void main(String args[ ]){
         Shape2D array[ ] = new Shape2D[5];

         array[0] = new Circle(5,3,2);
         array[1] = new Rect(5,3,20,7);
         array[2] = new Circle(3,1,1);
         array[3] = new Rect(0,0,3,3);
         array[4] = new Circle(-5,2,2);

         double areaCircle = 0;    // 円の面積
         double areaRect   = 0;    // 長方形の面積

    for( int i = 0 ; i < array.length ; i++ ){
              if( array[i] instanceof Circle )
                  areaCircle += array[i].getArea( );

              else
                  areaRect   += array[i].getArea( );
        
}

         System.out.println("円面積の合計="areaCircle);
         System.out.println("長方形面積の合計="areaRect);
         System.out.println("面積の合計="+ (areaCircle + areaRect));
     }
}

C:\java>java Test
円面積の合計=28.274333882308138
長方形面積の合計=69.0
面積の合計=97.27433388230814

C:\java>

 

12.マルチスレッド

12章 マルチスレッド
1.スレッドとは
 一つのプロセスで、同時に複数の処理をすることができる。例えば、ブラウザでインターネットからファイルを取得する仕事が動き、さらにユーザからの操作を受け付ける仕事があり・・・これらが同時に動いている。この仕事一つ一つをスレッドという。
複数スレッドが同時に処理することができるプログラムをマルチスレッドプログラムという。
2.スレッドのライフサイクル
 同時にスレッドが動くといっても実際はCPUが一つなので、一つ目のスレッドが動いたら二つ目のスレッドが動く、というように短時間の間に交互に動く。これをタイムディビジョン方式という。
実行状態
現在、実行されているスレッド
待機状態
通信の返事が返ってくるまで待つなどキーボード入力を待ったり、イベントが終了するまで待機している状態。または、何もすることがない状態。
実行可能状態

 本来は、すぐに実行になりたいが別のスレッドが実行中の場合は、一時待つ。
3.スレッドの作成 〜Threadクラス〜
 スレッドを作るには、java.langパッケージのThredクラスを継承すれば作られる。さらに、スレッドの中身はrunメソッドをオーバーライドして記述する。そして、スレッドクラスのstartメソッドを呼び起こせば、新しいスレッドが作成されrunメソッドが別スレッドとして実行される。
Test.java// 独自のスレッドクラス
class MyThread extends Thread{
public  MyThread( ){
}
// このrunメソッドがスレッド本体
public void run( ){
System.out.println(“MyThread Start!”);

// 10回500ミリ秒毎n”Hello”と表示する
    for (int i = 0 ; i < 10 ; i++ ) {
System.out println(“Hello”);
      try{
// sleepメソッドはThreadクラスの静的メソッドでnミリ秒(この場合500ミリ秒)待つ(待機状態)メソッド。
// 例外を投げる可能性があるので、キャッチしないとならない。
Thread.sleep(500);
}catch(Exception e){ }
}
}

public class Test {
public static void main (String args [ ] ) {
// 独自のスレッドクラスのインスタンスを作成
MyThread t = new MyThread(“Thread1”);

// スレッドを開始
t.start( ) ;

// 1秒毎に”Hi !”と10回表示する
for( int i = 0 ; i < 10 ; i++ ) {
System.out.println(“Hi !”) ;
try{
Thread.sleep(1000);
}catch(Exception e){ }
}
}
}

C:\java>javac Test.java

C:\java>java Test
Hi!
MyThread Start!
Hello
Hello
Hi!
Hello
Hello
Hi!
Hello
Hello
Hi!
Hello
Hello
Hi!
MyThread End!
Hi!
Hi!
Hi!
Hi!
C:\java>

4.Threadクラスのメソッド
●コンストラクタ概要
Thread( )
 新しいThreadオブジェクトを割り当てる
Thread(Runnable target )
 新しいThreadオブジェクトを割り当てる
Thread(Runnable target,String name)
 nameという名前の新しいThreadオブジェクトを割り当てる
Thread(String name)
 nameという名前の新しいThreadオブジェクトを割り当てる
●メソッド概要
String
getName( )        
 このスレッドの名前を返す 
boolean     
isAlive( )        
このスレッドが生存しているかどうかを判定する 
void          
join(long millis)  
 このスレッドが終了するのを、最高でmillisミリ秒待機する
void          
run( )                 
このスレッドが別個のRunnable実行オブジェクトを使用して作成された場合、そのRunnableオブジェクトのrunメソッドが呼び出される 
void
setName(String name)
このスレッドを、デーモンスレッドまたはユーザスレッドとしてマークする 
void
setPriority(int newPriority)
このスレッドの優先順位を変更する 
static void
sleep(long millis)
現在実行中のスレッドを指定されたミリ秒数の間、スリープ(一時的に実行を停止)させる
void
start( )
このスレッドの実行を開始 
static void
yield( )
現在実行中のスレッドオブジェクトを一時的に休止させ、ほかのスレッドが実行できるようにする 
5.スレッドの作成 〜Runnableインターフェース〜
Test.java// 独自のクラス
class MyRunnable implements Runnable{
// このrunメソッドがスレッド本体
public void run( ){
System.out.println(“MyRunnable Start!”);

// 10回500ミリ秒毎n”Hello”と表示する
    for (int i = 0 ; i < 10 ; i++ ) {
System.out println(“Hello”);
      try{
// sleepメソッドはThreadクラスの静的メソッドでnミリ秒(この場合500ミリ秒)待つ(待機状態)メソッド。
// 例外を投げる可能性があるので、キャッチしないとならない。
Thread.sleep(500);
}catch(Exception e){ }
}
}

public class Test {
public static void main (String args [ ] ) {
// 独自のスレッドクラスのインスタンスを作成
Runnable r = new MyRunnable( ):
Thread t = new Thread(r);

// スレッドを開始
t.start( ) ;

// 1秒毎に”Hi !”と10回表示する
for( int i = 0 ; i < 10 ; i++ ) {
System.out.println(“Hi !”) ;
try{
Thread.sleep(1000);
}catch(Exception e){ }
}
}
}

C:\java>javac Test.java

C:\java>java Test
Hi!
MyRunnable Start
Hello
Hello
Hi!
Hello
Hello
Hi!
Hello
Hello
Hi!
Hello
Hello
Hi!
MyRunnable End!
Hi!
Hi!
Hi!
Hi!
C:\java>

6.同期
 ロックとは、一つ目のスレッドがあるメソッドをロックすると別のスレッドはそのメソッド内に入れなくなる。そのメソッドを出るときにロックを解除することで別スレッドがそのメソッドに入ることができるようにする。
メソッドを作成するときに「Synchronized」修飾子をつける。javaではこれだけでロックをかける(同期をとる)ことができる。
Test.java// 口座クラス
class Account{
private int balance = 0;

 

 synchronized void deposit ( int amount ){
int a = balance;
    a = a + amount;
    try{
Thread.sleep((int)(Math.random( )*10));
}catch(Exception e){ }
    balance = a;
}
        int getBalance( ){
}
}

// 顧客クラス
class Customer extends Thread{
     Account account;

// コンストラクタ
  Customer(Account account){
this.account = account;
}

// 10円ずつ貯金することを1000回繰り返す
public void run( ) {
          for( int i = 0 ; i < 1000 ; i++ ){
account.deposit(10);
}
}
}

class Test{
public static void main(String args[ ]){
     // 口座を作る
Account account = new Account( );

        // 10人の顧客を作る
Customer customers[ ] = new Customer[10];

        // 10人の顧客が一つの口座に振り込み処理を
   //開始す
for( int i = 0 ; i < 10 ; i++ ){
customer[i] = new Customer(account);
customer[i].start( );

}

        // 10人のスレッドが終わるのを待つ
try{
for( int i = 0 ; < 10 ; i++ ){
customers[i].join( );
}
}catch(Exception e){
System.err.println(“Error:”+e);
}

     // 残高表示
System.out.println(“残高:”+account.getBalance()));
}
}

Test.java// 口座クラス
class Account{
private int balance = 0;

void deposit ( int amount ){
int a = balance;
    a = a + amount;
    try{
Thread.sleep((int)(Math.random( )*10));
}catch(Exception e){ }
    balance = a;
}

        int getBalance( ){
}
}

// 顧客クラス
class Customer extends Thread{
     Account account;

// コンストラクタ
  Customer(Account account){
this.account = account;
}

// 10円ずつ貯金することを1000回繰り返す
public void run( ) {
          for( int i = 0 ; i < 1000 ; i++ ){
account.deposit(10);
}
}
}

class Test{
public static void main(String args[ ]){
     // 口座を作る
Account account = new Account( );

        // 10人の顧客を作る
Customer customers[ ] = new Customer[10];

        // 10人の顧客が一つの口座に振り込み処理を
   //開始する
for( int i = 0 ; i < 10 ; i++ ){
customer[i] = new Customer(account);
customer[i].start( );

}

        // 10人のスレッドが終わるのを待つ
try{
for( int i = 0 ; < 10 ; i++ ){
customers[i].join( );
}
}catch(Exception e){
System.err.println(“Error:”+e);
}

     // 残高表示
System.out.println(“残高:”+account.getBalance()));
}
}

C:\java>javac Test.java

C:\java>java Test
残高:100000C:\java>

C:\java>javac Test.java

C:\java>java Test
残高:10000C:\java>

メソッドにsynchronized修飾子をつけて同期をとる方法ではなく、オブジェクトにロックをかけて同期をとる方法
Test.java// 口座クラス
class Account{
private int balance = 0;

void deposit ( int amount ){
int a = balance;
    a = a + amount;
    try{
Thread.sleep((int)(Math.random( )*10));
}catch(Exception e){ }
    balance = a;
}

        int getBalance( ){
}
}

// 顧客クラス
class Customer extends Thread{
     Account account;

// コンストラクタ
  Customer(Account account){
this.account = account;
}

// 10円ずつ貯金することを1000回繰り返す
public void run( ) {
          for( int i = 0 ; i < 1000 ; i++ ){
sychronized( account ){
account.deposit(10);
}
}
}
}

class Test{
public static void main(String args[ ]){
     // 口座を作る
Account account = new Account( );

        // 10人の顧客を作る
Customer customers[ ] = new Customer[10];

        // 10人の顧客が一つの口座に振り込み処理を開始する
for( int i = 0 ; i < 10 ; i++ ){
customer[i] = new Customer(account);
customer[i].start( );

}

        // 10人のスレッドが終わるのを待つ
try{
for( int i = 0 ; < 10 ; i++ ){
customers[i].join( );
}
}catch(Exception e){
System.err.println(“Error:”+e);
}

     // 残高表示
System.out.println(“残高:”+account.getBalance()));
}
}

C:\java>javac Test.javaC:\java>java Test
残高:100000

C:\java>

 この方法は、accountにロックをかけている。一つ目のスレッドがaccountにロックをかけると、別のスレッドはロックが解除されるまで、synchronizedで囲まれたブロック(synchronizedブロック)には、入ることができない。
7.デットロック
 デットロックとは、マルチスレッドプログラミングで起こりやすいバグの一つ。一つ目のスレッドがXというオブジェクトのロックが解除されるのを待つ。二つ目のスレッドはYというオブジェクトをロックしており、Xというオブジェクトの解放を待つ。このような場合、両方のスレッドはオブジェクトの解放を永遠に待ち続けてしまうため、プログラムは進まなくなり、いわゆる暴走の状態に陥る。このように、複数のスレッドがお互いにロックの解放を永久に待ち続けてしまうことをデットロックという。
Test.java class X{
  int x;
}

class Y{
int y;
}

class A extends Thread{
X x;
Y y;

A(X x, Y y){
this.x = x;
this.y = y;
}

void func1( ){
     System.out.println(“func1:start”);
synchronized(x){
synchronized(y){
try{
sleep(10);
}catch(Exception e){ }
}
}
}

void func2( ) {
System.out println(“func2:start”);
synchronized(y){
synchronized(x){
try{
sleep(10);
}catch(Exception e){ }
}
}
}

public void run( ){
for ( int i = 0 ; i < 1000 ; i++ ){
func1( );
func2( );
}
}
}

class Test{
pubic static void main ( String args[ ]) {
X x = new X( );
Y y = new Y( );

A a[ ] = new A[10];
   for( int i = 0 ; < 10 ; i++ ) {
try{
                 a[i].join( );
}catch(Exception e){ }
}
}
}

C:\java>javac Test.javaC:\java>java Test
func1:start
func1:start
func1:start
func1:start
func1:start
func1:start
func1:start
func1:start
func2:start
func1:start
func1:start
func2:start       ←ここでデットロックが発生

 

 多くの場合、ロックするオブジェクトを同じ順番でロックすれば、デットロックは防げる。例えば上記では、func2のsychronizedでロックする順番をfunc1と同じように「x→y」の順番でロックすればデットロックは防げる。言い換えるとオブジェクトに優先順位を与えるということになる。
このようにマルチスレッド特有のバグが発生した場合の多くは、バグを再現させることすら難しく、デバッグ作業は大変な作業になってしまう。バグが起きないようにあらかじめちゃんと設計してからプログラミングする必要がある。
8.スレッド間の通信
 スレッドから一時的にロックを解放し、他のスレッドにsynchronizedメソッドもしくはsynchronizedブロックを実行するチャンスを与えることができる。例えば、一つ目のスレッドはメッセージを受信するスレッドで、二つ目のスレッドはメッセージを与えるスレッドである。メッセージ受信のスレッドは、メッセージボックスにメッセージがなければメッセージが格納されるまですることがない。このような場合ほかのスレッド(メッセージ送信スレッド)に制御を移す。逆にメッセージ送信スレッドは、メッセージボックスがいっぱいならば、メッセージ受信スレッドがメッセージを処理し終わるまで待たなければならない。
このように、他のスレッドが処理し終わるまで待つ場合は「wait」メソッドを使用し、他のスレッドに制御を与える場合は「notify」または「notifyAll」メソッドを使用する。notifyAllメソッドはwaitしているすべてのメソッドに対して通知をするが、notifyメソッドは、いずれか一つのスレッドに対してのみ通知する。どのスレッドに通知されるかは、JVMが勝手に決めていいことになっている。
Test.java import java.util.*;

// メッセージボックス
class MessageBox{
     private String message;

// メッセージボックスにメッセージを入れる
synchronized void  messageIn(String msg){
try{
// メッセージがすでに入っていれば、なくなるまで待つ
while( message != null){
wait( );
}

// メッセージを入れる
message = msg;

// メッセージを入れ終わったら別のスレッドを起こす
notifyAll( );

// 以下例外処理
}catch(Exception e){
System.err.println(“messageIn:Error:”+e);
System.exit(1);
}
}

// メッセージ取り出し
synchronized String messageOut( ){
try {
// メッセージがなければ、メッセージが入るまで待つ
while(message == null){
wait( );
}

// メッセージ取り出し
String s == message;
message = null ;

// 取り出したら別のスレッドを起こす
notifyAll( );
return s ;
// 以下例外処理
} catch (Exception e ){ }
System.err.println(“messageOut:Error”);
System.exit( 1);
return “”;
}
}

// 以下例外処理
class MessageSender extends Thread{
public void run( ){
for( int i = 0 ; i < 100 ; i++ ){
Test.msgBox.messageIn(“”+Math.random( ));
          }
}
}

// メッセージを取り出し、表示するスレッド
class MessageReciever extends Thread {
public void run ( ){
          for ( int i = 0 ; i < 100 ; i++ ){
System.out.println(Test.msgBox.messageOut( ));
}
}
}

class Test{
public static MessageBox msgBox = new MessageBox( );

public static  void main(String args[ ]){
MessageReciever reciever = new MessageReciever( );
reciever.start( );

MessageSender sender = new MessageSender( );
sender.start( );

try{
sender.join( ;
reciever.join( );
}catch(Exception e){ }
}
}

C:\java>javac Test.javaC:\java>java Test
0.2894081823761079
0.20714542091971344

中略

0.519766868805261
0.6181891980702379

C:\java>