「クラスって変数とメソッドをまとめただけ?」「インスタンスとオブジェクトって何が違うの?」——Javaを学び始めると、こういった疑問が次々と湧いてきますよね。
実は、クラスこそがJavaの核心です。クラスを理解できると、「オブジェクト指向」という考え方が一気にクリアになり、より大規模なプログラムが書けるようになります。
この記事では、IT研修の現場で初学者に繰り返し伝えてきた視点をもとに、「なぜクラスが便利なのか」「Javaでの定義・利用方法」「現場で必ず押さえておきたいポイント」まで、ていねいに解説します。具体的なコード例も豊富に用意したので、ぜひ手を動かしながら読んでみてください!
目次
1.クラスって何が便利なの?
①データとメソッドを「ひとまとめ」にできる
これまでに学んだ「変数」は1種類のデータを入れる箱でした。でも現実のプログラムでは、複数のデータをまとめて管理したい場面がたくさんあります。
たとえば「社員情報」を管理したいとき、変数だけだとこうなります。
// ❌ 変数だけで社員情報を管理しようとすると…
String employeeName1 = "田中";
int employeeAge1 = 28;
String employeeDept1 = "開発部";
String employeeName2 = "鈴木";
int employeeAge2 = 35;
String employeeDept2 = "営業部";
// 社員が増えるたびに変数が爆発的に増える…
クラスを使うと、「社員」というひとまとまりの型を自分で定義できます。これが「ユーザー定義型」の考え方です。
// ✅ クラスを使うと社員情報がひとまとまりに
class Employee {
String name; // フィールド(属性)
int age;
String dept;
void work() { // メソッド(操作)
System.out.println(name + "が仕事をしています");
}
double getSalary() {
return age * 1000.0; // 仮の給与計算
}
}
クラスには「フィールド(属性)」と「メソッド(操作)」の両方を定義できるのがポイントです。データとそのデータに関する処理がセットになっているので、管理がとても楽になります。
②現実世界の「もの」をプログラムで表現できる
オブジェクト指向の考え方の根っこは、「現実世界に存在するものをそのままプログラムで表現しよう」というアイデアです。
たとえば「銀行口座」を考えてみましょう。現実の銀行口座には「残高(属性)」があり、「入金する・出金する(操作)」という行為ができます。これをそのままクラスで表現できます。
class Account {
String owner; // 口座名義(属性)
int balance; // 残高(属性)
void deposit(int amount) { // 入金する(操作)
balance += amount;
System.out.println(amount + "円入金しました。残高:" + balance + "円");
}
void withdraw(int amount) { // 出金する(操作)
if (balance >= amount) {
balance -= amount;
System.out.println(amount + "円出金しました。残高:" + balance + "円");
} else {
System.out.println("残高不足です");
}
}
}
このように、現実の「もの」の特徴(属性)と「できること」(操作)をそのままクラスに落とし込めます。これが「オブジェクト指向」という設計思想の本質です。
他にも「顧客クラス」「商品クラス」「注文クラス」など、業務システムの主要な概念がそのままクラスとして表現できます。
③「設計図」として何度でも使い回せる
クラスは「設計図(ひな形)」です。設計図そのものはメモリを使いませんが、設計図をもとに「実体(インスタンス)」を何個でも作ることができます。
家の設計図に例えると——同じ設計図から何棟でも家を建てられますよね。それぞれの家には違う人が住んでいて、違う家具が置いてある。クラスとインスタンスの関係もまったく同じです。
// Accountクラス(設計図)から複数のインスタンスを生成
Account account1 = new Account(); // 田中さんの口座
account1.owner = "田中";
account1.balance = 50000;
Account account2 = new Account(); // 鈴木さんの口座
account2.owner = "鈴木";
account2.balance = 120000;
// それぞれ独立したインスタンスとして存在する
account1.deposit(10000); // 田中さんの残高が変わる
account2.withdraw(5000); // 鈴木さんの残高が変わる(田中さんには無関係)
1つのクラス定義から、何個でも独立したインスタンスを作れる。これがクラスの最大の「使い回し」のメリットです。
2.クラスを定義するって?利用するって?
①クラスの定義:フィールドとメソッドをまとめる基本構文
Javaでクラスを定義する基本構文はこうです。
class クラス名 {
// フィールド(属性:データを保持する変数)
データ型 フィールド名;
// メソッド(操作:処理を定義する)
戻り値の型 メソッド名(引数) {
// 処理内容
}
}
具体例として「点(Point)クラス」を作ってみましょう。
class Point {
// フィールド(座標の属性)
int x;
int y;
// メソッド(2点間の距離を返す操作)
double distanceTo(Point other) {
int dx = this.x - other.x;
int dy = this.y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
// メソッド(座標を表示する操作)
void display() {
System.out.println("(" + x + ", " + y + ")");
}
}
ここで登場した this キーワードは「このインスタンス自身」を指します。this.x は「このインスタンスのフィールド x」という意味です。引数名とフィールド名が同じになるときなどに特に活躍します。
②クラスの利用:newでインスタンス化して「.」でアクセスする
クラス(設計図)を定義しただけでは何も起きません。new 演算子を使ってインスタンスを生成して初めて、メモリ上に実体が作られます。これを「インスタンス化」といいます。
// クラス名 変数名 = new クラス名();
Point p1 = new Point();
// フィールドへのアクセスは「.(ドット)」を使う
p1.x = 3;
p1.y = 4;
// メソッドの呼び出しも「.(ドット)」を使う
p1.display(); // → (3, 4)
// 別のインスタンスも生成してみる
Point p2 = new Point();
p2.x = 0;
p2.y = 0;
// p1からp2への距離を計算
double dist = p1.distanceTo(p2);
System.out.println("距離:" + dist); // → 距離:5.0
new のあとに呼び出している Point() はコンストラクタです(次のセクションで詳しく解説します)。
また、他のクラスのメソッドを呼び出す場合は、インスタンスを生成した上で インスタンス変数.メソッド名() の形でアクセスします。これが「クラスを利用する」ということです。
③アクセス修飾子とカプセル化:ゲッター・セッターで安全に守る
クラスのフィールドを外部から直接書き換えられると、意図しない値が入り込んでバグの原因になります。これを防ぐのがカプセル化(情報隠蔽)という考え方です。
アクセス修飾子を使って、フィールドの公開範囲をコントロールします。
| 修飾子 | アクセス範囲 | 使いどころ |
|---|---|---|
public | どこからでもアクセス可能 | 外部に公開したいメソッドに使う |
private | 同じクラス内からのみアクセス可能 | 直接触られたくないフィールドに使う |
| (なし) | 同じパッケージ内からアクセス可能 | パッケージ内での共有に使う |
カプセル化の実装パターンは「フィールドはprivate、アクセスはpublicなゲッター・セッター経由」です。
class Account {
private String owner; // ← privateにして外部から直接触れないようにする
private int balance;
// ゲッター(値を取得するメソッド)
public String getOwner() {
return owner;
}
public int getBalance() {
return balance;
}
// セッター(値を設定するメソッド)
public void setOwner(String owner) {
this.owner = owner;
}
public void setBalance(int balance) {
if (balance >= 0) { // ← 不正な値を弾くチェックを入れられる!
this.balance = balance;
} else {
System.out.println("残高に負の値は設定できません");
}
}
}
// 利用する側
Account act = new Account();
act.setOwner("田中");
act.setBalance(50000);
// act.balance = -9999; ← ❌ privateなのでコンパイルエラー!直接触れない
System.out.println(act.getBalance()); // ✅ ゲッター経由でアクセス → 50000
セッターの中に「残高が0以上かどうか」のチェックを入れられているのがポイントです。データの整合性をクラス自身が守る——これがカプセル化の真の価値です。
3.クラスについて整理しておきたいこと
①クラス・オブジェクト・インスタンスの言葉の違い
「クラス」「オブジェクト」「インスタンス」——似たような言葉が並んでいて混乱しやすいので、しっかり整理しておきましょう。
| 用語 | 意味 | たとえ話 |
|---|---|---|
| クラス | オブジェクトのひな形(設計図)。値を持たない | 家の設計図 |
| オブジェクト | 「もの」を抽象化して表現した概念。広い意味で使われる | 「家」という概念 |
| インスタンス | クラスをもとにメモリ上に生成された具体的な実体。具体的な値を持つ | 設計図から実際に建てた1棟の家 |
技術的に正確に言うと、クラスの属性(フィールド)は値を持ちません。値を持つのはインスタンスです。設計図に「寝室の広さ:8畳」と書いてあっても、設計図に「実際の8畳の空間」はないですよね。
// Employeeクラス(設計図)は値を持たない
// ↓
Employee emp1 = new Employee(); // インスタンス生成
emp1.name = "田中"; // インスタンスが初めて値を持つ
emp1.age = 28;
Employee emp2 = new Employee(); // 別のインスタンスを生成
emp2.name = "鈴木"; // emp1とは独立した値
emp2.age = 35;
// emp1を変えてもemp2は変わらない(別々の実体)
emp1.age = 29;
System.out.println(emp2.age); // → 35(変わっていない)
なお、「インスタンスをオブジェクトと呼ぶ」のは誤りではありませんが、「オブジェクトをインスタンスと呼ぶ」のは正確ではありません。この微妙なニュアンスも頭に入れておくと、技術的な会話で混乱しなくなります。
②コンストラクタとは:newのタイミングで自動実行される初期化処理
コンストラクタは、new でインスタンスを生成したときに自動的に呼ばれる特別なメソッドです。インスタンスの初期化(初期値の設定など)に使います。
コンストラクタの特徴は次の3つです。
- ✅ クラス名と同じ名前にする
- ✅ 戻り値の型を書かない(voidも書かない)
- ✅
newのタイミングで自動的に呼び出される
class Account {
private String owner;
private int balance;
// コンストラクタ(クラス名と同じ名前、戻り値なし)
public Account(String owner, int initialBalance) {
this.owner = owner;
this.balance = initialBalance;
System.out.println(owner + "の口座を開設しました(残高:" + initialBalance + "円)");
}
}
// 利用する側
Account act = new Account("田中", 50000);
// → 田中の口座を開設しました(残高:50000円)
// インスタンス生成と同時に初期値が設定される!
もしクラスにコンストラクタを定義しなかった場合、Javaが自動的に引数なしのデフォルトコンストラクタを生成してくれます。ただし、自分でコンストラクタを定義した場合はデフォルトコンストラクタは自動生成されないので注意が必要です。
// コンストラクタを定義しなかった場合は、これが自動生成される
// public Account() { } ← デフォルトコンストラクタ
// ただし、引数ありのコンストラクタを定義すると…
// public Account(String owner, int balance) { ... }
// ↑ これを定義した場合、下の呼び出しはエラーになる
Account act = new Account(); // ❌ 引数なしのコンストラクタがない!
③オーバーロードとガーベージコレクションの基礎知識
オーバーロードとは、同じクラス内に同じ名前のメソッドを複数定義することです。引数の数や型が異なれば、同じメソッド名を使えます。
class Calculator {
// 引数が2つのバージョン
static int add(int a, int b) {
return a + b;
}
// 引数が3つのバージョン(オーバーロード)
static int add(int a, int b, int c) {
return a + b + c;
}
// 引数の型がdoubleのバージョン(オーバーロード)
static double add(double a, double b) {
return a + b;
}
}
// 呼び出し側:同じ名前addで、引数によって自動的に適切なメソッドが選ばれる
System.out.println(Calculator.add(1, 2)); // → 3(2引数int版)
System.out.println(Calculator.add(1, 2, 3)); // → 6(3引数int版)
System.out.println(Calculator.add(1.5, 2.5)); // → 4.0(double版)
コンストラクタもオーバーロードできます。初期化パターンを複数用意しておくことで、利用する側の柔軟性が上がります。
次にガーベージコレクションについて。Javaでは、不要になったインスタンスはJVM(Java仮想マシン)が自動的にメモリから解放してくれます。これがガーベージコレクションです。
Account act = new Account("田中", 50000);
act = null; // 参照を切る → このインスタンスはガーベージコレクションの対象になる
act = new Account("鈴木", 30000); // 新しいインスタンスに参照を付け替え
ガーベージコレクションのポイントは「対象になっても、すぐには破棄されない」という点です。破棄されるタイミングはJVMが決めるため、プログラムから制御することはできません。「null を代入したらすぐ消える」と思ってしまう初学者が多いので、ここは注意しておきましょう。
まとめ
今回はJavaの「クラス」について、基礎の基礎から現場で使える知識まで解説しました。最後にポイントを整理しておきます。
| テーマ | ポイント |
|---|---|
| クラスの便利さ | データとメソッドをひとまとめにできる。現実の「もの」をプログラムで表現できる |
| クラスの定義 | class クラス名 { フィールド + メソッド }。this は自分自身のインスタンスを指す |
| インスタンス化 | new 演算子で実体を生成。.(ドット)でフィールドやメソッドにアクセス |
| カプセル化 | フィールドはprivate、アクセスはpublicなゲッター・セッター経由が基本 |
| コンストラクタ | new 時に自動呼び出し。クラス名と同名・戻り値なし。オーバーロードも可能 |
| 用語の整理 | クラス(設計図)→ new → インスタンス(実体)。ガーベージコレクションはJVMが自動管理 |
クラスの概念は、最初は少し抽象的に感じるかもしれません。でも「銀行口座」「社員」「商品」など、身近な”もの”をクラスとして書いてみることが、一番の近道です。
今日のコード例を自分でも書いて動かしてみてください。手を動かすことで、クラスという概念が体に染み込んでいきます。オブジェクト指向の扉は、もうすぐそこです!
次回は「クラスの継承とポリモーフィズム」について解説予定です。お楽しみに!
