オブジェクト指向の基礎
この章で得られるスキル:
- ✅ 関連するデータと処理をまとめて管理できる
- ✅ 自分でクラスを設計してオブジェクトを作れる
- ✅ 複数のオブジェクトを使った実用的なプログラムが書ける
- ✅ オブジェクト指向の基本的な考え方を理解できる
初学者への励まし
この章は、初学者にとって最初の鬼門 である。オブジェクト指向は、プログラミングを学ぶ上で避けて通れない重要な概念だが、 最初は難しく感じるのが普通である。
オブジェクト指向が難しい理由
- 用語が聞き慣れない:クラス、インスタンス、フィールド、メソッド、カプセル化、継承など、初めて聞く言葉ばかり
- 概念的で抽象的:目に見えないものをイメージする必要がある
- 今まで学んだこととの違い:変数や条件分岐は「手順」だったが、オブジェクト指向は「設計」の話
学習のコツ
- まずは用語に慣れる:完璧に理解しようとせず、何度も読んで用語に慣れることから始める
- 具体例で考える:「車」「犬」など、身近なものに置き換えて考える
- 繰り返し学習:1回で理解できなくても大丈夫。繰り返し読んで、少しずつ理解を深めていく
オブジェクト指向は、すぐに理解できなくても、コードを書いているうちに自然と身についてくる。 焦らず、じっくり学んでいこう。
Step 0: オブジェクト指向がないとどうなる?
まず、オブジェクト指向を使わずにプログラムを書いてみよう。
問題シナリオ:3人の学生を管理する
3人の学生(太郎、花子、次郎)の名前と年齢を管理し、全員の情報を表示したいとする。
オブジェクト指向を使わない場合:
問題点
このコードには、いくつかの深刻な問題がある:
1. データがバラバラ
name1とage1が同じ「太郎」に関連するデータだが、その関連性がコードから読み取りにくい- 変数が増えると、どの名前とどの年齢が対応するのか分からなくなる
2. スケールしない
- 学生が10人、100人になったら、
name1〜name100、age1〜age100を個別に管理する必要がある - 変数が爆発的に増える
3. 処理を追加しにくい
- 「自己紹介する」などの処理を追加したい場合、同じコードを3回書く必要がある:
System.out.println("私の名前は" + name1 + "で、" + age1 + "歳です");
System.out.println("私の名前は" + name2 + "で、" + age2 + "歳です");
System.out.println("私の名前は" + name3 + "で、" + age3 + "歳です");
4. 配列でも限界がある
配列を使えば少しマシになるが、それでも名前と年齢を別々の配列で管理する必要がある:
これでも問題は残る:
names[i]とages[i]の対応関係が暗黙的(インデックスが一致していることを前提とする)- データが増えると配列が増える(名前、年齢、住所、電話番号...)
- 処理(自己紹介、誕生日など)をどこに書けばよいのか不明確
本質的な課題
関連するデータと処理をまとめる仕組みがない。
- 「太郎」というデータと「自己紹介する」という処理は密接に関連している
- しかし、バラバラに定義されているため、管理が煩雑
ではどうすれば良いのか?
Step 1: オブジェクト指向という解決策
オブジェクト指向とは
オブジェクト指向 は、「もの」を中心にプログラムを設計する考え方 である。
「もの」とは:
- 関連するデータ(属性)と処理(振る舞い)をまとめたもの
- 例:「学生」というもの
- データ:名前、年齢
- 処理:自己紹介する、誕生日を迎える
手続き型との違い
| 比較項目 | 手続き型 | オブジェクト指向 |
|---|---|---|
| 考え方 | 「何をするか」(処理の手順)中心 | 「何があるか」(ものとその性質・振る舞い)中心 |
| データと処理 | バラバラに定義 | まとめて定義 |
| 例 | name1, age1, printInfo(name, age) | Studentにname, age, introduce()をまとめる |
現実世界の例:車
手続き型の考え方(処理中心):
- エンジンをかける
- アクセルを踏む
- ブレーキを踏む
オブジェクト指向の考え方(もの中心):
- 車 というもの(オブジェクト)がある
- 車は 色、速度 などの性質(データ)を持つ
- 車は 発進する、停止する などの振る舞い(処理)を持つ
オブジェクト指向では、「もの」を単位として、データと処理をまとめて考える。
オブジェクト指向の3つの利点
- データと処理の関連性が明確:
Studentクラスにnameとintroduce()がまとまっているため、関連性が一目瞭然 - スケーラビリティ:学生が何人になっても、
Studentの配列やリストで管理できる - 再利用性:一度
Studentクラスを定義すれば、何度でも使える
オブジェクト指向の用語整理
この章で登場する重要な用語を整理しておこう。最初は用語に慣れることが大切である。
| 用語 | 意味 | 例 |
|---|---|---|
| クラス | オブジェクトの設計図 | Studentクラス(学生の設計図) |
| オブジェクト | 「もの」そのもの、データと処理のまとまり | 学生、車、商品など |
| インスタンス | クラスから生成された実体 | new Student()で作られた具体的な学生 |
| フィールド | クラスが持つデータ(変数) | name(名前)、age(年齢) |
| インスタンスメソッド | インスタンスが持つ処理(関数) | introduce()(自己紹介) |
| staticメソッド | クラスに属する処理、インスタンス不要 | Math.abs()、main() |
| new | クラスからインスタンスを生成するキーワード | new Student() |
最初は用語を全て覚える必要はない。この章を読み進めながら、何度もこの表を見返して、徐々に慣れていこう。
Step 2: クラスとインスタンス
クラスとは
クラス は、オブジェクトの設計図 である。
インスタンスとは
インスタンス(または オブジェクト)は、設計図(クラス)から作られた実体 である。
例:クッキーの型と実際のクッキー
- クラス:クッキーの型(設計図)
- インスタンス:型を使って作った実際のクッキー(実体)
1つの型(クラス)から、たくさんのクッキー(インスタンス)を作ることができる。
図解
図の見方:
- Student(上):クラス(設計図)
- student1, student2, student3(下):
newによって生成されたインスタンス(実体) - 1つのクラスから複数のインスタンスを生成できる
「オブジェクト」と「インスタンス」は、ほぼ同じ意味で使われる。 厳密には「クラスから生成された実体」を指すとき「インスタンス」という。
Step 3: クラスの定義とフィールド
クラスの定義
Javaでクラスを定義する基本形は以下の通り:
class クラス名 {
// フィールド(データ)
// メソッド(処理)
}
Studentクラスの定義例
フィールドとは
フィールド は、クラスが持つデータ(変数)である。 インスタンスごとに異なる値を持つことができる。
class Student {
String name; // フィールド(名前)
int age; // フィールド(年齢)
}
インスタンスごとに異なる値
Student student1 = new Student();
student1.name = "太郎";
student1.age = 18;
Student student2 = new Student();
student2.name = "花子";
student2.age = 20;
student1は「太郎、18歳」student2は「花子、20歳」
それぞれのインスタンスが独立したデータを持つ。
フィールドへのアクセス
フィールドにアクセスするには、 ドット記法 を使う:
インスタンス名.フィールド名
例:
student1.name // student1のnameフィールド
student2.age // student2のageフィールド
Step 4: インスタンスメソッド
インスタンスメソッドとは
インスタンスメソッド は、インスタンスが持つ処理(振る舞い)である。
Studentクラスにメソッドを追加
メソッド内でフィールドにアクセス
インスタンスメソッドの中では、 そのインスタンスのフィールドに直接アクセスできる:
void introduce() {
System.out.println("私の名前は" + name + "で、" + age + "歳です");
// ↑ このnameとageは、呼び出したインスタンスのフィールド
}
taro.introduce()を呼ぶと、taroのnameとageが使われるhanako.introduce()を呼ぶと、hanakoのnameとageが使われる
メソッドの呼び出し方
インスタンス名.メソッド名()
例:
taro.introduce(); // 太郎のintroduce()メソッドを呼ぶ
hanako.birthday(); // 花子のbirthday()メソッドを呼ぶ
Step 5: staticメソッドとの違い
staticメソッドとインスタンスメソッドの違い
| 比較項目 | staticメソッド | インスタンスメソッド |
|---|---|---|
| 定義 | staticキーワードを付ける | staticを付けない |
| 呼び出し方 | クラス名.メソッド名() | インスタンス名.メソッド名() |
| インスタンス | 不要 | 必要(newで生成) |
| フィールドへのアクセス | インスタンスのフィールドにアクセス不可 | インスタンスのフィールドにアクセス可能 |
| 例 | Math.abs(-5) | student.introduce() |
具体例
なぜstaticとインスタンスの2種類があるのか
staticメソッド:
- インスタンスに依存しない汎用的な処理に使う
- 例:
Math.abs()、Math.max()、ユーティリティ関数
インスタンスメソッド:
- インスタンスのデータに依存する処理に使う
- 例:
student.introduce()(特定の学生の名前を表示)
public static void mainのstaticは、「mainメソッドはインスタンスなしで呼び出される」という意味だった。
今後、クラスを設計するときは、インスタンスメソッドを使うことが多い。
Step 6: インスタンスの生成とnewキーワード
newキーワード
newキーワード は、クラスからインスタンスを生成する ための命令である。
Student student = new Student();
処理の流れ:
new Student():Studentクラスのインスタンスを生成Student student:生成したインスタンスを変数studentに代入
インスタンスは変数に代入され、変数の中に構造を持つ
重要な理解ポイント:
- インスタンスは変数に代入できる
- 変数の中にフィールド(データ)の構造を持つ
Student student = new Student();
student.name = "太郎"; // 変数studentの中のnameフィールドに値を設定
student.age = 18; // 変数studentの中のageフィールドに値を設定
変数studentは、単なる値ではなく、 複数のフィールドを持つ構造 を格納している。
これが、オブジェクト指向プログラミングの核心である。
図解:
student(変数)
├─ name: "太郎" (フィールド)
├─ age: 18 (フィールド)
└─ introduce() (メソッド)
1つの変数の中に、複数のデータと処理をまとめて管理できる。 これにより、関連するデータをバラバラに管理する必要がなくなる。
複数のインスタンス
1つのクラスから、複数のインスタンスを生成できる。 それぞれのインスタンスは、独立したデータを持つ。
Step 7: 配列とオブジェクト指向の組み合わせ
複数のインスタンスを配列で管理
Step 0で見た「配列でも限界がある」問題を、オブジェクト指向で解決しよう。
オブジェクト指向を使わない場合(Step 0の再掲):
String[] names = {"太郎", "花子", "次郎"};
int[] ages = {18, 20, 19};
// 名前と年齢が別々の配列 → 対応関係が暗黙的
オブジェクト指向を使う場合:
利点
- データの関連性が明確:
students[0]に名前と年齢がまとまっている - スケーラビリティ:学生が何人になっても、配列のサイズを変えるだけ
- 処理が簡潔:
students[i].introduce()でメソッドを呼べる
データを追加しても影響が小さい
学生に「住所」フィールドを追加したいとしよう。
オブジェクト指向を使わない場合:
String[] names = {"太郎", "花子", "次郎"};
int[] ages = {18, 20, 19};
String[] addresses = {"東京", "大阪", "福岡"}; // 新しい配列を追加
// 配列が3つになり、対応関係がますます複雑に
オブジェクト指向を使う場合:
class Student {
String name;
int age;
String address; // フィールドを1行追加するだけ
void introduce() {
System.out.println("私の名前は" + name + "で、" + age + "歳、" + address + "在住です");
}
}
クラス定義を変更するだけで、全てのインスタンスに変更が適用される。
Step 8: 実践課題
ここまで学んだ知識を使って、以下の3つの課題に挑戦しよう。
課題1: Carクラスの作成
要件:
Carクラスを定義する- フィールド:
color(String)、speed(int) - メソッド:
start():速度を50に設定し、「〇〇の車が発進しました。速度: 50」と表示stop():速度を0に設定し、「〇〇の車が停止しました」と表示accelerate(int increase):速度を増加させ、「加速しました。現在の速度: 〇〇」と表示
- 2台の車を作成し、それぞれ異なる操作をさせる
ヒント:
Car car1 = new Car();
car1.color = "赤";
car1.speed = 0;
car1.start();
解答例を見る
課題2: Bookクラスの作成
要件:
Bookクラスを定義する- フィールド:
title(String)、author(String)、price(int) - メソッド:
showInfo():「『〇〇』 著者: 〇〇, 価格: 〇〇円」と表示applyDiscount(int discountPercent):価格を指定した割引率で割引し、「〇〇%割引適用。新しい価格: 〇〇円」と表示
- 3冊の本を配列で管理し、全ての本の情報を表示
- 1冊目の本に30%割引を適用
ヒント:
Book[] books = new Book[3];
books[0] = new Book();
books[0].title = "Javaの教科書";
解答例を見る
課題3: RPGのキャラクター管理
要件:
Characterクラスを定義する- フィールド:
name(String)、hp(int)、attackPower(int) - メソッド:
showStatus():「〇〇 HP: 〇〇, 攻撃力: 〇〇」と表示attack(Character target):相手のHPを自分の攻撃力分減らし、「〇〇が〇〇を攻撃! 〇〇のHPが〇〇減少」と表示isAlive():HPが0より大きければtrue、そうでなければfalseを返す
- 2人のキャラクター(勇者とモンスター)を作成
- 勇者がモンスターを攻撃し、モンスターが倒れるまで繰り返す
ヒント:
void attack(Character target) {
target.hp -= this.attackPower;
System.out.println(this.name + "が" + target.name + "を攻撃!");
}
解答例を見る
FAQ
Q1: クラス名は何でも良いのか?
A1:
クラス名には 命名規則 がある:
-
PascalCase(パスカルケース):各単語の頭文字を大文字にする
- 良い例:
Student,Car,BookStore - 悪い例:
student,car,bookstore
- 良い例:
-
名詞を使う:クラスは「もの」を表すため
- 良い例:
User,Product,Order - 悪い例:
Run,Calculate(動詞は避ける)
- 良い例:
-
意味のある名前:クラスの役割が分かる名前
- 良い例:
ShoppingCart,CustomerAccount - 悪い例:
Data,Object,Thing
- 良い例:
Q2: フィールドを直接書き換えて良いのか?
A2:
この章では学習のため直接書き換えているが、実務では推奨されない。
理由:
- データの整合性が保てない(例:年齢に負の値を設定できてしまう)
- 外部から自由に変更できると、予期しないバグが発生する
解決策(次章以降で学ぶ):
- カプセル化:フィールドを
privateにし、外部から直接アクセスできなくする - getter/setter:メソッド経由でアクセスし、バリデーションを入れる
class Student {
private int age; // privateにする
public void setAge(int age) {
if (age < 0) {
System.out.println("年齢は0以上で設定してください");
return;
}
this.age = age;
}
public int getAge() {
return age;
}
}
Q3: staticメソッドからインスタンスメソッドを呼べるか?
A3:
直接は呼べない。 インスタンスを経由すれば呼べる。
エラーになる例:
class Student {
String name;
void introduce() {
System.out.println("私の名前は" + name + "です");
}
public static void main(String[] args) {
introduce(); // エラー!staticからインスタンスメソッドを直接呼べない
}
}
正しい方法:
class Student {
String name;
void introduce() {
System.out.println("私の名前は" + name + "です");
}
public static void main(String[] args) {
Student student = new Student(); // インスタンスを生成
student.name = "太郎";
student.introduce(); // インスタンス経由で呼ぶ(OK)
}
}
理由:
staticメソッドはクラスに属し、特定のインスタンスに依存しない。 インスタンスメソッドは特定のインスタンスに属するため、どのインスタンスを指すのか明示する必要がある。
Q4: newを忘れるとどうなるか?
A4:
NullPointerException(ヌルポインター例外)が発生する。
エラーになる例:
Student student; // インスタンスを生成していない
student.name = "太郎"; // エラー!NullPointerException
エラーメッセージ:
Exception in thread "main" java.lang.NullPointerException
原因:
newを忘れて、インスタンスを生成していない。変数studentはnull(何も指していない)状態。
解決策:
Student student = new Student(); // newでインスタンスを生成
student.name = "太郎"; // OK
nullとは:
nullは「何も参照していない」という特別な値。
インスタンスを生成せずに変数だけ宣言すると、変数にはnullが入る。
Q5: クラスとファイルの関係は?
A5:
原則:1つのファイルに1つのpublicクラス。
詳細:
-
publicクラスはファイル名と一致させる
public class Studentなら、ファイル名はStudent.java
-
1つのファイルに複数のクラスを定義できる
- ただし、publicクラスは1つだけ
- 他のクラスはpublicを付けない
例:
// Student.java
public class Student {
String name;
int age;
}
// publicでない補助クラス(同じファイル内に定義可能)
class StudentHelper {
static void printAllStudents(Student[] students) {
for (Student s : students) {
System.out.println(s.name);
}
}
}
この章のサンプルコード:
OneCompilerでは便宜上、1つのファイルに複数のクラスを書いている。 実務では、各クラスを別ファイルに分けるのが一般的。
まとめ
この章では、オブジェクト指向の基礎 について学んだ。
学んだ内容
- Step 0: オブジェクト指向がないと、関連するデータと処理をまとめる仕組みがなく、管理が煩雑になる
- Step 1: オブジェクト指向は「もの」を中心にプログラムを設計する考え方である
- Step 2: クラスは設計図、インスタンスは設計図から作られた実体である
- Step 3: フィールドはクラスが持つデータであり、インスタンスごとに異なる値を持つ
- Step 4: インスタンスメソッドはインスタンスが持つ処理であり、フィールドにアクセスできる
- Step 5: staticメソッドはクラスに属し、インスタンスメソッドはインスタンスに属する
- Step 6:
newキーワードでインスタンスを生成し、変数に代入する - Step 7: 複数のインスタンスを配列で管理すると、データの関連性が明確になる
- Step 8: 実践課題で、クラス設計とインスタンス操作を体験した
次のステップ
次の章では、コンストラクタ について学ぶ。 インスタンス生成時に自動的に実行される特別なメソッドを使って、 インスタンスの初期化をより効率的に行う方法を学ぶ。