7章 クラス | |
1.this キーワード | |
MyPoint.java
class MyPoint{ // コンストラクタ // コンストラクタ2 // 文字列に変換する // 距離を計算する |
|
まず、コンストラクタはインスタンスが作られるときに最初に呼ばれるメソッドである。MyPointクラスでは引数をとるコンストラクタと引数をとらないコンストラクタの2種類が用意されている。メソッドでもそうだが、引数の異なる同じ名前のメソッドを複数用意することをメソッド(コンストラクタ)をオーバーロードするという。 引数なしのコンストラクタは変数「m_x」と「m_y」にそれぞれ10と20をを代入しているが、これは引数ありのコンストラクタに10,20を渡しても同じ結果が得られる。コンストラクタから同じクラス内の別のコンストラクタを呼び出すこともできる。もっと複雑なクラスになると、コンストラクタを複数用意する場合、ある一部を除いて同じことをする。このため、あるコンストラクタから別のコンストラクタを呼び出すと便利な場合が多い。その方法は以下のように「this」キーワードを用いる。ただし、コンストラクタの一番最初で使用しなければならない。 |
|
MyPoint.java
class MyPoint{ // コンストラクタ // コンストラクタ2 // 文字列に変換する // 距離を計算する |
|
calcDistanceメソッドでは、引数にMyPointクラスをもらっている。この場合、同じ名前の変数(m_x, m_y)が頻繁に出てくるため、プログラムを見直した場合、自分自身のインスタンスの変数か、引数でもらってきたインスタンスの変数が紛らわしくなることがある。この場合、thisというキーワードをつけて明示的に区別することができる。したがって、下記のように書き直すこともできる。(変数だけでなく、メソッドにもthisをつけることもできる。) | |
MyPoint.java
class MyPoint{ // コンストラクタ // コンストラクタ2 // 文字列に変換する // 距離を計算する } } |
|
2.public,protected,private | |
IntStackクラスでは、メンバー変数を変えられて誤動作する可能性がある。そこで、この変数をprivateにしたほうがバグの可能性が少なくなる。 | |
IntStack.java
public class IntStack{ private int m_sp; // スタックポインタ:次にプッシュする位置 // スタックサイズを保存する // スタックの実体(size個のint配列)自由記憶上に割り当てる // スタックポインタを初期化する // コンストラクタ:引数なし // プッシュ m_stack[m_sp] = value ; // ポップ // 指定位置(スタックトップからのオフセット)のスタック要素を覗き見る |
|
なお、以前のIntStackクラスでは、メンバー変数に対して、private,protected,publicいずれも指定していなかった。この場合、同じパッケージのクラスからのみ扱える(protectedはそれに加えてサブクラスでも扱える) 今のところ、パッケージについては後述するが、パッケージを指定しない場合は、同じファイルに複数のクラスを作成した時に、同じファイル内で定義されているクラスのみ扱えるようになる。 |
|
修飾子 | 意味 |
未指定 | 同じパッケージ。パッケージ未指定の場合は、同じファイル内に定義したクラス。 |
public | すべてのクラス。 |
protected | 同じパッケージ。サブクラス。 |
private | 同一クラスのみ |
3.静的変数・静的メソッド・静的ブロック | |
静的変数 これまでインスタンスを作ると、それに伴い変数も複数用意された。すなわち、インスタンス毎に変数が用意されたわけである。静的変数というのは、クラスに一つだけの変数で、複数のインスタンスを作っても一つしか作られない変数である。また、インスタンスを作らなくても使用できる変数である。主に定数として使う。 静的変数を作るには、変数宣言の前に「Static」とつける。 |
Test.java
class A{ A a1 = new A( ); System.out.println("a1.a=" + a1.a + "\ta1.s=" + a1.s); a1.a = 10; System.out.println("a1.a=" + a1.a + "\ta1.s=" + a1.s); |
||||||
C:\java>javac Test.java
C:\java>java Test C:\java> |
上記の例では、a2.sに200を代入しているにもかかわらず、a1.sも200になっている。性的変数は、複数のインスタンスを作成しても、全てのインスタンスが同じ変数を共通で使用するためである。 | |
静的メソッド 静的メソッドは、インスタンスに作用するメソッドではなくクラスに作用するメソッドである。つまり、静的変数のみ使用することができ、通常のインスタンス変数を扱うことはできない。また、同一クラスの他の制的メソッドを呼び起こすことはできるが、通常のインスタンスメソッドを呼び起こすことはできない。(通常のインスタンスメソッドから静的メソッドを呼び起こすことはできる。) 静的メソッドは、クラスに作用するメソッドであるからインスタンスが作られてなくても使用する事ができる。 静的メソッドを作るには、メソッド宣言の前に「static」をつけるだけである。 |
|
Test.java
class A{ class Test{ A a = new A( ); a.a = 10; System.out.println("a.a=" + a.a + "\ta.s=" + a.s); |
|
C:\java>javac Test.java
C:\java>java Test C:\java> |
|
静的ブロック インスタンス変数を初期化するためにコンストラクタがあるように、静的変数を初期化するために静的ブロックというものがある。これは、そのクラスが使用される直前に呼び出される。クラスを作っている段階では、どのようにそのクラスが使われるかわからないので、どのような順番で実行されてもいいように作らなければならない。つまり、自分のクラスの静的変数の初期化のみを行うのが一般的である。 静的ブロックは「static」の後にブロックを作る。 |
|
Test.java
class A{ public static void main( System args[ ] ) { A.s = 10; |
Test.java
class A{ |
C:\java>javac Test.java
C:\java>java Test C:\java> |
C:\java>javac Test.java
C:\java>java Test 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); { System.out.println("m=" + m); |
class Test{ // インスタンス変数として[m]を定義 int m; private void hoge( ){ System.out.println("m=" + m); { m = 1; // this.m =1 と同じ // インスタンス変数とし[m]を定義 } System.out.println("m=" + m); // インスタンス変数の場合は[this.]を使用する System.out.println("m=" + m); private static void main(String args[ ]) { |
C:\java>javac Test.java
C:\java>java Test C:\java> |
C:\java>javac Test.java
C:\java>java Test 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. 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 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++ ) |
|
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{ System.out.println(i.toString( )); private static void incremet( Int i ) { |
|
C:\java>javac Test.java
C:\java>java Test 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++ ) private static void increment( int[ ] array) { |
|
C:\java>javac Test.java
C:\java>java Test C:\> |
|
class Int{ int i; // コンストラクタ public i; // コンストラクタ public void increment( ) { System.out.println(i.toString( )); private static void increment(Int i ){ |
|
C:\java>javac Test.java
C:\java>java Test 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. C:\java> |
|
これはインスタンスを入れるための入れ物(変数)であるsには、まだインスタンスが入っていないのに使用しようとしたためである。インスタンスを使うためにはインスタンスをつくらなければならない。 逆に、使い終わったインスタンスは、使い終わったことを示すために、入れ物(変数)にインスタンスがないことを示す値を代入する。これが、「null」である。 |
|
class Test{ public static void main(String args[ ]) { String s = new String("abcdefg") ; String.out.println(s); // 使い終わったことを示す |
|
C:\java>javac Test.java
C:\java>java Test 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 |
|
使い終わった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文字分の文字列を作る private static void print(String s){ public static void main(String args[ ]){ s = alphabet(0); s = alphabet(26); |
|
C:\java>javac Test.java
C:\java>java Test 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[ ]){ |
|
8.クラスの包含 | |
クラスの包含とは、どのようにあるクラスが別のクラスのインスタンスを持つかという関係を表す。 | |
class A_Class{ B_Class B; // コンストラクタ |
class A_Class{ B_Class pB; }
|
A_Classのインスタンスが存在する間はB_Classのインスタンスも存在する。逆にB_Classのインスタンスがなくなってしまえば、A_Classは機能しなくなるような場合に用いられる。
このような場合はコンストラクタでインスタンスが作られる場合が多い。 |
A_Classのインスタンスが存在する間も、B_Classのインスタンスが存在する場合と存在しない場合があるような時に用いられる。
この場合、インスタンスは適当なところで作られる。 |
例)自動車クラスはエンジンクラスを包含するなど。 (この場合、エンジンがなくなれば自動車ではなくなる) |
例)自動車クラスは運転手クラスを包含するなど。 (この場合、運転手がいてもいなくても、自動車は自動車であることにかわりはない。) |