Java言語入門 〜C言語を学んだ君へ〜
[1] オーバーロード
ここでは、オーバーロードを説明します。
オブジェクト指向の概念であるポリモーフィズムに関わってくる内容です。
知っていると、わかりやすいソースが書けるようになります。
オーバーロードとは
まずはオーバーロードがどういうものなのかを簡単に説明をしておきます。
オーバーロードとは、全く同じ名前を持つメソッド(もしくはコンストラクタ)を複数定義すること
を言います。次のプログラムを参考にメソッドのオーバーロードについて説明をします。
オーバーロードのサンプルプログラム
public class Java08_03{
public static void main(String args[]){
overload test = new overload();
//オーバーロードメソッドを実行
test.func();
test.func(10);
test.func(10,20);
test.func("test");
}
}
class overload{
// 引数なし
void func() {
System.out.println("引数なしメソッド");
}
// int型の引数1個
void func(int a) {
System.out.println("int型の引数1個メソッド");
}
// int型の引数2個
void func(int a, int b) {
System.out.println("int型の引数2個メソッド");
}
// String型の引数1個
void func(String a) {
System.out.println("String型の引数1個メソッド");
}
}
実行結果
func()というメソッドを4つ作成しています。
しかし、Javaではこの4つとも定義することが可能です。それは、
引数の個数と引数の型が異なれば、同じ名前を複数定義できる
このように4つのメソッドを定義しても引数が異なるため、同じクラス内に定義することができます。
オーバーロードで定義したメソッドがどのように判断されるのかについては引数で決定しています。
同じ名前でも引数で区別することでどの引数のメソッドを実行するのか選択することができます。
オーバーロードの利点とは何か
オーバーロードは同じ名前のメソッド(コンストラクタ)で、引数の型と個数が異なることにより、複数使えると説明しました。
では、オーバーロードの利点とは何なのでしょうか。
文字を出力する方法(ここでは、printメソッドとする)で考えていきます。
オーバーロードがない場合
○ print(10);
× print("ほぷしぃ");
○ printString("ほぷしぃ");
同じ名前をつけることができないのでメソッド名を変えなくてはいけません。
上の例では1つ目が数字を引数として画面に出力するメソッドです。
2つ目は文字列ですが、オーバーロードがないため使うことができません。
そこで、3つ目のメソッドで文字列を画面に出力するメソッドを作ります。
これが5個、10個のメソッドになれば、それだけ管理が難しく大変になります。
わざわざ引数の違いによって異なるメソッド名で呼び出す必要まであります。
引数がどのような型でも、引数をコンソール出力するという意味では同じなのにも関わらずです。
画面に出力する方法が1つだけのほうがずいぶん便利だと感じると思います。
オーバーロードがある場合
print(10);
print("ほぷしぃ");
ない場合と比べて画面に出力するといったらprintメソッドを使うとなると大変楽です。
実は、これまでのソースにすでにオーバーロードは登場していました。
つまり、オーバーロードのありがたみをすでに体験していたのです。
画面に出力するときに"System.out.println();"という処理を使っていましたが、実はこれもオーバーロードがあるメソッドの1つなのです。
オーバーロードがあれば、同じメソッド名で異なる処理ができる
また、このように同じ名前で異なる処理を行うことをポリモーフィズムと言います。
オブジェクト指向の重要な概念ですのでしっかり覚えてください。
ポリモーフィズムときたら、同じ名前で異なる処理を行うです。
[2] 静的変数と静的メソッド
C言語でも静的変数はありました。staticを使った変数です。
Javaも同様にstaticを使います。JavaとC言語は似ているようで違います。
紛らわしいですが、しっかり覚えてください。
静的変数と静的メソッドの記述方法
記述方法をここで説明しておきますが
アクセス修飾子 static データ型 変数名;
アクセス修飾子 static 戻り値 メソッド名(引数)
変数の場合には、アクセス修飾子とデータ型の間
メソッドの場合には、アクセス修飾子と戻り値の間
に書きます。それ以外はこれまでのメンバ変数とメソッドとの違いはありません。
staticがあるとないでの違いを見ていきましょう。
staticのあるとないの違いを確認するプログラム
public class Java08_04{
public static void main(String args[]){
//staticがないクラスの場合
NoStaticClass nsc = new NoStaticClass();
nsc.a = 20;
nsc.print();
//staticがあるクラスの場合
StaticClass.a = 10;
StaticClass.print();
}
}
class StaticClass{
static int a;
public static void print(){
System.out.println("printメソッド実行:" + a);
}
}
class NoStaticClass{
int a;
public void print(){
System.out.println("printメソッド実行:" + a);
}
}
実行結果
今までのメンバ変数とメンバメソッドは次のように使いました。
Test test = new Test(); // インスタンス化
test.a = 10; // インスタンス名.変数名
test.method1(); // インスタンス名.メソッド名
必ず、インスタンス化を行ってから、変数の参照、メソッドの呼び出しを行っていました。
ところが、静的変数と静的メソッドは次のように使います。
Test.b = 10; // クラス名.変数名
Test.method2(); // クラス名.メソッド名
上の2つの手順を見比べれば何がないのかはすぐに分かります。
staticがある場合にはインスタンス化をしなくても使える。
そして、クラス名.〜という形式になります。
これはstaticの効果の1つ目になります。
staticの効果その2
staticがある場合とない場合での違いがほかにもあります。それは、
そのクラスで唯一の変数、あるいはメソッドを作成するとき
に使用します。これを活用すると、メソッドでC言語風の関数が作成できます。
staticのサンプルプログラム2
public class Java08_05 {
// 静的メソッド
private static void func() {
System.out.println("C言語風の関数です");
}
public static void main(String args[]) {
func();
}
}
実行結果
メインメソッドで静的メソッドを呼び出しています。
インスタンス化が不要なので、呼び出し方法はまさに関数と言ってよいでしょう。
ただし、何度も説明しているようにメソッドはメソッドですので、C言語の関数と混合しないようにしてください。
また、これまでmainメソッドに「static」をつけていましたが、これも同じ意味です。
インスタンス化なしで呼び出せるようにするためです。
ただし、mainメソッドは通常のメソッドと異なり、形は常に固定で
public static void main(String args[])
にしてください。次にstaticの効果3つ目について説明します。
次のプログラムの実行結果を予想してみてください。
staticの効果その3プログラム
class Sample {
private int a;
private static int b;
public Sample(int aa, int bb) {
a = aa;
b = bb;
}
public void put() {
System.out.println("a = " + a + " b = " + b);
}
}
public class Java08_06 {
public static void main(String args[]) {
Sample test1 = new Sample(1, 2);
Sample test2 = new Sample(3, 4);
test1.put();
test2.put();
}
}
実行結果
このようになりました。実行結果の予想は合っていましたか。
2つオブジェクトを生成しました。test1とtest2です。
そして、それぞれの値に別に値を入れたわけですから
test1.put()でa =1, b = 2
test2.put()で a = 3, b = 4
となりそうですが、実行結果を見てわかるようにそのようにはなっていません。
staticがついていると複数インスタンス化しても共通なものと認識する
インスタンス化を行ったことで全ての変数が4つあるように思えますが
実際には「static」はいくつ作っても1つしかないので事実上全部で3つとなります。
そして、「static」のある変数はこのプログラムでは後に代入されたほうが表示されるようになります。
つまりtest2が後に実行されるために、メンバ変数bは4となります。
インスタンス化の必要がない理由とは
では、なぜインスタンス化する必要がないのでしょうか。
静的変数、静的メソッドには別に言い方があります。
クラス変数(静的変数)、クラスメソッド(静的メソッド)という呼び方です。
実は、この「static」がつけられた変数とメソッドはクラスが持っています。
そのために、クラス変数、クラスメソッドという呼び方がされるのです。
このため、「static」で宣言した変数とメソッドはクラスに1つだけ存在することになり
インスタンス化を行わなくても利用することが可能になるわけです。
[3] thisキーワード
「this」の説明をします。
これを用いることで、プログラムを書く上でソースの簡単化が期待できます。
いくつか使い道があるので、活用できるようにしてください。
thisとは何か
「this」とは自身のオブジェクトを示します。
例えば、オブジェクトAの中でthisを用いた場合、「this」は「A」を示します。
つまり、オブジェクト自身で自分を指すということです。
説明を聞いただけでは、少しわかりづらいと思います。
最初に私も説明を聞いた時は意味が分かりませんでした。
これに関しては、実際の使い方を見て学んでください。
以降の項目で「this」の使い方を説明します。
そのとき上記のことを何となく思い浮かべつつ、使い方を覚えてください。
thisの使い道1 メンバ変数とローカル変数
まず、メンバ変数とローカル変数について説明します。
メンバ変数はクラス内で保持される変数です。
ローカル変数とは、一時的な変数です。
ローカル変数はメソッドなどの中括弧{}の中のみで使用されます。
処理が中括弧から外に出ると変数は消えてなくなります。これがローカル変数です。
これらはC言語にもあるので、理解しやすいと思います。
制御文のfor文で、for(int i = 0; i < 5; i++)のような書き方をしました。
これも同じように{}内のみで扱われる変数なのでローカル変数です。
この{}を出た先で表示しようとするとエラーになりました。
ちなみに、引数で渡された変数もローカル変数です。
重要なことは、メンバ変数とローカル変数に同じ名前を使用できるということです。
それでは、次のソースを実行して確認してみましょう。
thisを使ったプログラム1
class ThisTest {
// メンバ変数「value」の宣言
private int value = 20;
// メソッド
public void func() {
// ローカル変数「value」の宣言
int value= 10;
System.out.println("value="+value);
}
}
public class Java08_07{
public static void main(String args[]){
ThisTest test1 = new ThisTest();
test1.func();
}
}
実行結果
例に挙げたソースでは「value」という共通の名前で宣言しました。
classの下のprivate int valueと、func()メソッド内の「value」は同じ名前です。
この場合、func()メソッド内では「value」をローカル変数として自動的に処理されます。
また、メンバ変数よりもローカル変数の優先順位が高いので、
メンバ変数のvalueとfunc()メソッドのローカルvalueをvalueで表示したときには、
ローカル変数のvalueが表示されます。
C言語でローカル変数とグローバル変数のことを思い出してください。
同じように有効範囲(スコープ)がありました。あれと同じことです。
では、メンバ変数のvalueをfuncメソッド内で使いたい場合はどうすればよいでしょう?
先ほどのプログラムを次のように変更します。
thisを使ったプログラム1
class ThisTest {
// メンバ変数「value」の宣言
private int value = 20;
// メソッド
public void func() {
// ローカル変数「value」の宣言
int value= 10;
System.out.println("ローカル変数のvalue=" + value);
System.out.println("メンバ変数のvalue=" + this.value);
}
}
public class Java08_07{
public static void main(String args[]){
ThisTest test1 = new ThisTest();
test1.func();
}
}
実行結果
ローカル変数を使いたいときは、そのまま「value」
メンバ変数を使いたいときは、「this.value」とします。
「this」はそのクラス自身を示します。
「this.value」は「そのクラスのメンバ変数value」という意味になります。
これで、メンバ変数とローカル変数を識別できました。
ところで、「メンバ変数とローカル変数に違う名前をつければよいのでは?」と思う方がいると思います。
確かにそれでも識別はできますが、ソースによっては、同じ名前の方が見やすい場合があります。
thisの使い道2 コンストラクタ
最初で少し触れましたが、コンストラクタもオーバーロードが可能です。
コンストラクタは初期化を行うものですが、初期化の方法は場合によって異なります。
そのため、複数のコンストラクタを用意する必要があります。
次のソースを見てください。
thisを使ったプログラム2
class ConstructorTest {
private int value1;
private int value2;
// コンストラクタ1(自動で値を初期化する場合)
public ConstructorTest() {
value1 = 10;
value2 = 20;
}
// コンストラクタ2(value1のみ任意の値で初期化する場合)
public ConstructorTest(int value1) {
this.value1 = value1;
value2 = 20;
}
// コンストラクタ3(value1、value2共に任意の値で初期化する場合)
public ConstructorTest(int value1, int value2) {
this.value1 = value1;
this.value2 = value2;
}
public void print() {
System.out.println(this.value1);
System.out.println(this.value2);
}
}
public class Java08_08{
public static void main(String args[]){
ConstructorTest test1 = new ConstructorTest();
ConstructorTest test2 = new ConstructorTest(50);
ConstructorTest test3 = new ConstructorTest(100, 200);
test1.print();
test2.print();
test3.print();
}
}
実行結果
コンストラクタによって、メンバ変数value1とvalue2を初期化する方法を決めています。
初期化方法は複数考えられるため、オーバーロードにより、複数個のコンストラクタを定義しています。
特に指定しなければ、value1は10、value2は20で初期化されます。
注目すべき点はコンストラクタごとに値を代入するという共通の処理を書いていることです。
同じ処理なのに、すべてに記述することは面倒です。そして、書き間違えによるミスにもなります。
そこで、1つのコンストラクタに共通の処理を書き、他のコンストラクタはそのコンストラクタを呼び出すという方法を行います。
コンストラクタを呼び出すには、
this(); // ()内は呼び出したいコンストラクタと同等の引数を与える
と書きます。決してコンストラクタ名();ではないことに注意してください。
では、上記のテストクラスを書きなおします。
thisを使ったプログラム2
class ConstructorTest {
private int value1;
private int value2;
// コンストラクタ1(自動で値を初期化する場合)
public ConstructorTest() {
this(10, 20); // コンストラクタ3を呼び出す
}
// コンストラクタ2(value1のみ任意の値で初期化する場合)
public ConstructorTest(int value1) {
this(value1, 20); // コンストラクタ3を呼び出す
}
// コンストラクタ3value1、value2共に任意の値で初期化する場合
public ConstructorTest(int value1, int value2) {
this.value1 = value1;
this.value2 = value2;
}
}
public class Java08_08{
public static void main(String args[]){
ConstructorTest test1 = new ConstructorTest();
ConstructorTest test2 = new ConstructorTest(50);
ConstructorTest test3 = new ConstructorTest(100, 200);
test1.print();
test2.print();
test3.print();
}
}
コンストラクタ3に共通処理を書き、コンストラクタ1と2がコンストラクタ3を呼び出しています。
これで、ソースがすっきりしたと思います。
もし、コンストラクタが4個、5個とたくさんあったら、それだけこの書き方のありがたみがわかると思います。
なお、コンストラクタはインスタンス化したとき初めに処理されるという決まりがありました。
そのため、コンストラクタの呼び出しはコンストラクタの一番初めに行う必要があります。
つまり、上記のプログラムのコンストラクタ1を次のように書いてはいけません。
// コンストラクタ1(自動で値を初期化する場合)
public Test() {
System.out.println("エラー"); // コンストラクタの呼び出しより先に処理する
this(10, 20); // コンストラクタ3を呼び出す
}
[4] まとめ
新しい用語がたくさん出てきたのであまり理解できていない部分もあると思います。
そこで、このページで学んだことをここで簡単におさらいをしていきましょう。
オーバーロードのまとめ
オーバーロードとは、全く同じ名前を持つメソッド、コンストラクタを複数定義すること。
同じ名前を複数定義できる理由は、引数の個数もしくは引数の型が異なるから。
同じ処理を引数の違いで区別することにより分かりやすくすることが目的。
1つ捕捉になりますが、メソッド名と引数の個数と型を合わせてメソッドの「シグネチャ」と呼びます。
静的変数と静的メソッドのまとめ
静的変数、静的メソッドにするには、staticをつける。
staticがついている変数とメソッドはインスタンス化なしに利用できる。
別名として、クラス変数、クラスメソッドと呼ばれる。
クラス変数は全てのオブジェクトの共通の変数である。
thisキーワードのまとめ
ローカル変数とメンバ変数を区別するときに使う。
コンストラクタをコンストラクタで呼び出すときに扱う。
ローカル変数とメンバ変数のまとめ
同じ変数名を付けた時にメンバ変数よりもローカル変数が優先される。
メンバ変数はクラスの変数、ローカル変数はメソッド内の変数である。