オブジェクト指向の全体像
オブジェクト指向は「もの(オブジェクト)に状態と振る舞いを持たせ、役割ごとに分けて協調させる」考え方です。ものの設計図がクラスで、実体がオブジェクト。複雑さは“責務の分割”で小さくし、拡張は“型を増やす”ことで安全に実現します。Java はこの設計に最適化されていて、壊れにくさ(整合性)と変化への強さ(拡張容易性)を両立できます。
4つの柱(重要ポイントの深掘り)
カプセル化(中身を守り、安全な窓口だけ公開)
フィールドを private にし、検証付きメソッド経由でだけ状態を変えます。整合性が崩れない“安全な通路”を作るのが要点です。
public final class BankAccount {
private int balance;
public BankAccount(int initial) {
if (initial < 0) throw new IllegalArgumentException("negative");
balance = initial;
}
public int balance() { return balance; }
public void deposit(int amount) {
if (amount <= 0) throw new IllegalArgumentException("amount>0");
balance += amount;
}
public boolean withdraw(int amount) {
if (amount <= 0 || amount > balance) return false;
balance -= amount;
return true;
}
}
Java「直接代入禁止」「検証つき窓口」の二点で、壊れないオブジェクトに育てます。
抽象化(本質だけを約束して、実装は隠す)
“できること”をインターフェースで約束し、具体手段は実装に任せます。差し替えとテストが容易になります。
public interface PaymentGateway { boolean charge(int amount, String token); }
public final class StripeGateway implements PaymentGateway {
@Override public boolean charge(int amount, String token) { /* 実装 */ return true; }
}
public final class FakeGateway implements PaymentGateway {
@Override public boolean charge(int amount, String token) { return amount < 10_000; }
}
Java呼び手は「課金できるか」だけに集中し、裏側の手段に縛られません。
継承(共通を上にまとめ、差分だけ持つ)
共通の性質・振る舞いが自然に一致するなら親子でまとめます。ただし“安易な共通化”は複雑化の元。まずは妥当性を見極めます。
public abstract class Shape { public abstract double area(); }
public final class Circle extends Shape {
private final double r; public Circle(double r) { this.r = r; }
@Override public double area() { return Math.PI * r * r; }
}
public final class Rect extends Shape {
private final double w, h; public Rect(double w, double h) { this.w = w; this.h = h; }
@Override public double area() { return w * h; }
}
Java「同じメソッドを、型ごとに適切に実装できる」場面に絞って使います。
多態性(同じ呼び出しが、型ごとに正しく切り替わる)
呼び手は area() を呼ぶだけ。どの型でも“その型らしい”結果が返ります。分岐の嵐を型へ追い出し、拡張は“型を増やす”で完結させます。
Shape s = new Circle(10);
System.out.println(s.area()); // Circle版
s = new Rect(3, 4);
System.out.println(s.area()); // Rect版
Javaクラスとオブジェクトの基本(例題で理解する)
設計図(クラス)と実体(オブジェクト)
クラスに「整合性ルール」を封じ、オブジェクトはルールに守られて動きます。
public final class User {
private final String id;
private String name;
public User(String id, String name) {
if (id == null || id.isBlank()) throw new IllegalArgumentException("id");
this.id = id;
this.name = name == null ? "" : name.trim();
}
public String id() { return id; }
public String name() { return name; }
public void rename(String newName) {
if (newName == null || newName.isBlank()) return;
name = newName.trim();
}
}
User u = new User("U-1", " Taro ");
u.rename("Taro Yamada");
System.out.println(u.name()); // 整形済み
Java協調(役割ごとの小さなオブジェクトが連携)
責務を分けるほど読みやすく、変更に強くなります。
public record Item(String name, int price) {}
public final class Cart {
private final java.util.List<Item> items = new java.util.ArrayList<>();
public void add(Item it) { if (it != null) items.add(it); }
public int total() { return items.stream().mapToInt(Item::price).sum(); }
}
var cart = new Cart();
cart.add(new Item("Pen", 120));
cart.add(new Item("Note", 350));
System.out.println(cart.total()); // 470
Java設計の型と実践のコツ
責務を一つに絞る(単一責務)
クラスは「一つの理由でだけ変わる」ように設計。入力検証・計算・I/O を混在させないことで、テストが容易になります。
不変を好む(変更は新インスタンス)
副作用を減らし、並行性やテストに強くします。
public record Money(int amount, String currency) {
public Money add(int delta) { return new Money(amount + delta, currency); }
}
Java委譲で柔軟に(継承に頼りすぎない)
インターフェースで契約を表し、実装は注入して差し替えられるように。
public interface Discount { int apply(int subtotal); }
public final class RateDiscount implements Discount {
private final double rate; public RateDiscount(double rate) { this.rate = rate; }
@Override public int apply(int subtotal) { return (int) Math.round(subtotal * (1 - rate)); }
}
public final class OrderService {
private final Discount discount; public OrderService(Discount discount) { this.discount = discount; }
public int total(int subtotal) { return discount.apply(subtotal); }
}
Java例題で体験するオブジェクト指向
例 1: 図形の面積(多態性で拡張に強く)
public interface Shape2 { double area(); }
public record Circle2(double r) implements Shape2 { public double area() { return Math.PI * r * r; } }
public record Rect2(double w, double h) implements Shape2 { public double area() { return w * h; } }
public final class ShapePrinter {
public static void print(Shape2 s) { System.out.printf("area=%.2f%n", s.area()); }
}
ShapePrinter.print(new Circle2(10));
ShapePrinter.print(new Rect2(3, 4));
Java新しい図形を足しても ShapePrinter は変更不要。分岐を型に追い出した成果です。
例 2: 口座と取引(カプセル化+型で安全化)
public final class Account {
private int balance;
public Account(int initial) { if (initial < 0) throw new IllegalArgumentException(); balance = initial; }
public int balance() { return balance; }
public void apply(Transaction tx) { balance = tx.applyTo(balance); }
}
public sealed interface Transaction permits DepositTx, WithdrawTx { int applyTo(int balance); }
public record DepositTx(int amount) implements Transaction {
public int applyTo(int balance) {
if (amount <= 0) throw new IllegalArgumentException(); return balance + amount;
}
}
public record WithdrawTx(int amount) implements Transaction {
public int applyTo(int balance) {
if (amount <= 0 || amount > balance) throw new IllegalArgumentException(); return balance - amount;
}
}
var acc = new Account(1000);
acc.apply(new DepositTx(300));
acc.apply(new WithdrawTx(500));
System.out.println(acc.balance()); // 800
Java「取引の整合性」を型に閉じ込め、Account は適用だけに集中できます。
仕上げのアドバイス(重要部分のまとめ)
オブジェクト指向は「ものの性質と振る舞いを責務で分け、協調させる」設計術。カプセル化で整合性を守り、抽象化で契約を明確にし、継承は慎重に、ポリモーフィズムで分岐を型へ追い出す。クラスは一責務に絞り、不変を好み、委譲で柔軟に差し替える——この型が身につくと、読みやすく変更に強いコードに必ず近づきます。

