プログラミングというのは、カンがいいと、言われたとおりに入力しているだけである程度のことができるようになります。しかし、そういうやり方では「ここはおまじない」「ここは決まり文句」という部分がどうしても多くなってしまいます。
ちょっと訳あって、秋葉原のメイドさんにプログラミングを教えています。そのメイドさんたちはまさにそうで、彼女たちは既にAndroid Studioで画面レイアウトをいじり、画面遷移をするプログラムを書く能力はありますが、「おまじない」「決まり文句」の多さに困惑しているように思います。
そこで、今日から「メイドさんでも分かるプログラミング」シリーズを初め、プログラミングの基礎をやさしく説明していこうと思います。
読者のレベルとしては、PCを日常的に使いこなしていているが、プログラミングの専門教育を受けていないという想定です。
なお、あらかじめ、次の記事レベルのJavaの基礎知識は学習済みであることを前提とします。
プログラム初心者に贈るJavaプログラミング - Qiita
では、早速初めます。
第1回は、クラスとインターフェースについてです。
クラスってなに?
Javaのプログラムを書くと、必ずこんな「おまじない」を書いてくださいと言われたと思います。
public class Hello {
// ....(省略)
}
「パブリック」「クラス」と読みます。何でしょうこれは?
実はJavaのプログラムは「クラス」の集まりとして定義する「オブジェクト指向言語」なのです。
「オブジェクト指向」???
また新しい言葉が出てしまいました。
大丈夫です。これからゆっくり、説明します。
オブジェクト指向とは
「オブジェクト指向」とは、まじめに説明すると本が一冊書けてしまいます。ここに知り合いが書いた本気な資料がありますので、興味があったら読んでみてください。
ざっくり言うと、プログラムを「オブジェクト」という塊の集まりで作成します。「オブジェクト」とは「モノ」です。ここで、ちょっと日常世界を見てください。あらゆるモノがありますよね。
例えば、次の絵では、テーブルの上にコーヒーが淹れてあります。コーヒーはコーヒーカップに注がれていて、ソーサーの上に乗っています。スプーンも付いています。テーブルは木製ですね。金属の脚が付いています。「コーヒー」「カップ」「ソーサー」「スプーン」「テーブル」これらすべてがオブジェクトです。しかも「テーブル」は「木の板」「金属の脚」などの部品から構成されています。部品もオブジェクトです。
オブジェクト指向では、このように、あらゆる物に注目していきます。そうすると、一つ一つの「モノ」はシンプルに表現することができます。「カップ」はコーヒーを注ぐことができればいい。「スプーン」はお砂糖を掬うことができれば良いといった機能に特化することができるからです。
なぜそんなことをするかというと、昨今のコンピュータプログラムはとても複雑だからです。もし、すべてを一つのプログラムで書いていたら、神様のようにもの凄く巨大な存在になってしまいます。人間に神を作ることはできません。オブジェクト指向プログラミングがあるお陰で、複雑なプログラムも部品の組み合わせとして簡単に作れるというわけです。
クラスとインスタンス
さて、オブジェクトは「クラス」と「インスタンス」に分かれます。クラスはオブジェクトの設計図、インスタンスは設計図から作られたオブジェクトの実体です。一つの設計図からたくさんの実体を作ることができます。
これは、たい焼きモデルで説明されることがあります。
さて、Javaはこのうち「クラス」の部分をプログラミングして行きます。プログラムの初めに public class
と書いているのはそのためです。
実際にプログラムが動く時には「インスタンス」の方が使われます。「クラス」から「インスタンス」を作るためには次のように書きます。
new クラス名();
作ったクラスは通常、次のように書いて「変数」に格納して使います。変数は覚えていますよね?
クラス名 変数名 = new クラス名();
あれ、クラス名を2度書いていますね?実は、左側のクラス名は正しくは「型名」と言います。変数には「型」がありました。例えばこんな型です。
型 | 意味 |
---|---|
int | 整数 |
long | 大きな整数 |
double | 浮動小数点数 |
boolean | true or false |
Object | オブジェクトインスタンスを格納 |
クラス名 | そのクラスに属するオブジェクトインスタンスを格納 |
オブジェクトインスタンスを格納する変数型としては「Object」という型が存在します。なので、次のように書いてもエラーにはなりません。
Object 変数名 = new クラス名();
しかし、通常「Object型」は使用しません。それはなぜでしょうか?変数の型は、プログラミング言語に「その変数は何ができるのか?」と教える印なのです。つまり、
Object 変数名 = new クラス名();
というプログラムは「その変数はモノです」と言ってるに過ぎないのです。
「モノ」であることは分かっています。でも「モノ」と言われても、何ができるのかわからないですよね。「自動車」も「鍋」も「コーヒー」も、「人間」さえも、「モノ」ですから。
そこで、変数名の左にクラス名を書くことで、それが「モノ」の中でも何なのかを教えてあげているのです。
自動車 mycar = new 自動車();
mycar.走れ(); // これはOK
Object mycar = new 自動車();
mycar.走れ(); // 「モノ」が「走る」とは限らないからエラー
フィールドとメソッド
モノの設計図であるクラスは次のようにプログラミングします。
class クラス名 {
フィールド定義;
:
:
メソッド定義
:
:
}
「フィールド」とは、クラスに属する変数のことです。Java以外の言語では「メンバー変数」ということもあります。そして「メソッド」はクラスに属する関数のことです。同様に「メンバー関数」と呼ぶこともあります。
ざっくり言うと、クラスの定義は「フィールド」と「メソッド」を定義するだけです。それでどうしてモノの設計図になるのでしょうか?
状態と振る舞い
モノの概念に立ち戻ってみると、モノは「状態」と「振る舞い」という概念を持ちます。
「状態」というのは、年齢とか、職業とか、モノそれぞれによって、あるいは同じモノでもその時々によって変化する情報です。
「振る舞い」というのは、モノの外側から見える動作です。
ティーカップを例にすると、次のような状態と振る舞いがあります。
- 振る舞い
- 液体を注ぐ
- 注いだ液体を飲む
- 状態
- 汚れの有無
- ヒビの有無
- 今注がれているモノ
そして、Javaのフィールドは「状態」を、メソッドは「振る舞い」を表現しているのです。
ねこ様をプログラミング
それでは、ニャーと鳴くねこ様をプログラミングしてみましょう。
このねこ様は外部から見ると、こんな振る舞いをするようです。
ここで、ねこ様は「鳴け」「寝ろ」「起きろ」なんていう人間の命令を聞かないとは思わないでください。コンピュータプログラミングというのは、日常世界全てを表現する必要はありません。今、自分が必要な範囲だけプログラミングすれば良いのです。
ねこ様の状態として「寝ている」という状態があるとします。このプログラムでは、寝ている時と、起きている時で鳴き方が違うということを表現します。
以下がプログラミング例です。
/** ねこ様 */
class Cat {
/** 寝ているかどうか。true:寝ている, false:起きている */
boolean slept;
/** 鳴け */
void mew() {
if (slept) {
System.out.println("ふにゅあ")
} else {
System.out.println("にゃー")
}
}
/** 寝ろ */
void goToSleep() {
slept = true;
}
/** 起きろ */
void wakeUp() {
slept = false;
}
}
早速、ねこ様クラスを使ってみましょう
Cat tama = new Cat();
tama.mew();
tama.goToSleep();
tama.mew();
tama.wakeUp();
tama.mew();
実行結果
ニャー
ふにゃあ
ニャー
※ 最初のmewは「ニャー」となっています。これは、sleptフィールドの初期値がfalseであることを意味しています。このようにフィールド変数は、初期値が決められています。
型 | 初期値 |
---|---|
数値 | 0 |
boolean | false |
オブジェクト | null |
アクセス修飾子
よく見かけるpubicとか、privateとは何でしょうか?これは外部に公開するかどうかを指定する記述です。ざっくり言うと
アクセス修飾子 | 意味 |
---|---|
private | 外部に非公開 |
public | 外部に公開 |
という風に分類されます。他にprotectedというのがありますが、後ほど説明します。
もし、さっきのねこ様クラスをprivate classとして作成してしまうと、別のプログラムから参照できなくなります。(実際は記述は許されません)
では、何でもpublicにしてしまえば便利かというと、世の中何でもかんでも中のことをオープンにしてしまうことは不都合だったりします(笑)
オブジェクト指向プログラミングでは、積極的に、外側から見て不要なものを隠蔽していきます。そのようなテクニックを「カプセル化」と呼びます。
常に当てはまるわけではありませんが、「メソッド」は公開し、「フィールド」は非公開するのがセオリーです。もし、フィールドの値を直接確認したい場合は、getフィールド名()
、setフィールド名()
という「メソッド」を作るのが作法とされます。こういったメソッドを「アクセッサー」と呼びます。
アクセッサーの例
public class Human {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
メソッドもまた「非公開」にすることがあります。これは、外部からの振る舞いといよりも、プログラミングの都合上で共通部分を抜き出した場合などに、そのようにします。
/** 名前を教えると挨拶してくれるマシーン */
public class HelloMachiene {
private String firstName;
private String lastName;
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
// 姓と名をくっつけて使うことが多いので共通化
private String getFullName() {
return lastName + firstName;
}
public void sayHello() {
System.out.println("こんにちは" + getFullName() + "さん");
}
public void sayGoodBye() {
System.out.println("さようなら" + getFullName() + "さん");
}
}
コンストラクタ
new クラス名()
と書いた時にプログラムを動かして、フィールドを初期化したいことが良くあります。このようなときに使うものとして「コンストラクタ」というものがあります。
コンストラクタはクラス内に次のように記述します。
public クラス名() {
// 初期化プログラム
}
メソッドと似ていますが、返り値の型がありません。メソッドは
public 返り値の型 メソッド名() {
// プログラム
}
でした。ところで、コンストラクタは、メソッドと同じようにパラメータを持つことが出来ます。上手く使うとプログラムを完結に書くことが出来ます。
public class Human {
private String name;
private int age;
public Human(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return name + "(" + age + ")";
}
}
使用例
Human satoshi = new Human("サトシ", 10);
Human okido = new Human("オーキド", 55);
System.out.println(satochi);
System.out.println(okido);
結果
サトシ(10)
オーキド(55)
※toStringという名前のメソッドを作って文字列が必要な場所で使用すると、toStringを省略してもtoStringが呼ばれます。これはJavaの仕様です。つまり
System.out.println(satochi.toString());
と書いても同じ結果になります。
継承・多態性
新しいことが次々に出てきて大変ですね!でも、もう少しでオブジェクト指向の一番大事なところを知ることが出来ます。大変ですが、どうか我慢してください。
クラスを作っていくと、同じようなクラスができてくることに気が付きます。そして、それらを束ねたいことがあります。その時に使用するのが「継承」というしくみです。
例えば、「ニャー」と鳴くねこ様のほかに「ワン」と鳴く犬クラス、「モー」と鳴く牛クラス、「ブヒー」と鳴くブタクラスを作ったとします
class Cat {
public makeSounds() {
System.out.println("ニャー");
}
}
class Dog {
public makeSounds() {
System.out.println("ワン");
}
}
class Cow {
public makeSounds() {
System.out.println("モー");
}
}
class Pig {
public makeSounds() {
System.out.println("ブヒー");
}
}
※英語とは面倒なもので、ねこはmew, 犬はbarkのように「鳴く」という動詞が異なるため、音を鳴らすというmakeSoundsで統一しました。
あるメソッドがあって、動物を鳴かせたいとします。しかし、ねこを鳴かせるメソッドを使って、犬を鳴かせることは出来ません。
void makeSounds(Cat cat) {
cat.makeSounds();
}
Cat tama = new Cat();
Dog pochi = new Dog();
makeSounds(tama); // これはOK
makeSounds(dog); // これはダメ
これでは不便です。なぜなら、CatもDogも、makeSoundsというメソッドを持っているからです。
そこで、そこで、次の2つの考え方があります。
考えた方1: 犬もねこも「鳴く」ことができる
外部から見て、共通の振る舞いを持っているものは、「インターフェース」というしくみで束ねることが出来ます。
インターフェースは次のように定義します
interface インターフェース名 {
返り値の型 メソッド名();
返り値の型 メソッド名();
:
:
}
中身の無いメソッドの名前だけを並べて行くのです。
そして、クラスを作るときにimplementsというキーワードでインターフェースを指定すると、そのクラスのインスタンスは、そのクラス型の変数の他に、そのインターフェース型の変数にも代入することが出来ます。
難しいのでアンダーラインを引きました。深呼吸して、この部分を5回読んでみてください。
あせらないで!ゆっくりで良いですよ!
5回読みましたか?
では、具体例を書きますね。
class Cat implements SoundsMaker {
// ...
}
として定義したら、こんな書き方がOKになります。
SoundsMaker tama = new Cat();
そして、次のプログラミングコードが成立します。
interface SoundsMaker {
void makeSounds();
}
class Cat implements SoundsMaker {
public makeSounds() {
System.out.println("ニャー");
}
}
class Dog implements SoundsMaker {
public makeSounds() {
System.out.println("ワン");
}
}
class Cow implements SoundsMaker {
public makeSounds() {
System.out.println("モー");
}
}
class Pig implements SoundsMaker {
public makeSounds() {
System.out.println("ブヒー");
}
}
public makeSounds(SoundsMaker soundsMaker) {
soundsMaker.makeSounds();
}
Cat tama = new Cat();
Dog pochi = new Dog();
makeSounds(tama);
makeSounds(pochi);
実行結果
ニャー
ワン
このテクニックを「インターフェース継承」あるいは「インターフェース抽出」と呼びます。
考え方2:犬もねこも「動物」である
犬も、ねこも、牛も「動物」という共通の概念を持っています。そういう共通の概念をスーパークラスと呼びます(親クラスとも呼びます)。反対は「サブクラス」(子クラス)です。
Javaではextendsというキーワードでスーパークラスを指定します。
class Animal {
void makeSounds() {
System.out.println("???");
}
}
class Cat extends Animal {
public makeSounds() {
System.out.println("ニャー");
}
}
class Dog extends Animal {
public makeSounds() {
System.out.println("ワン");
}
}
インターフェースとの違いは、スーパークラスはあくまでクラスであるということです。
new Animal()
と書いてしまうことも出来ます(禁止することも出来ますが今は割愛します)。またフィールドを持ったり、メソッドのプログラムを記述することも出来ます。そのようにすることで、共通メソッドなどを作ることも出来ます。
このテクニックを「クラス継承」、または単に「継承」と呼ぶことがあります。
どっちを使ったら良いの?
もし、振る舞いを束ねたいだけ場合は、インターフェースを使用してください。でも、インターフェースはめんどくさいこともあります。特にたくさんのメソッドを束ねるときには向いていません。また実装を持つことが出来ないため、コピーペーストが増えてしまいます。
スーパークラスは、一つしか持つことが出来ません。つまり、安易にスーパークラスを使うと後で後悔することになります。
どんなときにクラス継承を使うか、インターフェースを使うかなどを指南するものの一つとして、「デザインパターン」というテクニック集が存在します。このシリーズの中でも紹介して行きたく思います。
多態性(ポリモーフィズム)
「多態性」とは聞き慣れないキーワードですね。しかし、これこそが、オブジェクト指向の真髄なのです。しかも、既にあなたはこのテクニックを使っています!
先程の例で、SoundsMakerインターフェースを使った例でも、Animalスーパークラスを使った例でも、どちらでも結構です。
あるところに
SoundsMaker m;
という変数がありました。そして、紆余曲折を経てmには何か値が代入されています。その後
m.makeSounds();
と書いてあったとします。実行結果は次のうちどれでしょうか?
- ニャー
- ワン
- ブヒー
- モー
- ???
正解は「わからない」です。
引っかけでごめんなさい。そうなのです。その時点で、どのように動くのか、実行してみないと分からないという柔軟なプログラミングが出来るのです。これが出来ると、プログラミングが大変便利になります。
とにかく「鳴く」ことが出来るものを(実際にどんな鳴き声かは気にせず)一緒くたに全部束ねてプログラミングしておいて、後から「鳴く」何かを当てはめるというプログラミング手法が実際には非常によく行われます。
そうすれば、たったひとつのプログラムで何通りものプログラムが作れてしまうからです。
インラインサブクラス
ここまで読めたあなたはもうオブジェクト指向マスター一歩手前です。(おめでとうございます!パチパチ)
最後に、先ほどの多態性を超強力に使えるプログラミングテクニックを紹介します。
ここまでの復習でオブジェクトとは何かを振り返ってみましょう
- オブジェクトの設計図として、クラスを定義する
- クラスはメソッドとフィールドを持つ
- クラスはインターフェースまたは別のクラスを「継承」することができる
- 継承元のインターフェースやクラスを型とした変数には、「継承」先のサブクラスのインスタンスを代入することができる
ここで「継承先のサブクラス」というのは、実は、いつでも作ることができます。いちいち、javaファイルを作って、名前を決めてextendsって書いたりせずに、欲しくなったらいきなり書いてしまうことが出来るのです。
先ほどの、SoundsMakerインターフェースを使った例を元に話を進めますね。
SoundsMaker s = new SoundsMaker() {
public void makeSounds() {
System.out.println("ぎゃああああああ");
}
};
makeSounds(s);
実行結果
きゃああああああ
こんな風にいきなりプログラムの途中に作られたクラスを、「インラインサブクラス」と呼びます。この技はインターフェースに対しても、スーパークラスに対しても使用可能です。
なぜこんなテクニックが必要かというと、名前を決めることが結構大変だからです。親になった気持ちになってください。その子の名前をいい加減に決められますか?その子の将来がかかっているので、他人から見て変じゃないかとか、縁起が良いかとか、あれこれ考えてようやく名前が決まります。
プログラミングしていて圧倒的に多くの時間を費やすのが「名前決め」です。ですから、一度しか使わないような子には、名前を与えずいきなり使ってしまえば良いのです。(笑)
変数すら省略できる
オブジェクトインスタンスは変数に格納すると書きました。しかし、その場で一回だけ使えば良いなら、なにも変数すらも使わなくても良いのです。
new Cat().makeSounds()
実行結果
ニャー
これらの技を組み合わせると、こんなコードも書けてしまいます。
new SoundsMaker() {
void makeSounds() {
System.out.println("ぎょええ~~~")
}
}.makeSounds();
分かりますか?「ぎょええ~~~」と鳴くモノを作って、その場で鳴かせているのです。
なぜこんなことを書いたかというと、次回以降で多用するからです。