初心者入門レベルの【Javaの勉強】Javaのオブジェクト指向(クラスとインスタンス)
2020/04/02
「クラス」を使ったプログラム
Javaにはオブジェクト指向が取り入れられているので、ここでオブジェクト指向の基本の「クラス」について勉強してみましょう。
Javaではオブジェクト指向の「クラス」がプログラムの基本単位となっていますが、「クラス」というのは簡単に言うと「データ(属性)」と「手続き(振る舞い)」を1つにまとめたものです。
Javaのプログラムでは「データ」と「手続き」はすべて「クラス」に属している必要があるので、プログラムはあらかじめ用意された「クラス」や自分で作った「クラス」を組み合わることで作成することになります。
このプログラムを実行すると、「クラス」を雛形にして生成された「オブジェクト(Javaでは「インスタンス」と呼ぶ)」が、互いにメッセージをやり取りしながら処理を進めていくのです。
それではここで前回作った幸運度プログラムを例にして、実際に「クラス」を使ったプログラムを作ってみることにします。
public class Fortune06 {
public static void main(String[] args) {
double rnd = Math.random(); //乱数の生成
int fortune = (int)(rnd * 10) + 1; //1から10までの整数に変換
System.out.println("10% 50% 100%");
System.out.println("┏━━━━━━━━┓");
for (int i = 0; i < fortune; i++) {
System.out.print("■");
}
System.out.print("\n今日の幸運度は");
System.out.print(fortune);
System.out.print("0%です!");
}
}
これが前回作った幸運度プログラムですが、その処理の流れは以下のようになっています。
(1) randomメソッドで、0以上1未満の乱数を生成する。
(2) 生成した乱数に10をかけてからint型に変換して、1を加えて1から10までの整数にする。
(3) (2)で得た整数の回数だけ“■”を表示する。
(4) 最後に幸運度を数値で表示する。
まずはこの(1)から(2)の処理と、(3)から(4)の処理の2つの「クラス」に分けて作り変えてみましょう。
最初に後半部分を「クラス」化したのが、以下のプログラムです。
class FortuneClass01 {
int fortune;
void result() {
System.out.println("10% 50% 100%");
System.out.println("┏━━━━━━━━┓");
for (int i = 0; i < fortune; i++) {
System.out.print("■");
}
System.out.print("\n今日の幸運度は");
System.out.print(fortune);
System.out.print("0%です!");
}
}
まず1行目ですが、“FortuneClass01”という名前の「クラス」を作ることの宣言をしています。
元の幸運度プログラムと比べると先頭の修飾子の“public”が無くなっていますが、「クラス」の宣言で修飾子を省略した場合は、同じ「パッケージ」内からだけアクセスできる「クラス」になります。
“public”を付けると、外部の「パッケージ」からもアクセスできる「クラス」になりますが、ここでは「パッケージ」というのはWindowsのフォルダと同じだと考えてください。
つまり新しく作った“FortuneClass01”の「クラス」を利用するプログラムは、必ず“FortuneClass01”と同じフォルダ内に置かなければならないということになります。
続いて変数“fortune”ですが、元のプログラムでは“main”メソッドの中で宣言されていましたが、今回は“result”メソッドの外で宣言されています。
このようにメソッドの外で宣言した変数は、他のクラスから参照することができますが、これを「フィールド」と呼びます。
「フィールド」には、インスタンスを生成しなければ利用できない「インスタンス変数」と、生成せずに利用できる「クラス変数」の2種類があります。
今回の変数“fortune”のように、型の前に何も付けずに宣言した場合は「インスタンス変数」になります。
最後に、“result”メソッドの宣言を見てみましょう。
メソッドの基本的な書式は次のようになります。
修飾子 戻り値の型 メソッド名 (引数1, 引数2, ・・・) {
処理
return 戻り値;
}
元のプログラムの“main”メソッドでは修飾子に“public”と“ static”が付いていますが、“public”修飾子を付けたメソッドは外部のパッケージから利用できるメソッドになります。
今回は“result”メソッドのように、修飾子を省略した場合は同じパッケージの中からだけの利用に制限されるというのは、「フィールド」の場合と同様です。
さらに「メソッド」にも、インスタンスを生成しなければ利用できない「インスタンスメソッド」と、生成せずに利用できる「クラスメソッド」の2種類があります。
“main”メソッドのように“static”修飾子を付けると「クラスメソッド」になり、“result”メソッドのように付けないと「インスタンスメソッド」になります。
修飾子の次に記述する戻り値の型というのは、メソッドが返す値のデータ型を宣言しています。
例えば処理した結果の戻り値を「int型」で返す場合は「int型」を指定しますが、今回の“result”メソッドは何も返さないので、戻り値が無いことをしめす「void型」で宣言しています。
戻り値の型に続いて記述される「メソッド名」は、「変数名」や「クラス名」と同じ識別子なので、識別子としての規約に従って付けなければなりません。
「メソッド名」の次のカッコ内にはメソッドが受け取るパラメータの宣言をしますが、今回のようにパラメータを受け取らない場合は何も書かずにカッコだけを記述します。
ちなみに、これら「フィールド」と「メソッド」は、まとめて「クラス」の「メンバー」と呼ぶこともあります。
さてそれでは、この“FortuneClass01”を実行してみますが、Javaのプログラムは“main”メソッドから起動するので、“FortuneClass01”だけでは実行することができません。
そこで“FortuneClass01”を呼び出して実行するために、以下のプログラムを作ってみましょう。
public class FortuneMain01 {
public static void main(String[] args) {
double rnd = Math.random();
FortuneClass01 fortuneClass = new FortuneClass01();
fortuneClass.fortune = (int)(rnd * 10) + 1;
fortuneClass.result();
}
}
ここで特に注目すべきは、以下の行です。
FortuneClass01 fortuneClass = new FortuneClass01();
これは、次のような構文になっています。
クラス名 変数名 = new クラス名();
これを実行すると、「new演算子」によって「クラス名」で指定した「クラス」を雛形にして「インスタンス」を生成し、それが「変数」に代入されます。
今回は、「クラス名」が“FortuneClass01”の「クラス」から「インスタンス」を生成して、“fortuneClass”という「変数」に代入することになります。
メモリ上でのプログラムの動き
それでは、このように「クラス」から「インスタンス」を生成して実行するというプログラムが、コンピュータのメモリ上ではどのように動いているのかを考えてみましょう。
Javaのクラスには、「クラスファイル」とメモリ上の「クラス」の2種類があります。
ソースコードをコンパイルしてバイトコードに変換したものが「クラスファイル」で、その実体はハードディスク上に存在します。
プログラムを実行すると、この「クラスファイル」がメモリ上に読み込まれて展開されますが、これがメモリ上の「クラス」です。
「new演算子」によって「クラス」から「インスタンス」が生成されると、メモリ上の「クラス」からメンバーごとに別の領域にコピーされますが、このコピーされたものが「インスタンス」というわけです。
ここで「クラス」はメモリ上に1つだけしか存在できませんが、そこからコピーされる「インスタンス」は複数生成することができるという点も重要なポイントです。
そしてメモリ上に生成された「インスタンス」にアクセスするために、それを一意に識別するための値(「参照値」と呼ぶ)を変数に格納し、「インスタンス」を使うときはこの変数を使って呼び出します。
「参照値」を格納する変数の型は「クラス名」に一致させますが、上記の1行でこれらの処理がメモリ上で動いているわけです。
ちなみにこの1行は、以下のように2行に分けて書くこともできます。
FortuneClass01 fortuneClass;
fortuneClass = new FortuneClass01();
次は以下の行ですが、ここでは「インスタンス変数」の“fortune”にアクセスしています。
fortuneClass.fortune = (int)(rnd * 10) + 1;
“FortuneClass01”クラスには「インスタンス変数」の“fortune”がありますから、そこからコピーした「インスタンス」の“fortuneClass”にも「インスタンス変数」の“fortune”があります。
ここで“fortuneClass.fortune”という記述でそれにアクセスして、1から10までの乱数を代入しているわけです。
そして以下の行で、「インスタンスメソッド」の“result”を実行しています。
fortuneClass.result();
この「インスタンス」の「メソッド」の呼び出しが、冒頭の「オブジェクトが互いにメッセージをやり取りしながら処理を進めていく」という流れに該当します。
さて、それでは実際にプログラムを実行してみましょう。
“FortuneMain01.java”と“FortuneClass01.java”の2つのファイルをコンパイルして、生成された“FortuneMain01.class”のほうを実行してみましょう。
すると“FortuneClass01”の「クラス」が、“FortuneClass01”の「クラス」の「インスタンス」を生成して実行することが確認できます。
>javac FortuneMain01.java
>javac FortuneClass01.java
>java FortuneMain01
10% 50% 100%
┏━━━━━━━━┓
■■
今日の幸運度は20%です!
>
「メソッド」が値を受け取ったり返したりする場合
次に、このプログラムでは「インスタンス変数」を使って乱数を受け取っていますが、それを「メソッド」のパラメータを使って受け取るように変更してみましょう。
以下のプログラムが、変更した結果になります。
class FortuneClass02 {
void result(int fortune) {
System.out.println("10% 50% 100%");
System.out.println("┏━━━━━━━━┓");
for (int i = 0; i < fortune; i++) {
System.out.print("■");
}
System.out.print("\n今日の幸運度は");
System.out.print(fortune);
System.out.print("0%です!");
}
}
元のプログラムで「インスタンス変数」を宣言した部分が無くなって、その代わりに「メソッド」の宣言のカッコ内で変数を宣言するようになっています。
この変数が、「メソッド」のパラメータというわけです。
そして、これに合わせて呼び出し側も変更したのが、次のプログラムです。
public class FortuneMain02 {
public static void main(String[] args) {
double rnd = Math.random();
int ftn = (int)(rnd * 10) + 1;
FortuneClass02 fortuneClass = new FortuneClass02();
fortuneClass.result(ftn);
}
}
ここで変数“rnd”と同様に、この「クラス」の中でだけ使える変数“ftn”を宣言して乱数を代入し、それを「メソッド」のパラメータとして“result”に渡しています。
さらにこのプログラムで、乱数を発生させている部分も「クラス」として切り出すと、以下のようなプログラムになります。
class FortuneClass03 {
public int rndm() {
double rnd = Math.random();
int ftn = (int)(rnd * 10) + 1;
return ftn;
}
}
ここでは、“rndm”という「メソッド」の宣言の先頭に「int型」の値を返すことを示す“int”が記述されている点に注目してください。
このプログラムでの「クラス」の「メソッド」は、これまでとは違って呼び出し元に値を返すようになっているためです。
「メソッド」内の最後の“return”の行が、変数“ftn”の値を返すための記述になります。
そしてこの値を返す「クラス」を使うように、呼び出し側を変更したのが、次のプログラムになります。
public class FortuneMain03 {
public static void main(String[] args) {
FortuneClass03 rnd = new FortuneClass03();
int ftn = rnd.rndm();
FortuneClass02 fortuneClass = new FortuneClass02();
fortuneClass.result(ftn);
}
}
まとめ
今回はJavaのオブジェクト指向を理解するために、「クラス」を使ったプログラムの作成方法やその使い方について簡単に勉強してみました。
ここで注意しなければならないのは、Javaのオブジェクト指向と一般的なオブジェクト指向とは少し意味が異なる部分があるということです。
両方を勉強すると混乱してしまうかも知れませんが、Javaというプログラミング言語はオブジェクト指向という考え方を便利に使えるように取り入れた一例だと思うと良いでしょうね。
そのうえでJavaのオブジェクト指向を勉強していけば、一般的なオブジェクト指向という考え方への理解も深まると思いますよ。
それでは、今回はここまでです。
《 【Javaの勉強】一覧 》