Javaにおけるクラスとオブジェクトの概念
Javaのオブジェクト指向プログラミングを理解する第一歩は、設計図である「クラス」と、メモリ上に実際に展開される「インスタンス(オブジェクト)」の違いを明確にすることです。クラスは状態(フィールド)と振る舞い(メソッド)を定義する枠組みであり、オブジェクトはその枠組みに基づいてヒープメモリ上に割り当てられた実体です。
1. クラス定義とフィールドの役割
クラス内部で宣言される変数は「インスタンス変数」と呼ばれ、各オブジェクトが個別に保持するデータを表します。データ型にはプリミティブ型と参照型の2種類があり、メモリ上の扱いが異なります。
設計図の定義例
public class Employee {
// 社員番号(プリミティブ型)
private int empId;
// 氏名(参照型)
private String fullName;
// 所属部署(参照型)
private Department dept;
}
public class Department {
// 部署名
private String deptName;
// 所在地
private String location;
}
2. インスタンス生成と参照型変数の実体
new演算子を実行すると、JVMはヒープ領域にオブジェクト用のメモリ領域を確保し、その開始アドレスを返します。ローカル変数に代入されるのはオブジェクト本体ではなく、そのメモリ番地(参照)です。参照型変数はオブジェクトへの「ハンドル」として機能し、ドット演算子(.)を用いて内部のフィールドにアクセスします。
生成とアクセスのテストコード
public class Main {
public static void main(String[] args) {
// ヒープ上にEmployeeインスタンスを生成し、参照をスタック変数に代入
Employee worker = new Employee();
// 初期値は型に応じたデフォルト値(数値は0、参照型はnull)
System.out.println(worker.empId); // 0
System.out.println(worker.fullName); // null
System.out.println(worker.dept); // null
// フィールドへの値設定
worker.empId = 1024;
worker.fullName = "田中 太郎";
// 関連する別クラスのインスタンスも生成して参照を紐付け
worker.dept = new Department();
worker.dept.deptName = "開発本部";
worker.dept.location = "東京オフィス";
// 参照チェインによる階層アクセス
System.out.println(worker.fullName + "の所属:" + worker.dept.deptName);
}
}
※ インスタンス変数はクラス名を直接指定してアクセスできません。必ず具体的なインスタンス参照を介して操作する必要があります。
3. 参照の代入とメモリ共有の仕組み
参照型変数を別の変数に代入する場合、オブジェクトのディープコピーが行われるわけではなく、単にメモリ番地が複製されます。結果として、複数の変数が同一のオブジェクトを指す状態(エイリアシング)が生成されます。
参照共有の動作確認
public class ReferenceTest {
public static void main(String[] args) {
Department branchA = new Department();
// branchAはヒープ上のDepartmentインスタンスの番地を保持
Employee staff = new Employee();
staff.dept = branchA; // 番地のみがコピーされる
// branchA経由でプロパティを変更
branchA.deptName = "大阪支社";
// staff.deptも同じ番地を指しているため、変更が即時反映される
System.out.println(staff.dept.deptName); // 大阪支社
}
}
4. オブジェクト間の関連関係(Association)
クラス設計において、オブジェクト同士が参照を通じて関連付けられることがあります。双方向の関連を実装する場合は、相互に参照フィールドを持たせる形で構成します。
双方向参照の実装例
public class Manager {
private String mName;
private Team team; // 担当チームへの参照
}
public class Team {
private String tName;
private Manager lead; // 担当マネージャーへの参照
}
public class RelationTest {
public static void main(String[] args) {
Manager m = new Manager();
m.mName = "佐藤";
Team t = new Team();
t.tName = "アルゴリズム班";
// 参照を相互に設定(関連の確立)
m.team = t;
t.lead = m;
// 参照パスを通じて関連先のデータにアクセス
System.out.println(m.mName + "が担当するチーム:" + m.team.tName);
}
}
5. 空参照による実行時エラー(NullPointerException)
参照型変数にnullが代入された状態で、フィールドやメソッドへアクセスしようとすると、Javaランタイムはjava.lang.NullPointerExceptionをスローします。コンパイル段階では型チェックが通過するため構文エラーとはみなされませんが、実行時にオブジェクトが存在しない領域へアクセスしようとするため致命的な例外が発生します。
NULL参照の発生例
public class Item {
private int code;
}
public class NullTest {
public static void main(String[] args) {
Item product = new Item();
System.out.println(product.code); // 0(デフォルト値)
// 参照を明示的に空にする
product = null;
// 実行時にNullPointerExceptionが発生
// コンパイルは正常に終了するが、実体が存在しないためアクセス不能
System.out.println(product.code);
}
}
参照型変数がnullの状態では、ドット演算子によるメンバアクセスはすべて実行時例外へ直結するため、オブジェクトのライフサイクル管理が必須となります。