関連の多重度とは何か
「関連の多重度」は、
「あるクラスと別のクラスが、どれくらいの数で結びついているか」
を表す考え方です。
UML だと 1, 0..1, *, 0..*, 1..* みたいな表記を見たことがあるかもしれませんが、
要するに「片方から見たとき、相手は何件なの?」をハッキリさせるためのものです。
Java コードでは、
「1つのフィールドで持つのか」「List や Set で持つのか」「Optional なのか」
といった形で表現します。
1 対 1(1–1): いつも必ず 1 つ
概念のイメージ
ある User に対して、必ず 1 つの Profile がある、といった関係です。
「User が存在するなら、Profile も必ずセットで存在する」が前提になります。
例えば:
- マイページ用のプロフィール
- ユーザごとの設定オブジェクト(Config)
などが、1 対 1 の候補になります。
Java での表現例
final class Profile {
private final String displayName;
Profile(String displayName) {
this.displayName = displayName;
}
String displayName() {
return displayName;
}
}
final class User {
private final String id;
private final Profile profile; // 1対1:必ず1つ持つ
User(String id, Profile profile) {
this.id = id;
this.profile = profile;
}
Profile profile() {
return profile;
}
}
Javaここでは User が Profile を「必ず 1 つ」持ちます。
コンストラクタで必須にしているので、「Profile が null の User」は基本的に作れない設計です。
「1 対 1」にしたいのに、Profile をあとから set したり、null を許してしまうと、
実質「0..1(あってもなくてもいい)」になってしまうので注意が必要です。
0 または 1 対 1(0..1): あってもいいし、なくてもいい
概念のイメージ
User に対して、ある場合だけ存在する何か、という関係です。
例えば:
- ユーザが任意で登録できる「配送先住所」
- 認証情報(まだパスワード未設定のユーザがいる場合など)
のように、「ない状態も普通にあり得る」ものです。
Java での表現例
final class Address {
private final String city;
Address(String city) {
this.city = city;
}
String city() {
return city;
}
}
final class User {
private final String name;
private Address address; // 0..1:あるかもしれないし、ないかもしれない
User(String name) {
this.name = name;
}
void setAddress(Address address) {
this.address = address;
}
Address addressOrNull() {
return address;
}
}
Java多重度 0..1 は、Java 的には
「フィールドは 1 つだけど null を許す」
という形で表現されます。
Java 8 以降なら、戻り値に Optional を使って明示してもいいです。
java.util.Optional<Address> address() {
return java.util.Optional.ofNullable(address);
}
Java重要なのは「この関連は“なくてもOK”なんだ」という前提を、
設計として自覚しておくことです。
1 対 多(1–*): 1 つに対して複数
概念のイメージ
「親 1 件に対して、子が複数ぶら下がる」典型的なパターンです。
例えば:
- 注文(Order)と注文明細(OrderLine)
- ブログ記事(Post)とコメント(Comment)
- 顧客(Customer)と複数の電話番号(PhoneNumber)
などです。
「親が存在するなら、その配下の子は 0 個以上存在しうる」という関係です。
(0 個もアリですが「“多重度”としては 1..* か 0..* か」を文脈で決めます)
Java での表現例
final class Comment {
private final String message;
Comment(String message) {
this.message = message;
}
String message() {
return message;
}
}
final class Post {
private final String title;
private final java.util.List<Comment> comments = new java.util.ArrayList<>();
Post(String title) {
this.title = title;
}
void addComment(String message) {
comments.add(new Comment(message));
}
java.util.List<Comment> commentsView() {
return java.util.List.copyOf(comments); // 外部から直接いじらせない
}
}
Javaここで Post は、「自分に紐づく Comment の一覧」を List として持っています。
これが「1 対 多」の典型的な表現方法です。
0 件の場合もあるので、「多重度」としては厳密に言えば 0..* ですが、
「1 つの Post に対して多くの Comment」なので習慣的には「1 対 多」と呼びます。
多 対 多(–): お互いが複数を持ち合う
概念のイメージ
「ユーザは複数のグループに属し、グループも複数のユーザを持つ」
といった関係です。
例えば:
- ユーザ(User)とグループ(Group)
- 生徒(Student)と講義(Lecture)
- 商品(Product)とカテゴリ(Category)
など、「片側だけでなく両側が“多”」という関係になります。
Java での表現例(シンプル版)
import java.util.HashSet;
import java.util.Set;
final class Group {
private final String name;
private final Set<User> members = new HashSet<>();
Group(String name) {
this.name = name;
}
void addMember(User user) {
members.add(user);
}
Set<User> membersView() {
return Set.copyOf(members);
}
}
final class User {
private final String name;
private final Set<Group> groups = new HashSet<>();
User(String name) {
this.name = name;
}
void join(Group group) {
groups.add(group);
group.addMember(this); // 双方向関連を維持
}
Set<Group> groupsView() {
return Set.copyOf(groups);
}
}
Javaここでは、User も Group も互いに Set で相手の複数を持ちます。
これが「多 対 多」です。
注意点は、双方向に持つと関連の整合性を自分で守らないといけないことです。
上の例では join の中で
groups.add(group);
group.addMember(this);
Javaのように、両方を同時に更新して「片側だけが知っている」状態にならないようにしています。
実務では、多対多をそのままモデリングせず
「中間テーブルに相当する“関連クラス”を作って、それを 1 対 多に分解する」設計をよく使います。
多重度と「null」「コレクション」の関係(重要な深掘り)
多重度の違いは、Java 的には
- ただのフィールド(0..1 or 1)
- コレクション(0..* or 1..*)
で表現しますが、そこに設計の意図をどう乗せるかが重要です。
「必ずある」のか「ないかもしれない」のか
1 と 0..1 の違いは、「存在が必須かどうか」です。
- 必須なら:コンストラクタで受け取って final にする(null 禁止)
- 任意なら:あとから set できるようにする/null を許容する/Optional を使う
というルールを決めておくと、コードを見ただけで多重度の意図が読み取りやすくなります。
// 必須(1)
final class User {
private final Profile profile; // コンストラクタ必須
User(Profile profile) {
this.profile = profile;
}
}
// 任意(0..1)
final class User2 {
private Address address; // あってもなくてもいい
void setAddress(Address address) {
this.address = address;
}
}
Java「0..」か「1..」か
コレクションの場合、「空でもOK」か「最低1件必要」かを考えます。
- 0..*:リストが空でも自然なケース(コメント一覧、検索結果など)
- 1..*:必ず1件以上あるべき(例えば「必ず1商品は入る注文」など)
Java の型としてはどちらも List ですが、
0..* を許すかどうかはビジネスルール側でチェックする必要があります。
final class Order {
private final java.util.List<OrderLine> lines;
Order(java.util.List<OrderLine> lines) {
if (lines == null || lines.isEmpty()) {
throw new IllegalArgumentException("at least 1 line required"); // 1..*
}
this.lines = java.util.List.copyOf(lines);
}
}
Javaこのようにバリデーションで「1..*」を保証すれば、
Order オブジェクトが存在する限り「明細が 0 件」という矛盾状態を防げます。
多重度を意識すると何が嬉しいのか(重要)
設計の「あいまいさ」をなくせる
多重度を意識せずに「なんとなく List」「なんとなく null 許容」にすると、
コードを読む人が「ここ、何件想定なんだろう?」「null あり?」と毎回考えることになります。
多重度を明確にすると、
- ここは必ず 1 件
- ここは 0 件も普通にある
- ここは複数ある前提
- 多 対 多なので、中間の関連クラスを用意するべき
といった判断がしやすくなり、
バグ(特に null 関連・空リスト関連)の混入を防ぎやすくなります。
一貫性ルールやバリデーションの場所が見える
「1..* のつもりなら、コンストラクタで check しよう」
「0..1 なら null と Optional をどう扱うか決めよう」
といったルールが立てやすくなります。
結果として、
- トランザクションの単位
- 集約の境界
- どの方向から更新するか
といった設計がスッキリしていきます。
まとめと、実際のコードへの落とし込み方
関連の多重度は、「クラス同士が何件どう結びつくか」を表す大事な情報です。
1 対 1(必ず 1 つ)
0..1(あってもなくてもいい)
1 対 多(親 1 件に子が複数)
多 対 多(お互いに複数)
これを Java では、
- 単純なフィールド(null あり/なし)
- List や Set などのコレクション
- コンストラクタでのチェックや Optional
を使って表現します。
ポイントは、「多重度の設計意図をコードににじませる」ことです。
コンストラクタの引数、null 許容かどうか、防御的コピー、バリデーション……
こうした部分に「この関連は何対何か」が表れます。
