Javaの同一性と同値性をわかりやすく解説!初心者向けの基礎知識
生徒
「Javaで2つのオブジェクトが同じかどうかを判定したいのですが、どうすればよいですか?」
先生
「それはJavaの同一性と同値性を理解することが重要です。それぞれについて詳しく説明しますね。」
生徒
「同一性と同値性ですか?違いがよくわからないです。」
先生
「大丈夫ですよ!同一性はオブジェクトの参照が同じかどうか、同値性はオブジェクトの値が等しいかどうかを表します。これから詳しく説明していきますね。」
1. Javaにおける同一性とは?
Javaで「同一性」とは、2つの変数が同じインスタンスを参照していることを意味します。同一性の判定には==演算子を使用します。
例: 同一性を確認するコード
public class IdentityExample {
public static void main(String[] args) {
Object obj1 = new Object();
Object obj2 = obj1; // obj1の参照をobj2にコピー
System.out.println(obj1 == obj2); // trueが出力される
}
}
このコードでは、obj1とobj2は同じオブジェクトを参照しているため、==の結果はtrueになります。
2. 問題のコード解説
以下のコードを確認してみましょう。
public class Main {
public static void main(String[] args) {
Sample obj1 = new Sample(10);
Sample obj2 = obj1;
obj1 = new Sample(10);
System.out.println(obj1 == obj2);
}
}
このコードでは、以下のように処理が進行します:
Sample型の新しいインスタンスを作成し、その参照をobj1に代入します。obj1の参照をobj2にコピーします。Sample型の新しいインスタンスを作成し、その参照をobj1に代入します。
その結果、obj1とobj2は異なるインスタンスを参照しているため、==の結果はfalseになります。
3. 同一性と同値性の違い
Javaでは、同一性と同値性は以下のように区別されます:
- 同一性: 2つの変数が同じオブジェクトを参照しているかどうか(
==で判定)。 - 同値性: 2つのオブジェクトの値が等しいかどうか(
equals()メソッドで判定)。
例: 同一性と同値性の違いを確認するコード
public class EqualityExample {
public static void main(String[] args) {
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2); // false: 異なるインスタンスを参照
System.out.println(str1.equals(str2)); // true: 値が等しい
}
}
この例では、str1とstr2は異なるインスタンスですが、値が同じためequals()の結果はtrueになります。
4. 注意点とコツ
- オブジェクトの比較方法を理解する:オブジェクトの参照を比較する場合は
==、値を比較する場合はequals()を使う。 - 意図的な設計を:カスタムクラスで値の比較を行いたい場合、
equals()メソッドをオーバーライドする。 - デバッグの際のチェック:参照の比較と値の比較を混同しないように注意。
5. 実践課題
次のコードを試して、同一性と同値性の違いを確認してください:
public class Practice {
public static void main(String[] args) {
Sample obj1 = new Sample(20);
Sample obj2 = obj1;
obj1 = new Sample(20);
System.out.println(obj1 == obj2); // 結果は?
}
}
このコードの実行結果を確認して、obj1とobj2が参照しているオブジェクトを理解しましょう。
6. メモリイメージで見る同一性と同値性
同一性と同値性の違いを理解するためには、コンピュータのメモリの中で何が起きているかを知るのが近道です。Javaでは、変数にはオブジェクトそのものではなく、オブジェクトが置かれている場所(住所)である「参照」が格納されています。
プログラミング未経験の方でも、以下の図解コードでイメージを掴んでみましょう。
public class MemoryVisualizer {
public static void main(String[] args) {
// インスタンスAを作成(住所: 0x100)
String a = new String("Java");
// インスタンスBを作成(住所: 0x200、中身は同じ)
String b = new String("Java");
// == は「住所(0x100と0x200)」を比較している
System.out.println(a == b);
// equals は「中身("Java")」を比較している
System.out.println(a.equals(b));
}
}
実行結果:
false
true
このように、==演算子は「同じ箱を見ているか」を確認し、equals()メソッドは「箱の中身が同じか」を確認していると考えると分かりやすくなります。
7. 文字列リテラルとStringの特殊な挙動
Javaには、メモリを節約するための「文字列リテラル(String Pool)」という仕組みがあります。newを使わずに文字列を作成した場合、同じ文字列は同じ参照(住所)を共有することがあります。
これが原因で、==を使っても偶然 true になってしまうケースがあり、初心者が混乱するポイントです。
public class StringPoolExample {
public static void main(String[] args) {
String s1 = "Hello"; // String Poolに作成
String s2 = "Hello"; // Pool内の同じ参照を再利用
System.out.println(s1 == s2); // 参照が同じなので true になる!
}
}
実行結果:
true
この挙動があるため、Javaでは「文字列の内容を比較したいときは、常に equals() を使う」というルールが徹底されています。リテラルの仕組みに頼って == を使うと、思わぬバグの原因になるため注意しましょう。
8. 同値性の判定ロジックを自作する
自分で作成したクラス(カスタムクラス)で equals() を使う場合、初期状態では == と同じ動き(同一性の比較)しかしてくれません。「どの項目が一致したら同じとみなすか」を自分で定義する必要があります。
以下の例では、IDが同じであれば同じユーザーであると判定するロジックを組んでいます。
class User {
int id;
User(int id) { this.id = id; }
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 同一なら即座にtrue
if (obj instanceof User) {
User other = (User) obj;
return this.id == other.id; // IDが一致すれば同値とみなす
}
return false;
}
}
public class CustomEquality {
public static void main(String[] args) {
User u1 = new User(123);
User u2 = new User(123);
System.out.println("同一性(==): " + (u1 == u2));
System.out.println("同値性(equals): " + u1.equals(u2));
}
}
実行結果:
同一性(==): false
同値性(equals): true
このように、プログラムの目的に合わせて「同値」の定義をカスタマイズできるのがJavaの柔軟な点です。実務では、この equals() の設計が非常に重要になります。
まとめ
Javaの同一性と同値性について学びました。同一性は==演算子を使って、2つの変数が同じオブジェクトを参照しているかを確認します。一方、同値性はequals()メソッドを用いて、オブジェクトの値が等しいかを確認します。これらを理解することで、コードの意図を正確に反映させることが可能になります。また、参照の変更や新しいインスタンスの生成が、結果にどのように影響を与えるのかを明確に理解することが大切です。
サンプルコード: 同一性と同値性の再確認
public class SummaryExample {
public static void main(String[] args) {
String value1 = "Java";
String value2 = new String("Java");
System.out.println(value1 == value2); // false: 異なる参照
System.out.println(value1.equals(value2)); // true: 同じ値
}
}
このコードでは、リテラルと新しいインスタンスを比較しています。==では異なる参照を持つためfalseを返し、equals()では値が同じであるためtrueを返します。
生徒
「同一性と同値性について、やっと理解できました!でも、コードを書くときにいつも混乱してしまいそうです。」
先生
「最初は混乱するかもしれませんが、基本を押さえれば大丈夫です。同一性は参照、同値性は値の比較と覚えておくと簡単ですよ。」
生徒
「これからは、コードを書くときに==とequals()の使い分けを意識してみます!」
先生
「素晴らしい意識ですね!それに加えて、カスタムクラスで値の比較が必要な場合はequals()メソッドをオーバーライドするのも覚えておいてください。」