はじめに#
Javaはオブジェクト指向プログラミング(OOP) を基盤とする言語です。オブジェクト指向を理解することで、現実世界の概念をプログラムで表現し、保守性・拡張性の高いコードを設計できるようになります。
この記事では、オブジェクト指向の核となる「クラス」と「オブジェクト」について、基本から実践まで段階的に解説します。クラスの定義方法、フィールドとメソッド、コンストラクタ、インスタンス化、thisキーワード、そしてカプセル化まで、一通りマスターすることを目標とします。
オブジェクト指向プログラミングとは#
オブジェクト指向プログラミング(Object-Oriented Programming、OOP)とは、プログラムを「オブジェクト」という単位で構成する設計手法です。
現実世界とプログラムの対応#
オブジェクト指向では、現実世界の「もの」や「概念」をプログラム上で表現します。
| 現実世界 |
プログラムでの表現 |
| 車 |
Carオブジェクト |
| 車の色、速度 |
フィールド(データ) |
| 走る、止まる |
メソッド(動作) |
オブジェクト指向の4大原則#
オブジェクト指向には以下の4つの重要な原則があります。
| 原則 |
説明 |
| カプセル化 |
データと処理をひとまとめにし、外部からの不正なアクセスを防ぐ |
| 継承 |
既存のクラスの機能を引き継いで新しいクラスを作成する |
| ポリモーフィズム |
同じ操作でも対象によって異なる動作をさせる |
| 抽象化 |
共通の特徴を抽出してモデル化する |
この記事では、最も基本となるカプセル化に焦点を当てて解説します。継承とポリモーフィズムは後続の記事で詳しく扱います。
クラスとオブジェクトの関係#
オブジェクト指向を理解する上で、最も重要な概念が「クラス」と「オブジェクト」の関係です。
flowchart LR
subgraph クラス["クラス(設計図)"]
direction TB
A["Car"]
A1["フィールド: color, speed"]
A2["メソッド: accelerate(), brake()"]
end
subgraph オブジェクト["オブジェクト(実体)"]
direction TB
B1["myCar\ncolor: 赤\nspeed: 60"]
B2["yourCar\ncolor: 青\nspeed: 40"]
end
クラス -->|new| B1
クラス -->|new| B2
| 用語 |
説明 |
たとえ |
| クラス |
オブジェクトの設計図・型 |
車の設計図 |
| オブジェクト |
クラスから生成された実体 |
実際に製造された車 |
| インスタンス |
オブジェクトの別名(同義) |
- |
クラスは「設計図」であり、オブジェクトはその設計図から作られた「実体」です。1つの設計図から複数の実体を作成できるように、1つのクラスから複数のオブジェクトを生成できます。
クラスの定義#
Javaでクラスを定義する基本構文を確認しましょう。
クラス定義の基本構文#
1
2
3
4
5
6
7
|
public class クラス名 {
// フィールド(データ)
// コンストラクタ
// メソッド(動作)
}
|
最小限のクラス定義#
まずは最も単純なクラスを定義してみましょう。
1
2
3
|
public class Car {
// 現時点では中身は空
}
|
このクラスはまだ何も持っていませんが、有効なJavaクラスとして機能します。
クラス定義のルール#
クラスを定義する際は、以下のルールに従います。
| ルール |
説明 |
| ファイル名 |
publicクラス名と同じにする(Car.java) |
| クラス名 |
大文字で始める(PascalCase) |
| 1ファイル1publicクラス |
1つのファイルにはpublicクラスは1つだけ |
フィールドの定義#
フィールドとは、クラスが持つ「データ」を表す変数です。オブジェクトの状態を保持します。
フィールドの宣言#
1
2
3
4
5
6
|
public class Car {
// フィールド(インスタンス変数)
String color; // 色
int speed; // 速度
String modelName; // モデル名
}
|
フィールドのアクセス#
オブジェクトを生成すれば、ドット(.)演算子でフィールドにアクセスできます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class Main {
public static void main(String[] args) {
Car myCar = new Car();
// フィールドへの値の設定
myCar.color = "赤";
myCar.speed = 60;
myCar.modelName = "プリウス";
// フィールドの値を取得
System.out.println("色: " + myCar.color);
System.out.println("速度: " + myCar.speed + " km/h");
System.out.println("モデル: " + myCar.modelName);
}
}
|
実行結果:
色: 赤
速度: 60 km/h
モデル: プリウス
フィールドのデフォルト値#
フィールドを初期化しない場合、データ型に応じたデフォルト値が自動的に設定されます。
| データ型 |
デフォルト値 |
| int, long, short, byte |
0 |
| float, double |
0.0 |
| boolean |
false |
| char |
‘\u0000’(空文字) |
| 参照型(String, 配列など) |
null |
1
2
3
4
5
6
7
8
9
|
public class DefaultValueDemo {
public static void main(String[] args) {
Car car = new Car();
// 初期化していないフィールドはデフォルト値を持つ
System.out.println(car.color); // null
System.out.println(car.speed); // 0
}
}
|
メソッドの定義#
メソッドとは、クラスが持つ「動作」を表す処理のまとまりです。オブジェクトが実行できる操作を定義します。
インスタンスメソッドの基本構文#
1
2
3
4
|
アクセス修飾子 戻り値の型 メソッド名(引数リスト) {
// 処理内容
return 戻り値; // 戻り値がある場合
}
|
Carクラスにメソッドを追加#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public class Car {
// フィールド
String color;
int speed;
String modelName;
// 加速するメソッド
void accelerate(int amount) {
speed = speed + amount;
System.out.println(amount + " km/h 加速しました。現在の速度: " + speed + " km/h");
}
// ブレーキをかけるメソッド
void brake(int amount) {
speed = speed - amount;
if (speed < 0) {
speed = 0;
}
System.out.println(amount + " km/h 減速しました。現在の速度: " + speed + " km/h");
}
// 車の情報を表示するメソッド
void showInfo() {
System.out.println("モデル: " + modelName);
System.out.println("色: " + color);
System.out.println("速度: " + speed + " km/h");
}
}
|
メソッドの呼び出し#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class Main {
public static void main(String[] args) {
Car myCar = new Car();
myCar.color = "赤";
myCar.speed = 0;
myCar.modelName = "プリウス";
myCar.showInfo();
System.out.println();
myCar.accelerate(30);
myCar.accelerate(20);
myCar.brake(10);
}
}
|
実行結果:
モデル: プリウス
色: 赤
速度: 0 km/h
30 km/h 加速しました。現在の速度: 30 km/h
20 km/h 加速しました。現在の速度: 50 km/h
10 km/h 減速しました。現在の速度: 40 km/h
インスタンスメソッドとstaticメソッドの違い#
メソッドには「インスタンスメソッド」と「staticメソッド」の2種類があります。
| 種類 |
呼び出し方 |
フィールドへのアクセス |
| インスタンスメソッド |
オブジェクト経由 |
インスタンス変数にアクセス可能 |
| staticメソッド |
クラス名経由 |
インスタンス変数にアクセス不可 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class Calculator {
// インスタンスメソッド
int add(int a, int b) {
return a + b;
}
// staticメソッド
static int multiply(int a, int b) {
return a * b;
}
}
public class Main {
public static void main(String[] args) {
// インスタンスメソッドはオブジェクト経由で呼び出す
Calculator calc = new Calculator();
System.out.println(calc.add(3, 5)); // 8
// staticメソッドはクラス名で直接呼び出せる
System.out.println(Calculator.multiply(3, 5)); // 15
}
}
|
コンストラクタの役割と定義#
コンストラクタとは、オブジェクトを生成する際に自動的に呼び出される特別なメソッドです。オブジェクトの初期化処理を記述します。
コンストラクタの基本構文#
1
2
3
4
5
6
|
public class クラス名 {
// コンストラクタ(クラス名と同じ名前、戻り値なし)
public クラス名(引数リスト) {
// 初期化処理
}
}
|
コンストラクタの特徴#
コンストラクタには以下の特徴があります。
| 特徴 |
説明 |
| 名前 |
クラス名と完全に一致する |
| 戻り値 |
戻り値の型を書かない(voidも書かない) |
| 呼び出し |
new演算子でオブジェクト生成時に自動で呼ばれる |
| 目的 |
フィールドの初期化 |
デフォルトコンストラクタ#
クラスにコンストラクタを1つも定義しない場合、Javaコンパイラが自動的に「デフォルトコンストラクタ」を追加します。
1
2
3
4
5
6
7
8
|
public class Car {
String color;
int speed;
// コンストラクタを定義しない場合、以下が暗黙的に追加される
// public Car() {
// }
}
|
引数付きコンストラクタ#
オブジェクト生成時に初期値を指定できるコンストラクタを定義しましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class Car {
String color;
int speed;
String modelName;
// 引数付きコンストラクタ
public Car(String color, int speed, String modelName) {
this.color = color;
this.speed = speed;
this.modelName = modelName;
}
void showInfo() {
System.out.println("モデル: " + modelName + ", 色: " + color + ", 速度: " + speed + " km/h");
}
}
|
1
2
3
4
5
6
7
8
9
10
|
public class Main {
public static void main(String[] args) {
// コンストラクタで初期値を設定
Car myCar = new Car("赤", 0, "プリウス");
Car yourCar = new Car("青", 0, "アクア");
myCar.showInfo();
yourCar.showInfo();
}
}
|
実行結果:
モデル: プリウス, 色: 赤, 速度: 0 km/h
モデル: アクア, 色: 青, 速度: 0 km/h
コンストラクタのオーバーロード#
同じクラスに複数のコンストラクタを定義できます。引数の数や型が異なれば、複数のコンストラクタを持てます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public class Car {
String color;
int speed;
String modelName;
// 引数なしコンストラクタ
public Car() {
this.color = "白";
this.speed = 0;
this.modelName = "不明";
}
// モデル名のみ指定するコンストラクタ
public Car(String modelName) {
this.color = "白";
this.speed = 0;
this.modelName = modelName;
}
// 全フィールドを指定するコンストラクタ
public Car(String color, int speed, String modelName) {
this.color = color;
this.speed = speed;
this.modelName = modelName;
}
void showInfo() {
System.out.println("モデル: " + modelName + ", 色: " + color + ", 速度: " + speed + " km/h");
}
}
|
1
2
3
4
5
6
7
8
9
10
11
|
public class Main {
public static void main(String[] args) {
Car car1 = new Car(); // 引数なし
Car car2 = new Car("クラウン"); // モデル名のみ
Car car3 = new Car("黒", 0, "レクサス"); // 全指定
car1.showInfo();
car2.showInfo();
car3.showInfo();
}
}
|
実行結果:
モデル: 不明, 色: 白, 速度: 0 km/h
モデル: クラウン, 色: 白, 速度: 0 km/h
モデル: レクサス, 色: 黒, 速度: 0 km/h
オブジェクトのインスタンス化#
クラスからオブジェクトを生成することを「インスタンス化」と呼びます。
new演算子の役割#
new演算子は以下の処理を行います。
- メモリ上にオブジェクト用の領域を確保する
- コンストラクタを呼び出してフィールドを初期化する
- 生成したオブジェクトへの参照を返す
1
2
3
4
5
|
// インスタンス化の構文
クラス名 変数名 = new クラス名(引数);
// 具体例
Car myCar = new Car("赤", 0, "プリウス");
|
参照変数とオブジェクトの関係#
参照変数は、オブジェクトそのものではなく、オブジェクトへの「参照(アドレス)」を保持します。
flowchart LR
subgraph スタック領域
A["myCar\n(参照変数)"]
B["yourCar\n(参照変数)"]
end
subgraph ヒープ領域
C["Carオブジェクト\ncolor: 赤\nspeed: 0"]
D["Carオブジェクト\ncolor: 青\nspeed: 0"]
end
A --> C
B --> D
1
2
3
4
5
6
7
8
9
10
|
public class ReferenceDemo {
public static void main(String[] args) {
Car myCar = new Car("赤", 0, "プリウス");
Car anotherRef = myCar; // 同じオブジェクトを参照
anotherRef.color = "緑"; // anotherRef経由で変更
System.out.println(myCar.color); // "緑" と表示される
}
}
|
myCarとanotherRefは同じオブジェクトを指しているため、一方を通じた変更はもう一方にも反映されます。
複数オブジェクトの生成#
1つのクラスから複数の独立したオブジェクトを生成できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class MultipleObjectsDemo {
public static void main(String[] args) {
// 3つの独立したCarオブジェクトを生成
Car car1 = new Car("赤", 0, "プリウス");
Car car2 = new Car("青", 0, "アクア");
Car car3 = new Car("白", 0, "ヤリス");
// それぞれ独立して操作可能
car1.accelerate(50);
car2.accelerate(30);
car3.accelerate(40);
System.out.println("car1の速度: " + car1.speed); // 50
System.out.println("car2の速度: " + car2.speed); // 30
System.out.println("car3の速度: " + car3.speed); // 40
}
}
|
thisキーワードの使い方#
thisキーワードは、現在のオブジェクト自身を参照するための予約語です。
thisの主な用途#
| 用途 |
説明 |
| フィールドと引数の区別 |
同名のフィールドと引数を区別する |
| 同一クラスのコンストラクタ呼び出し |
オーバーロードされたコンストラクタを呼び出す |
| 自分自身を返す |
メソッドチェーンを実現する |
フィールドと引数の区別#
コンストラクタやメソッドの引数名がフィールド名と同じ場合、thisで明示的に区別します。
1
2
3
4
5
6
7
8
9
10
11
12
|
public class Car {
String color;
int speed;
String modelName;
public Car(String color, int speed, String modelName) {
// thisをつけることでフィールドを指定
this.color = color; // this.color = フィールド、color = 引数
this.speed = speed;
this.modelName = modelName;
}
}
|
thisをつけない場合、引数(ローカル変数)が優先されるため、フィールドに値が設定されません。
1
2
3
4
5
6
|
// 悪い例:thisを使わない場合
public Car(String color, int speed, String modelName) {
color = color; // 引数に引数を代入している(意味なし)
speed = speed;
modelName = modelName;
}
|
他のコンストラクタの呼び出し#
this()を使用して、同じクラスの別のコンストラクタを呼び出せます。コードの重複を避けられます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public class Car {
String color;
int speed;
String modelName;
// 全引数コンストラクタ
public Car(String color, int speed, String modelName) {
this.color = color;
this.speed = speed;
this.modelName = modelName;
}
// モデル名のみ指定(他はthis()で委譲)
public Car(String modelName) {
this("白", 0, modelName); // 全引数コンストラクタを呼び出し
}
// 引数なし(デフォルト値を設定)
public Car() {
this("白", 0, "不明"); // 全引数コンストラクタを呼び出し
}
void showInfo() {
System.out.println("モデル: " + modelName + ", 色: " + color + ", 速度: " + speed + " km/h");
}
}
|
this()の呼び出しは、コンストラクタの最初の行に記述する必要があります。
メソッドチェーンの実現#
thisを返すメソッドを定義すると、メソッドを連続して呼び出す「メソッドチェーン」が実現できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public class Car {
String color;
int speed;
String modelName;
public Car(String modelName) {
this.modelName = modelName;
this.color = "白";
this.speed = 0;
}
// thisを返すことでメソッドチェーンを可能にする
Car setColor(String color) {
this.color = color;
return this;
}
Car accelerate(int amount) {
this.speed += amount;
return this;
}
void showInfo() {
System.out.println("モデル: " + modelName + ", 色: " + color + ", 速度: " + speed + " km/h");
}
}
|
1
2
3
4
5
6
7
8
9
10
|
public class MethodChainDemo {
public static void main(String[] args) {
Car myCar = new Car("プリウス")
.setColor("赤")
.accelerate(30)
.accelerate(20);
myCar.showInfo(); // モデル: プリウス, 色: 赤, 速度: 50 km/h
}
}
|
カプセル化とアクセス修飾子#
カプセル化とは、オブジェクトの内部データを外部から隠蔽し、決められた方法でのみアクセスを許可する仕組みです。
なぜカプセル化が必要なのか#
フィールドを直接公開すると、以下の問題が発生します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class BadCar {
public String color;
public int speed;
void showInfo() {
System.out.println("速度: " + speed + " km/h");
}
}
public class Main {
public static void main(String[] args) {
BadCar car = new BadCar();
// 不正な値を直接設定できてしまう
car.speed = -100; // 負の速度
car.speed = 10000; // 現実にはありえない速度
car.showInfo(); // 速度: 10000 km/h
}
}
|
カプセル化によって、不正な値の設定を防ぎ、データの整合性を保つことができます。
アクセス修飾子の種類#
Javaには4種類のアクセス修飾子があります。
| 修飾子 |
同じクラス |
同じパッケージ |
サブクラス |
全クラス |
| private |
○ |
× |
× |
× |
| (なし)パッケージプライベート |
○ |
○ |
× |
× |
| protected |
○ |
○ |
○ |
× |
| public |
○ |
○ |
○ |
○ |
カプセル化の実装手順#
カプセル化は以下の手順で実装します。
- フィールドを
privateにする
- 必要に応じてgetter/setterメソッドを提供する
- setterでバリデーション(値の検証)を行う
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
public class Car {
// フィールドをprivateにして外部から直接アクセスできなくする
private String color;
private int speed;
private String modelName;
public Car(String modelName) {
this.modelName = modelName;
this.color = "白";
this.speed = 0;
}
// getter: フィールドの値を取得する
public String getColor() {
return color;
}
public int getSpeed() {
return speed;
}
public String getModelName() {
return modelName;
}
// setter: フィールドの値を設定する(バリデーション付き)
public void setColor(String color) {
if (color != null && !color.isEmpty()) {
this.color = color;
}
}
public void setSpeed(int speed) {
// 0〜200の範囲のみ許可
if (speed >= 0 && speed <= 200) {
this.speed = speed;
} else {
System.out.println("速度は0〜200の範囲で指定してください");
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class EncapsulationDemo {
public static void main(String[] args) {
Car myCar = new Car("プリウス");
// 正常な値の設定
myCar.setColor("赤");
myCar.setSpeed(60);
System.out.println("色: " + myCar.getColor()); // 色: 赤
System.out.println("速度: " + myCar.getSpeed()); // 速度: 60
// 不正な値は拒否される
myCar.setSpeed(-50); // "速度は0〜200の範囲で指定してください"
myCar.setSpeed(300); // "速度は0〜200の範囲で指定してください"
System.out.println("速度: " + myCar.getSpeed()); // 速度: 60(変更されていない)
}
}
|
getter/setterパターン#
getter/setterは、カプセル化されたフィールドにアクセスするための標準的なパターンです。
getterの命名規則#
1
2
3
4
5
6
7
8
9
|
// 基本形: getフィールド名(先頭大文字)
public 戻り値の型 getフィールド名() {
return フィールド;
}
// boolean型の場合: isフィールド名 または hasフィールド名 も使用可能
public boolean isActive() {
return active;
}
|
setterの命名規則#
1
2
3
4
|
// 基本形: setフィールド名(先頭大文字)
public void setフィールド名(引数) {
this.フィールド = 引数;
}
|
実践的なgetter/setterの例#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
public class User {
private String name;
private int age;
private String email;
private boolean active;
public User(String name) {
this.name = name;
this.active = true;
}
// nameのgetter
public String getName() {
return name;
}
// nameのsetter(nullや空文字を拒否)
public void setName(String name) {
if (name != null && !name.trim().isEmpty()) {
this.name = name.trim();
}
}
// ageのgetter
public int getAge() {
return age;
}
// ageのsetter(0〜150の範囲のみ許可)
public void setAge(int age) {
if (age >= 0 && age <= 150) {
this.age = age;
}
}
// emailのgetter
public String getEmail() {
return email;
}
// emailのsetter(簡易バリデーション)
public void setEmail(String email) {
if (email != null && email.contains("@")) {
this.email = email;
}
}
// boolean型のgetter(isを使用)
public boolean isActive() {
return active;
}
// activeのsetter
public void setActive(boolean active) {
this.active = active;
}
public void showProfile() {
System.out.println("名前: " + name);
System.out.println("年齢: " + age);
System.out.println("メール: " + email);
System.out.println("アクティブ: " + active);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class UserDemo {
public static void main(String[] args) {
User user = new User("田中太郎");
user.setAge(25);
user.setEmail("tanaka@example.com");
user.showProfile();
// 不正な値は設定されない
user.setAge(-5); // 無視される
user.setEmail("invalid"); // @がないため無視される
System.out.println("\n--- 不正な値を設定後 ---");
user.showProfile(); // 値は変わっていない
}
}
|
実行結果:
名前: 田中太郎
年齢: 25
メール: tanaka@example.com
アクティブ: true
--- 不正な値を設定後 ---
名前: 田中太郎
年齢: 25
メール: tanaka@example.com
アクティブ: true
getter/setterを使わないケース#
すべてのフィールドにgetter/setterを用意する必要はありません。以下のケースでは省略を検討します。
| ケース |
対応 |
| 読み取り専用フィールド |
getterのみ提供 |
| 内部でのみ使用するフィールド |
getter/setter両方不要 |
| 不変オブジェクト(Immutable) |
setterを提供しない |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 不変オブジェクトの例
public final class ImmutableUser {
private final String name;
private final int age;
public ImmutableUser(String name, int age) {
this.name = name;
this.age = age;
}
// getterのみ提供(setterは提供しない)
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
|
まとめ#
この記事では、Javaのオブジェクト指向プログラミングの基礎となる「クラスとオブジェクト」について解説しました。
学習ポイントの振り返り#
| 概念 |
ポイント |
| クラス |
オブジェクトの設計図。フィールドとメソッドを持つ |
| オブジェクト |
クラスから生成された実体。new演算子で生成 |
| フィールド |
オブジェクトの状態(データ)を保持する変数 |
| メソッド |
オブジェクトの動作(処理)を定義する |
| コンストラクタ |
オブジェクト生成時に呼ばれる初期化処理 |
| this |
現在のオブジェクト自身への参照 |
| カプセル化 |
privateでデータを隠蔽し、getter/setterで制御 |
次のステップ#
オブジェクト指向の基礎を理解したら、次は以下の概念を学習しましょう。
- 継承(Inheritance): 既存クラスの機能を引き継いで新しいクラスを作成する
- ポリモーフィズム(Polymorphism): 同じメソッド呼び出しでも異なる動作をさせる
- 抽象クラスとインターフェース: より柔軟な設計を実現する
クラスとオブジェクトの理解は、Javaプログラミングのすべての土台となります。実際にコードを書いて、さまざまなクラスを設計することで、オブジェクト指向の考え方を身につけていきましょう。
参考リンク#