Javaによるオブジェクト指向講座 第2回
< 第1回
今回は前回ないがしろにしていたクラスとメソッドについて書いていきます。
C言語をやったことのある方からしたら、クラスは構造体、メソッドは関数にそれぞれ対応しているものと捉えてください。
第2回 クラス
オブジェクト指向とは何か
そもそもオブジェクト指向って何なのか、ということを説明します。
レストランへ行き、お金を払って料理を提供してもらうことを想定します。
この流れをプログラミングしようと思うと、どこから手を付けますか。
食べたい料理を記録しておいてお金をレジに移し、対応する料理を作る命令が呼ばれ、お客さんの所持品にその料理がプラスされるという感じにでもなるでしょう。
いっぺんに処理すべてを考えると、なんだかややこしくないでしょうか。
このように、この処理の流れで登場するすべてのモノ(オブジェクト)を洗い出し、それぞれのオブジェクトの役割を考えてそれぞれ実装してやれば、全体としては複雑な処理であっても一個一個はシンプルになることがわかると思います。
このようにオブジェクトを主体として考え、それらのメッセージのやりとりでプログラムを構成する考え方のことをオブジェクト指向と言います。
[お客さん]
・お金を持っている
・接客に料理を伝え、お金を渡す
・接客から料理をもらう
[接客]
・お客さんからのお金をレジに格納する
・お客さんの依頼のあった料理を料理人に伝える
・料理人からもらった料理をお客さんに提供する
[料理人]
・接客から依頼された料理を鉄板で作る
・できた料理を接客に渡す
それぞれのオブジェクトの役割を一個一個実装していって、最後にくっつければ、全体の流れとしてこのレストランが完成します。
こうすれば、作る内容が必然的に分離されるので、混乱しづらくなります。バグってもどこが悪いのが見つけやすくなります。
もっとほかにも利点がありまして、お客さんにとっては料理人がどうやって料理を作るかなんて知らなくていいですよね。そのため、お客さんが料理人とコンタクトをとること自体を禁止してしまうことが出来ます。こうすればお客さんが料理人と直接会ってるような実装が原理的に行えなくなるので、バグりづらくなるのです。
このように処理の中身をブラックボックスにしてしまう仕組みを「カプセル化」といいます。処理やデータのカプセル化を行うことで、各々が出来ることを整理できて色々嬉しいのです。
もう、いいことざんまいですね>< みなさんもぜひオブジェクト指向を使っていきましょう。
オブジェクト指向が持っているとされる3大要素が挙げられており、次のようなものとなっています。
「カプセル化」「継承」「ポリモーフィズム」
厳密にはこれらを持ち合わせていないオブジェクト指向言語もあるのですが、今回はJavaなのでこれらはすべて持っています。どれもオブジェクト指向らしい仕組みです。
カプセル化については冒頭で述べましたね。今回はクラスの解説とカプセル化までやってみます。
クラスとオブジェクト
オブジェクトの設計図になる概念を「クラス」といいます。流れとしてはまず「クラス」を定義して、オブジェクトを使いたい側がそのクラスをもとにオブジェクトを「生成」するということになります。
ここが注意です。Javaではオブジェクトを直接定義するのではなく、その設計図であるクラスを定義します。似たようなオブジェクトを同じクラスから量産できるというのが、そうなってる理由の一つだと思います。
クラスをもとにオブジェクトを生成することを「インスタンス化」と呼び、作られたオブジェクトのことを「インスタンス」と呼びます。用語が多いですね;
実際、インスタンス化しないとオブジェクトが出来ないことから、オブジェクト=インスタンスと紹介されているところもあります。
では単純な例として硬貨クラスを作ってみましょう。
// 【Coin.java】
class Coin{
public int value; // 価値
}
// 【Main.java】
class Main{
public static void main(String args[]){
Coin coin1 = new Coin();
Coin coin2 = new Coin();
coin1.value = 100;
coin2.value = 500;
System.out.println(coin1.value + "円玉");
System.out.println(coin2.value + "円玉");
}
}
Javaでは一つのファイルにつき一つのクラスしか定義できません。(たぶん^^;)
ここでは新たにCoinクラスを作るため、Coin.javaというファイルを作ってそこに定義しました。class Coinで、Coinクラスの定義になります。Coinクラスは硬貨の設計図になります。
Coinクラスの中に「public int value;」とありますね。publicは後述するので、とりあえず無視します。「int value」で、int型変数valueを宣言するという意味なのは通常の変数と同じです。当然ながら硬貨によって値段が違うので(100円硬貨とか500円硬貨とか)、その部分がvalueという変数の値になっているということです。この記述で、Coinクラスから作られたオブジェクトはvalue(価値)を持っている意味になります。
次にmainメソッドの中を見てみます。初めの2行が、Coinクラスを使ってまさにCoinオブジェクトを作っている部分です。
Coin coin1 = new Coin();
Coin coin1; とすると、Coin型のcoin1変数を宣言するという意味になります。intとかdoubleと同じようなもので、クラスは型に相当します。new Coin()というのが、「Coinクラスをもとにして作ったCoinオブジェクト」という意味です。「クラス名()」の頭にnewと付けてやると、クラスからオブジェクトを作る処理をします。
要するに以上の操作で、Coin型の変数coin1を宣言し、そこにCoinオブジェクトを入れたことになります。
作った硬貨がcoin1として使えるようになりました。そして、次にその価値を決めています。
coin1.value = 100;
このvalueとは、さっきCoinクラスで定義した変数valueです。coin1の持つvalueに100を代入しています。このように、オブジェクトの持つデータにアクセスするときは「.」で繋げます。これでcoin1は100円硬貨になりました。
そのcoin1.valueの値を最後に表示しているということですね。coin2の方は500円硬貨になっています。
メソッド
ここではCoinに変数valueを持たせただけですが、処理を持たせることもできます。さっきの例ではmainメソッドの中で「~~円玉」など表示させていましたが、この表示を硬貨自身が行ってくれるようにしてみましょう。すなわち、Coinクラスの中に価値を表示する処理を加えてみます。
処理のことをメソッドと呼びます。C言語でいう関数とまったく同じです。
// 【Coin.java】
class Coin{
public int value; // 価値
public void printValue(){
System.out.println(value + "円玉");
}
}
// 【Main.java】
class Main{
public static void main(String args[]){
Coin coin1 = new Coin();
Coin coin2 = new Coin();
coin1.value = 100;
coin2.value = 500;
coin1.printValue();
coin2.printValue();
}
}
Coinの中に新たにprintValueという名前のメソッドを追加しました。voidとかの説明は後述しますが、処理の中身は「~~円玉」の表示となっています。表示する値が「value」になっていますね。「value」は自分自身が持っているデータなので、このように書けば自分のデータを使って表示します。
Mainの方では変数のときと似た感じで、「coin1.printValue();」などと書けばcoin1のprintValueメソッドを実行するという意味になります。
coin1.printValue() であれば coin1.value の値を表示しますし、
coin2.printValue() であれば coin2.value の値を表示します。
メソッドにはオプションを付けた形で処理を実行させるということも可能です。このオプションのことを「引数」と呼びます。
// 【Coin.java】
class Coin{
public int value; // 価値
public void printValue(int n){
System.out.println(value + "円玉");
System.out.println("これが" + n + "枚あれば、" + (value * n) + "円になるね");
}
}
// 【Main.java】
class Main{
public static void main(String args[]){
Coin coin1 = new Coin();
Coin coin2 = new Coin();
coin1.value = 100;
coin2.value = 500;
coin1.printValue(3);
coin2.printValue(8);
}
}
上のように書き換えましょう。さっき空だったprintValueのかっこの中にint nを入れています。実行する際に何か整数を入れてやれば、それを使ってメソッドを実行することになります。
mainの方ではprintValueのかっこ内に3とか8を入れていますね。これが引数です。
coin1.printValue(3); と指定すれば、coin1のprintValueメソッドをn=3で実行することになり、「これが3枚あれば、300円になるね」と表示されます。
引数はメソッド実行時の入力データですが、出力データを指定することもできます。これを「戻り値」といいます。
printValueで戻り値を設定するのはややこしいので、新たに掛け算メソッドkakezanを追加しましょう。
// 【Coin.java】
class Coin{
public int value; // 価値
public void printValue(int n){
System.out.println(value + "円玉");
System.out.println("これが" + n + "枚あれば、" + kakezan(value, n) + "円になるね");
}
public int kakezan(int x, int y){
return x * y;
}
}
Mainは変えていません。
kakezanメソッドでは2つの引数x, yを指定しています。戻り値がある場合、メソッド名の先頭のvoidを戻り値の型名に変えます。(voidは空という意味)整数の掛け算なので、整数を返したいため、int型としています。そして、xとyを掛け算した結果をreturnしています。returnなんとかで、その値を返します。int a = kakezan(3, 5) とかすれば、3×5=15がaに代入されるということです。
なんだか関数みたいですね。そのため、C言語などでは関数と呼ばれています。Javaではメソッドと呼ぶのが普通だと思いますが……
コンストラクタ
100円硬貨とか500円硬貨とか、価値が初めから決まっているなら、後で価値を代入するのではなく、初めから初期値として与えてやりたいですよね。
そのような場合、オブジェクトが生成される瞬間に行われる処理を作れば便利です。
生成時に自動で呼ばれる初期化用のメソッドのことを、コンストラクタと呼びます。
// 【Coin.java】
class Coin{
public int value; // 価値
public Coin(int n){
value = n;
}
public void printValue(){
System.out.println(value + "円玉");
}
}
// 【Main.java】
class Main{
public static void main(String args[]){
Coin coin1 = new Coin(100);
Coin coin2 = new Coin(500);
coin1.printValue();
coin2.printValue();
}
}
Coinクラスの中に、Coinメソッドというクラスと同名のメソッドを追加しました。このように、クラスのコンストラクタは、そのクラスと同じ名前のメソッドと決まっています。
コンストラクタに戻り値は無いので、先頭のvoidはありません。ただし初期化用オプションとして引数を与えることはできます。ここでは引数nを与えると、それを自分のvalueに代入しています。
mainメソッドの方でコンストラクタに引数を与えるときは、オブジェクト生成時のnew Coinの後のかっこの中に指定します。これで、mainメソッドの方でわざわざvalueに値を代入しなくてもよくなりました。
this
Coinのコンストラクタは次のように記述していました。
public Coin(int n){
value = n;
}
引数nの値を自分の持っているvalueに入れるという意味でしたね。
ここの引数と、自分が持っている変数って、大体同じ意味になりますから、だんだん名前をこう区別するのが煩わしくなってきます。
だからといって、
public Coin(int value){
value = value;
}
なんて書くと、valueにvalueを代入する、???と、意味のわからない文になります。
引数のvalueと、自分が持っているvalueを区別して、同じ名前の変数を使いたい、というときは、次のように書けばOKです。
public Coin(int value){
this.value = value;
}
自分が持っているvalueの方を、「this.value」と書くのです。「この」valueです。
クラスからオブジェクトが生成されるので、外部からこのクラスのメソッドを呼び出すときは、例えば変数の名前を使ってcoin1.valueなどと書いていたわけですが、それと同じように、クラス内で自分自身のデータにアクセスするときはthis.valueなどと書きます。thisは自分自身を表しています。
自分がもともと持っている変数・メソッドなのか、そうでないのか区別するために、そういうものにはわざわざすべてにthisを付けるのも技法として用いられます。
アクセス権限
ここまで作ったことによりCoinクラスがそれっぽく完成したわけですが、せっかくなのでカプセル化を実現してみましょう。すなわち、mainメソッドの方は硬貨の価値を勝手に変えられないようにするのです。
現状だと、こんなことが出来てしまいます。
// 【Coin.java】
class Coin{
public int value; // 価値
public Coin(int value){
this.value = value;
}
public void printValue(){
System.out.println(value + "円玉");
}
}
// 【Main.java】
class Main{
public static void main(String args[[]]){
Coin coin1 = new Coin(100);
Coin coin2 = new Coin(500);
coin1.value = 123;
coin1.printValue();
coin2.printValue();
}
}
100円玉を作っておきながら、後で価値を123円に変えていますね。これは偽造です><
こんなことは禁止するべきですから、外部から変数の値を変えられないようにします。
Coinクラスで宣言されていた
public int value;
を、
private int value;
に変えてみます。publicがprivateになりました。すると、コンパイル時にアクセス禁止ということでエラーが発生するようになります。
publicとは「これは外部クラスでも好きに読み書きできるよ!」という意味だったのです。たしかに今までは変数の値を好きにいじったり、メソッドを好きに呼んだり出来ていました。
対してprivateは「自分自身しか読み書きできない!」というものになります。private int valueと宣言すると、このvalueはCoinクラス自身でしか読み書きできなくなります。書き込むことだけでなく、読むことも出来なくなりますから、mainメソッドの方で「System.out.println(coin1.value);」とか書いてもエラーになります。外部からcoin1.valueと書くことすらできなくなってしまいます。
不便に思われるかもしれませんが、これにより偽造を防止できるわけです。
硬貨の価値を表す変数valueは直接読めないけど、printValueメソッドがあるおかげで間接的に何円玉なのか知ることは出来ますからね。これで安泰です。このように、変数の値を間接的に読み書きするメソッドのことを「アクセッサ」と呼びます。
注意:参照型
Javaではnewして作ったオブジェクトは、変数に参照型として保持されます。変数は箱なので、変数にオブジェクトを代入したらなんだか変数の中にオブジェクトのすべてが入っているように思ってしまうのですが、実は違うのです。
実際、単なる数値などはそのまま入っているのですが、オブジェクトは箱には入りません。オブジェクトはおおむね大きいので、変数の箱が巨大になりがちなのですよね。
実際には↓このように格納されています。
オブジェクトの実体はどこか遠い所に置き去りにし、変数の中にはそのオブジェクトの居場所の情報(アドレス)だけを入れておくのです。こうすれば変数は軽量で済みますよね。このように、直接データを格納せずにそのデータのアドレスだけを入れておくものを参照型データと言います。Javaのオブジェクトはすべて参照型です。(C++などは使い分けられます)
そのため、気を付けないとこういうことが起こります。
↑これはベイ助を2体作ってそれぞれに番号を振っているのですが、そうではなくて↓このように記述するとベイ助は1体だけになります。
baysuke2の方にbaysuke1を代入していますが、これはベイ助1をコピーして新たに作ったベイ助をbaysuke2にしているのではなく、baysuke1が指していたベイ助のアドレスをそのままbaysuke2に与えただけなので、baysuke2もベイ助1を指すようになっただけになります。ですから、もともとベイ助1だったものに対してbaysuke2.number = 2としてしまったことにより、ベイ助1がベイ助2に変わってしまうのです。
参照型だと知っていれば問題ないと思いますが、変数の中にデータが直接入っていると思ってしまうと、いざというとき間違ってしまいます。よく注意しましょう。
次回はオブジェクト指向で使える面白い仕組みである継承とポリモーフィズムについて解説して終了します。
> 第3回