7章 クラス
1.this キーワード
MyPoint.java

class MyPoint{ 
     double m_x; 
     double m_y;

     // コンストラクタ
     public MyPoint( ) {
       System.out.println("コンストラクタ1が呼ばれました");
          m_x = 10;
          m_y = 20;
     }

     // コンストラクタ2
     public MyPoint( double x, double y ) {
       System.out.println("コンストラクタ2が呼ばれました");
          m_x = x;
          m_y = y;
     }

     // 文字列に変換する
     public String toString( ) {
       return "X:"+m_x+" Y"+m_y;
     }

     // 距離を計算する
     public double calcDistance(MyPoint pnt ) {
       // Mathクラスは数学関係を担うクラス
        // Math.pow累乗を計算するメソッド
        // Math.sqrtはルートを計算するメソッド
        return Math.sqrt(Math.pow((m_x – pnt.m_x), 2) + Math.pow((m_y – pnt.m_y), 2));
      }
}

 まず、コンストラクタはインスタンスが作られるときに最初に呼ばれるメソッドである。MyPointクラスでは引数をとるコンストラクタと引数をとらないコンストラクタの2種類が用意されている。メソッドでもそうだが、引数の異なる同じ名前のメソッドを複数用意することをメソッド(コンストラクタ)をオーバーロードするという。
 引数なしのコンストラクタは変数「m_x」と「m_y」にそれぞれ10と20をを代入しているが、これは引数ありのコンストラクタに10,20を渡しても同じ結果が得られる。コンストラクタから同じクラス内の別のコンストラクタを呼び出すこともできる。もっと複雑なクラスになると、コンストラクタを複数用意する場合、ある一部を除いて同じことをする。このため、あるコンストラクタから別のコンストラクタを呼び出すと便利な場合が多い。その方法は以下のように「this」キーワードを用いる。ただし、コンストラクタの一番最初で使用しなければならない。  
MyPoint.java

class MyPoint{ 
     double m_x; 
     double m_y;

     // コンストラクタ
     public MyPoint( ) {
         this( 10,20);
        System.out.println("コンストラクタ1が呼ばれました");
//       m_x = 10;
//       m_y = 20;

     }

     // コンストラクタ2
     public MyPoint( double x, double y ) {
       System.out.println("コンストラクタ2が呼ばれました");
          m_x = x;
          m_y = y;
     }

     // 文字列に変換する
     public String toString( ) {
       return "X:"+m_x+" Y"+m_y;
     }

     // 距離を計算する
     public double calcDistance(MyPoint pnt ) {
       // Mathクラスは数学関係を担うクラス
        // Math.pow累乗を計算するメソッド
        // Math.sqrtはルートを計算するメソッド
        return Math.sqrt(Math.pow((m_x – pnt.m_x), 2) + Math.pow((m_y – pnt.m_y), 2));
      }
}

 calcDistanceメソッドでは、引数にMyPointクラスをもらっている。この場合、同じ名前の変数(m_x, m_y)が頻繁に出てくるため、プログラムを見直した場合、自分自身のインスタンスの変数か、引数でもらってきたインスタンスの変数が紛らわしくなることがある。この場合、thisというキーワードをつけて明示的に区別することができる。したがって、下記のように書き直すこともできる。(変数だけでなく、メソッドにもthisをつけることもできる。)
MyPoint.java

class MyPoint{ 
     double m_x; 
     double m_y;

     // コンストラクタ
     public MyPoint( ) {
         this( 10,20);
        System.out.println("コンストラクタ1が呼ばれました");
//       m_x = 10;
//       m_y = 20;

     }

     // コンストラクタ2
     public MyPoint( double x, double y ) {
       System.out.println("コンストラクタ2が呼ばれました");
          m_x = x;
          m_y = y;
     }

     // 文字列に変換する
     public String toString( ) {
       return "X:"+m_x+" Y"+m_y;
     }

     // 距離を計算する
     public double calcDistance(MyPoint pnt ) {
       // Mathクラスは数学関係を担うクラス
        // Math.pow累乗を計算するメソッド
        // Math.sqrtはルートを計算するメソッド
        return Math.sqrt(Math.pow((this.m_x – pnt.m_x), 2) + Math.pow((
this.m_y – pnt.m_y), 2));
      }
}

 
2.public,protected,private
 IntStackクラスでは、メンバー変数を変えられて誤動作する可能性がある。そこで、この変数をprivateにしたほうがバグの可能性が少なくなる。
IntStack.java

public class IntStack{ 
     ///////////////////////////////////////////////////
    // 管理情報

 private int m_sp;           //  スタックポインタ:次にプッシュする位置
 private int m_size;        //  スタックサイズ
 private int m_stack[ ];   //  スタック
   
   // コンストラクタ:引数あり
   // IntStack オブジェクトが定義された時に自動的に呼び出される
 private IntStack( int sz ){

          // スタックサイズを保存する
            m_size = sz ;

          // スタックの実体(size個のint配列)自由記憶上に割り当てる
            m_stack = sz ;

          // スタックポインタを初期化する
            m_sp = 0 ;
     }

     // コンストラクタ:引数なし
     // IntStack オブジェクトが定義された時に自動的に呼び出される
     public IntStack( ) {
          // もう一つのコンストラクタを呼び出す
             this(100);
     }

     // プッシュ
     public void push( int value ) {
    if ( m_sp >= m_size ){
        System.err.println("stack overflow");
        System.exit(1) ;               // 異常終了する
      }

         m_stack[m_sp] = value ;
         m_sp = m_sp + 1 ;
      }

     // ポップ
     public int pop( ) {
            // アンダーフローのチェック
    if( m_sp <=0 ) {
           System.err.println("stack overflow");
           System.exit(1) ;               // 異常終了する
          }
         m_sp = m_sp – 1 ;
         return m_stack[m_sp] ;
          }
      // 現在のスタック長を獲得する
      public int getLength( ){
         return m_sp ;
    }

      // 指定位置(スタックトップからのオフセット)のスタック要素を覗き見る
      public int peek( ) {
          // オフセットのチェック
         if ( 0 >= m_sp ){
           System.err.println("stack overflow");
           System.exit(1) ;               // 異常終了する
    }
          // 指定位置の要素をコピーする
         return m_stack[m_sp-1];
    }
}

なお、以前のIntStackクラスでは、メンバー変数に対して、private,protected,publicいずれも指定していなかった。この場合、同じパッケージのクラスからのみ扱える(protectedはそれに加えてサブクラスでも扱える) 
今のところ、パッケージについては後述するが、パッケージを指定しない場合は、同じファイルに複数のクラスを作成した時に、同じファイル内で定義されているクラスのみ扱えるようになる。
修飾子 意味
未指定 同じパッケージ。パッケージ未指定の場合は、同じファイル内に定義したクラス。
public  すべてのクラス。
protected   同じパッケージ。サブクラス。
private  同一クラスのみ
 
3.静的変数・静的メソッド・静的ブロック
静的変数 これまでインスタンスを作ると、それに伴い変数も複数用意された。すなわち、インスタンス毎に変数が用意されたわけである。静的変数というのは、クラスに一つだけの変数で、複数のインスタンスを作っても一つしか作られない変数である。また、インスタンスを作らなくても使用できる変数である。主に定数として使う。  
 静的変数を作るには、変数宣言の前に「Static」とつける。
Test.java

class A{
     
int          a;
     
static int s;
}

class Test{
      public static void main( String args[ ]) {
          // インスタンスを作らなくても、静的変数は使える
         A.s = -10;
         
System.out.println("A.s=" + A.s);

             A a1 = new A( );
             A a2 = new A( );

          System.out.println("a1.a=" + a1.a + "\ta1.s=" + a1.s);

          a1.a = 10;
          a2.a = 20;
          a1.s = 100;
          a2.s = 200;
 

          System.out.println("a1.a=" + a1.a + "\ta1.s=" + a1.s);
          System.out.println("a2.a=" + a2.a + "\ta2.s=" + a2.s);
    }
}
 

C:\java>javac Test.java

C:\java>java Test
A.s=-10
a1.a=0 a1.s=-10
a1.a=10 a1.s=200
a2.a=20 a2.s=200

C:\java>

上記の例では、a2.sに200を代入しているにもかかわらず、a1.sも200になっている。性的変数は、複数のインスタンスを作成しても、全てのインスタンスが同じ変数を共通で使用するためである。 
静的メソッド 
 静的メソッドは、インスタンスに作用するメソッドではなくクラスに作用するメソッドである。つまり、静的変数のみ使用することができ、通常のインスタンス変数を扱うことはできない。また、同一クラスの他の制的メソッドを呼び起こすことはできるが、通常のインスタンスメソッドを呼び起こすことはできない。(通常のインスタンスメソッドから静的メソッドを呼び起こすことはできる。)

 静的メソッドは、クラスに作用するメソッドであるからインスタンスが作られてなくても使用する事ができる。
 静的メソッドを作るには、メソッド宣言の前に「static」をつけるだけである。 
Test.java

class A{
     
int          a;
     
static int s;

      // 静的メソッド
      static void hoge( ){
         s = -10;

              // 静的変数から、インスタンス変数にアクセスできないからコンパイルエラー
//    a = -10;
     }
}

class Test{
       
public static void main( String args[ ]) {
              // インスタンスを作らなくても、静的変数は使える
           System.out.println("A.s=" + A.s);

          A a = new A( );

          a.a = 10;
          a.s = 100;

          System.out.println("a.a=" + a.a + "\ta.s=" + a.s);
    }
}
 

C:\java>javac Test.java

C:\java>java Test
A.s=0
A.s=-10
a.a=10 a.s=100

C:\java>

静的ブロック
 インスタンス変数を初期化するためにコンストラクタがあるように、静的変数を初期化するために静的ブロックというものがある。これは、そのクラスが使用される直前に呼び出される。クラスを作っている段階では、どのようにそのクラスが使われるかわからないので、どのような順番で実行されてもいいように作らなければならない。つまり、自分のクラスの静的変数の初期化のみを行うのが一般的である。

 静的ブロックは「static」の後にブロックを作る。
Test.java

class A{
     
int          a;
     
static int s;

      // 静的ブロック
     
static {
           System.out.println("Aクラスの静的ブロック");
     }
}

      // 静的ブロック
     
static {
           System.out.println("Testクラス静的ブロック");
     }

     public static void main( System args[ ] ) {
          
System.out.println("mainメソッド実行開始");

             A.s = 10;
     }
}

Test.java

class A{
     
int          a;
     
static int s;

      // 静的ブロック
     
static {
           System.out.println("Aクラスの静的ブロック");
     }
}
class Test{
      // 静的ブロック
     
static{
           System.out.println("Testクラスの静的ブロック");

             A.s = 10;
     }

     public static void main(String args[ ]) {
           System.out.println("mainメソッド実行開始");
     }
}

C:\java>javac Test.java

C:\java>java Test
Testクラス静的ブロック
mainメソッド実行開始
Aクラスの静的ブロック

C:\java>

C:\java>javac Test.java

C:\java>java Test
Testクラス静的ブロック
Aクラスの静的ブロック
mainメソッド実行開始

C:\java>

 Aクラスの使用する場所が異なると、実行される順番がかわる。
 
4.ローカル変数
 ローカル変数 メンバー変数(インスタンス変数や静的変数)はクラス定義内で定義している。ローカル変数とはこれまでにも何度か登場しているが、メソッド内などブロック内(中括弧)で定義されている変数である。
class Test{
     public static void main(String args[ ]){
           int i;    // これがローカル変数
  }
}
 
class Test{
     public static void main(String args[ ]){
           int i;
       
       
    // 不確定な変数を使用することはできないのでコンパイルエラー
     System.out.println( i );
  }
}
C:\java>javac Test.java
Test.java:6: Variable i may not have been initialized.
          System.out.println( i );
                                      ^
1 error

C:\java>

class Test{
     public static void main(String args[ ]){
           int i;
       
       
    // 不確定な変数を使用することはできないのでコンパイルエラー
     System.out.println( i );
  }
}
 ローカル変数は、定義されたブロック内でしか使用することができない。変数を使用できる範囲のことをスコープという。Cではブロックの最初でしか変数を定義できなかったが、Javaではブロックのどこでも宣言できる。しかし宣言したブロックを出ると、スコープの範囲外になり、その変数は使用できない。  
class Test{
     public static void main(String args[ ]) {
     System.out.println("mainメソッド");

           // ブロックの途中でも宣言できる
          
int i = 0;
       
           for( ; i < 5 ; i++ ){
                int j = 0;
                // ブロック内であればその中のブロックの中でも使用できる
       System.out.println( i );
     }
           // ローカル変数は[ j ]はスコープを出ているのでコンパイルエラー
     System.out.println( j );
  }
}

C:\java>javac Test.java
Test.java:14: Undefined variable: j
          System.out.println( j );
                                      ^
1 error

C:\java>

 また、特例として、for文の初期設定で変数を宣言することができる。この場合、そのfor文(を含むブロック)がスコープになる。 
class Test{
     public static void main(String args[ ]) {
     System.out.println("mainメソッド");

           // for文の初期設定でもローカル変数を宣言できる
           for ( int i = 0 ; i < 5 ; i++ ){
                // ローカル変数[i]はfor文(ブロック)でのみ有効だから、ここではOK!
       System.out.println( i );
     }
         
// ローカル変数[i]はスコープを出ているのでコンパイルエラー
       System.out.println(i);
     }
}

C:\java>javac Test.java
Test.java:11: Undefined variable: j
          System.out.println( i );
                                      ^
1 error

C:\java>

 ローカル変数は、メンバー変数と同じ名前で使用する事ができる。この場合、ローカル変数のスコープ内ではローカル変数が仕様されスコープを外れるとメンバー変数が使用される。つまり、そのスコープ内では、メンバー変数が隠されることになる。この場合、インスタンス変数の場合は「this」キーワードを、静的変数の場合は「クラス名」を使用することで、隠されているメンバー変数にアクセスできる。
class Test{
      // 静的変数として[m]を定義
     
static int m;

      public static void main(String args[ ]) {
           System.out.println("m=" + m);

             {
                
// 静的変数とし同じ[m]を定義
       
int m = 10;
                    System.out.println("m=" + m);
 
                 // 静的変数の場合は[クラス名.]を使用する
  
          System.out.println("Test.m=" + Test.m);
           }

         System.out.println("m=" + m);
     }
}

class Test{
      // インスタンス変数として[m]を定義
     
int m;

     private void hoge( ){
          System.out.println("m=" + m);
         {
                  m = 1; // this.m =1 と同じ

          // インスタンス変数とし[m]を定義     }
        
int m = 10;
        
      System.out.println("m=" + m);

         // インスタンス変数の場合は[this.]を使用する
      System.out.println("this.m=" + this.m);

             }

     System.out.println("m=" + m);
             }

          private static void main(String args[ ]) {
               new Test().hoge( );
             }
}

C:\java>javac Test.java

C:\java>java Test
m=0
m=10
Test.m=0
m=0

C:\java>

C:\java>javac Test.java

C:\java>java Test
m=0
m=10
this.m=1

m=1

C:\java>

class Test{
     public static void main(String args[ ] ) {
           int a = 0;
           {
               // これはエラー
               int a = 10;
        }
     }
}
 
C:\java>javac Test.java

Test.java:6: Variable 'a' is already defined in this method.
                iint a = 10;
                      ^
1 error

C:\java>

 
5.メソッドの引数
 メソッドへ引数を渡す場合、渡した先のメソッドで値を変えられたらどうなるのでしょう。
class Test{
     public static void main(String args[ ] ) {
           int a = 10;
           increment( i );

        System.out.println( i );
     }
    private static void increment ( int i ) {
        i++;
    
}
}

 
C:\java>javac Test.java

C:\java>java Test
10

C:\>

 
 上の例のように、incrementメソッドに10が代入されている変数[i]を渡して、メソッドの中で値を変えても、呼び出し元に戻ってきてもその値はかわっていません。
 では、渡す引数が配列だった場合はどうだろう。
class Test{
     public static void main(String args[ ]) {
           int array[ ] = {  1,2,3,4,5,6,7,8,9,10 };
        increment(array);

           for( int i = 0 ; i < array.length ; i++ )
       System.out.println( "array[" + i + "]=" + array[i] );
    
}

     private static void increment( int[ ] array) {
           for int i =  0 ; i < array.length ; i++ ) 
         array[i]++;
   }
}

C:\java>javac Test.java

C:\java>java Test
array[0]=2
array[1]=3
array[2]=4
array[3]=5
array[4]=6
array[5]=7
array[6]=8
array[7]=9
array[8]=10
array[9]=11

C:\>

配列の場合は、配列の中身が変わってしまっている。では、もうひとつ引数がクラスのインスタンスだった場合はどうだろう。
class Int{
     Int i;

     // コンストラクタ
    public Int( int n) {
{        i = n;
    }
     // コンストラクタ
     public Int( ) {
         this(0);
  }

     public void increment( ) {
           i++;
  }
     public void decrement( ) {
           i–;
  }
     public int intValue( ) {
    return i;
     }
     public String toString( ) {
    return "" + i;
     }
}

class Test{
     public static void main(String args[ ]) {
         Int i = new Int( );

         System.out.println(i.toString( ));
         increment(i);
        
System.out.println(i.toString( ));
    }

     private static void incremet( Int i ) {
         i.increment( );
    }
}

 
C:\java>javac Test.java

C:\java>java Test
0
1

C:\>

 
 このように、インスタンスの場合も値が変わってしまう。
 参照渡しというのは、インスタンスそのものを渡すのでメソッドの先で値を変えるとそれが呼び出しもとにも反映される。 実はJavaではint型やdouble型などのプリミティブ型ではコピー渡しをする。

 配列にしてもインスタンスにしてもその中身はメモリのどこかに格納されているわけだが、そのアドレスが引数として呼び出し先に渡される。呼び出し先では渡されたアドレスがさしているインスタンスや配列を変えてしまえば呼び出し元に戻ってきてもその変更が影響される。

 

 呼び出し先ではどうなるか。
class Test{
     public static void main(String args[ ]) {
           int array[ ] = {  1,2,3,4,5,6,7,8,9,10 };
        increment(array);

           for( int i = 0 ; i < array.length ; i++ )
       System.out.println( "array[" + i + "]=" + array[i] );
    
}

     private static void increment( int[ ] array) {
         array = new int[10];
                   forint i =  0 ; i < array.length ; i++ ) 
                         array[i] = i+2;
   }
}

C:\java>javac Test.java

C:\java>java Test
array[0]=1
array[1]=2
array[2]=3
array[3]=4
array[4]=5
array[5]=6
array[6]=7
array[7]=8
array[8]=9
array[9]=10

C:\>

class Int{
     int i;

    // コンストラクタ
        public
i;

    // コンストラクタ
        public
Int( ){
           this(0);
       }

     public void increment( ) {
      i++;
   }
     public void decrement( ) {
      i–;
   }
     public int intValue( ) {
      return i;
   }
     public String toString( ) {
      return "" + i;
   }
}

class Test{
     public static void main(String args[ ]){
          Int i = new Int( );

         System.out.println(i.toString( ));
        
increment( );
         System.out.println(i.toString( ));
    }

     private static void increment(Int i ){
         i = new Int(i.intValue() + 1);
    }
}

C:\java>javac Test.java

C:\java>java Test
0

0

C:\>

 このように、呼び出し先でnewしなおした場合は、結果は反映されていない。変数は、本来(配列の実体など)が格納されているアドレスを持っているに過ぎず、引数はそのアドレスのコピーが渡されるからである。つまり、newしなおして、アドレスが変更されても、変更されたアドレスはコピーにしか過ぎず、呼び出し元はnewする前の古いアドレスを持っているからである。

 

 
6.null
 下記のように変数だけ宣言してnewせずに使用するとコンパイルエラーとなる。
class Test{
     public static void main(String args[ ]) {
           String s;
           System.out.println( s );
     }
}
C:\java>javac Test.java

Test.java:4 Variable s may not have been initialized.
           System.out.println( s );

C:\java>

これはインスタンスを入れるための入れ物(変数)であるsには、まだインスタンスが入っていないのに使用しようとしたためである。インスタンスを使うためにはインスタンスをつくらなければならない。
逆に、使い終わったインスタンスは、使い終わったことを示すために、入れ物(変数)にインスタンスがないことを示す値を代入する。これが、「null」である。
class Test{
     public static void main(String args[ ]) {
           String s = new String("abcdefg") ;
           String.out.println(s);

            // 使い終わったことを示す
           s = null ;
     }
}

C:\java>javac Test.java

C:\java>java Test
abcdefg

C:\java>

 このnullは配列にも同じように使用する。
class Test{
      public static void main(String args[ ]){
           int a[ ] = new int [10];

              // 使い終わったことを示す
           a = null ;
       }
}
C:\java>javac Test.java

C:\java>java Test
1
C:\>

 使い終わったnullを代入するというのは、強制ではない。例えば上の例でnullを代入しなくてもエラーはでないしバグもおきない。またプログラムが終了すれば、自動的に使用していたメモリーも解放するたメモリーリーク(メモリにごみが残る現象)もない。
 しかし、javaではガーベージコレクターというメモリーを掃除するきのうがある。これは、常時いらないインスタンスを消去する。使い終わったインスタンスをnullに置き換えないとそのインスタンスが使用中なのかわからないため掃除することができない。したがって、nullを代入することが推奨される。
 また、nullは使い終わったことを示すだけではなく、インスタンスがないことを示すので、使い終わったことを示すだけではなく「インスタンスがない」という事をしめすような使い方をすることもできる。
 例えば、引数にint型のnをもらい、アルファベットのn番目の文字をString型で返すメソッドをつくる。該当する文字がない場合は、nullを返す。
class Test{
      private static String alphabet( int n ){
           String s ;
             if( n < 1 | | n  > 26 ){
                // 該当する文字がない場合はnullを返す
      
       s = null ;
             }
          else{
           char ch[ ] = { 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V'',W','X','Y','Z'} ; 

                 // char配列chのn-1番目から1文字分の文字列を作る
      
          s = new String(ch, n-1, 1);
           }
          return s;
     }

      private static void print(String s){
            if ( s == null )
                 System.err.println("nullです");
            else
                 System.out.println(s);
    
}

      public static void main(String args[ ]){
           int n = 0;
           String s;

           s = alphabet(0);
           print(s);

           s = alphabet(26);
           print(s);
     }
}

C:\java>javac Test.java

C:\java>java Test
nullです
Z

C:\java>1

 
7.インナークラス(内部クラス)
 インナークラスは、あるクラスの中で定義するクラスである。あるクラスの中でのみ使用するデータ群をまとめてしまうのに便利である。 例えば、int型の数字10個からなるデータがあるとする。10個ものデータを別個に管理するのは面倒なので、int型10個の数値を1つのクラスとしてまとめてしまうことにする。このデータをDataクラスとする。しかし、このDataクラスは他のクラスからは参照されないとすると、publicスコープでDataクラスを宣言する必要はないのでインナークラスとして宣言する。もし、複数の人で1つのプログラムを作っていたとする。publicなスコープにDataクラスを宣言すると他の人はDataクラスというクラスを作る事ができなくなってしまう。作ってしまうとバグになってしまう。したがって、できるだけpublicなスコープでクラスなどは作らないようにするべきなのである。
class Test{
     // インナークラス
    
class Data{
          int a1,a2,a3,a4,a5,a6,a7,a8,a9,a10; 
     }

    private static void print(String args[ ]){
            Data data = new Data( );
     }
}

 
8.クラスの包含
 クラスの包含とは、どのようにあるクラスが別のクラスのインスタンスを持つかという関係を表す。
class A_Class{
      B_Class B;

     // コンストラクタ
    
public A_Class( ){
          B = new B_Class( )
     }
}

class A_Class{
      B_Class pB;
}

A_Classのインスタンスが存在する間はB_Classのインスタンスも存在する。逆にB_Classのインスタンスがなくなってしまえば、A_Classは機能しなくなるような場合に用いられる。

このような場合はコンストラクタでインスタンスが作られる場合が多い。

A_Classのインスタンスが存在する間も、B_Classのインスタンスが存在する場合と存在しない場合があるような時に用いられる。

この場合、インスタンスは適当なところで作られる。

例)自動車クラスはエンジンクラスを包含するなど。
(この場合、エンジンがなくなれば自動車ではなくなる)

  
例)自動車クラスは運転手クラスを包含するなど。
(この場合、運転手がいてもいなくても、自動車は自動車であることにかわりはない。)