大半のインスタンスの共通メソッド
以下のメソッドは利用機会の多いクラスの上位クラス
であり、特にObject
クラスは全てのクラスの上位クラス
であるため、
ユーザ定義クラス
を実装する場合は基本的にオーバーライド
が推奨される。
メソッド | 内容 | 関連クラス |
---|---|---|
toString() |
String型 への変換 |
Object |
equals() |
等価 判定 |
Object |
hashCode() |
ハッシュ値 の取得 |
Object |
clone() |
オブジェクト の複製 |
Object , Cloneable
|
compareTo() |
大小関係 の判定 |
Comparable |
equals()メソッドとhashCode()メソッド
Object.equals()
メソッドはハッシュ値
が等しいオブジェクト
間の等価判定
を行い、
Object.hashCode()
メソッドはオブジェクト
のハッシュ値
の生成を行う。
定義
// オブジェクト間の等価判定
boolean Object.equals(Object obj)
// パラメータ
// obj: 等価判定を行う参照型オブジェクト
// オブジェクトのハッシュ値の生成
int Object.hashCode()
等価(equivalent)と等値(equality)
等価(equals)
は、ポインタ
に格納される値が同じであることを指し、
等値(==)
は、ポインタ
が同じであることを指す。
サンプルコード1(String型の等値判定)
class EquivalentAndEquality {
public static void main(String[] args) {
// 同じ綴りをもつ文字列リテラルは、同一のString型インスタンスを参照
String s1 = "ABC";
String s2 = "ABC";
// 実行時に生成される文字列は、異なるインスタンスとして扱われる
String s3 = new String("ABC");
String s4 = new String("ABC");
// 文字列リテラルで定義したString型インスタンス間の「等価」判定
if (s1.equals(s2)) {
System.out.println("Both strings are equivalent.");
}
else {
System.out.println("Both strings are not equivalent.");
}
// 文字列リテラルで定義したString型インスタンス間の「等値」判定
if (s1 == s2) {
System.out.println("Both strings have equality.");
}
else {
System.out.println("Both strings does not have equality.");
}
// 実行時に生成した文字列(Stringオブジェクト)間の「等価」判定
if (s3.equals(s4)) {
System.out.println("Both strings are equivalent.");
}
else {
System.out.println("Both strings are not equivalent.");
}
// 実行時に生成した文字列(Stringオブジェクト)間の「等値」判定
if (s3 == s4) {
System.out.println("Both strings have equality.");
}
else {
System.out.println("Both strings do not have equality.");
}
}
}
Both strings are equivalent.
Both strings have equality. // 文字列リテラルを代入した場合は「参照先」も同じ
Both strings are equivalent.
Both strings do not have equality. // 実行時にオブジェクトを生成した場合は「参照先」が異なる
サンプルコード2(配列の等価判定)
import java.util.Arrays;
public class ArraysEquivalent {
public static void main(String[] args) {
int[] a = {1,2,3};
int[] b = {1,2,3};
// 配列間の比較では、Objectクラスのequals()メソッドは「等値」判定を行う
System.out.println("Equality: " + a.equals(b));
// 配列間で「等価」判定を行う場合は、Arraysクラスのequals()メソッドを用いる
System.out.println("Equivalence: " + Arrays.equals(a, b));
}
}
コレクション(Collection)と配列(Arrays)
コレクション
は新しい要素が追加されるたびにメモリ領域
を確保し、
配列
は生成時にメモリ領域
を確保する。
また、コレクション
はリスト(List)
とセット(Set)
に分類され、
リスト
は内部的に順序(Order)
をもつため、値の重複を許しているが、
セット
は内部的に順序
をもたないため、値の重複を許さない。
サンプルコード(HashSetの等価判定)
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class HashSetEquivalent {
public static void main(String[] args) {
// HashSetオブジェクトの生成
Set<A> list = new HashSet<>();
// ユーザ定義クラスのオブジェクトを生成・HashSetオブジェクトへの追加
A a1 = new A("Object A", 1);
list.add(a1);
System.out.println("components: " + list.size());
// ユーザ定義クラスの(等値でない)オブジェクトの再生成・HashSetオブジェクトから削除
a1 = new A("Object A", 1);
// HashSetクラスのremove()メソッドは、以下の手順で等価である構成要素を走査
// 1. 構成要素に対してhashCode()メソッドを呼び出しハッシュ値を生成
// 2. ハッシュ値が同じである構成要素を走査(->ハッシュ値はint型であり比較が容易であるため)
// 3. ハッシュ値が同じである構成要素に対してequals()メソッドで等価判定を実行
// 4. 等価である構成要素をHashSetから削除
list.remove(a1);
System.out.println("components: " + list.size());
}
}
// ユーザ定義クラス
class A {
private String str;
private int n;
// コンストラクタ
public A(String str, int n) {
this.str = str;
this.n = n;
}
// 等価判定
// -> 「等価」の基準を定義するためにオーバーライドする必要がある
@Override
public boolean equals(Object o) {
// ①「等値」であれば「等価」である
if (o == this) return true;
// ②「引数」がnullであればそもそも「等価」でない(=「等価」判定が実施できない)
if (o == null) return false;
// ③「引数」が「クラスA」または「クラスAの下位クラス」でなければそもそも「等価」でない
if (!(o instanceof A)) return false;
// また、「引数」が「クラスAの下位クラス」である場合に備えてアップキャストを行う
A h = (A) o;
// ④String.trim()メソッドを用いて先頭・末尾の「空白文字」を削除し、「等価」でなければfalseを返却
if (!this.str.trim().equals(h.str.trim())) {
return false;
}
// 上記の条件にすべて当てはまらない場合はtrueを返却
return true;
}
// ハッシュ値の生成
// -> 「ハッシュ値」の算出方法を定義するためにオーバーライドする必要がある
@Override
public int hashCode() {
// ユーザ定義の引数をもとにハッシュ値を生成
return Objects.hash(this.str, this.n);
}
}
components: 1
components: 0 // 「等価」判定が正常に行われ、構成要素が削除される
compareTo()メソッド
Comparable<T>
インタフェースのcompareTo()
メソッドを実装することで、自然順序付け(natural ordering)
を定義でき、
Comparator<T>
インタフェースのcompare()
メソッドを実装することで、コンパレータ(comparator)
を作成することができる。
自然順序付け
やコンパレータ
によって、大小関係
を比較するインスタンス変数
を定義することができる。
ただし、大小関係
を比較するクラスはComparable<T>
インタフェースの実装クラス
でなければならない。
また、定義したソート基準
に基づいて、Collections
クラスのsort()
メソッドでソート
することができる。
なお、compareTo()
およびcompare()
メソッドのint型
返り値には、以下の規則がある。
値 | 内容 |
---|---|
負 の数 |
自身 < 比較対象 |
0 |
自身 = 比較対象 |
正 の数 |
自身 > 比較対象 |
定義
// 「自然順序付け」をもとに大小関係を比較
int Comparable<T>.compareTo(T o)
// パラメータ
// o: 比較対象となるオブジェクト
// 「コンパレータ」をもとに大小関係を比較
int Comparator<T>.compare(T o1, T o2)
// パラメータ
// o1: 比較対象となる1つ目のオブジェクト
// o2: 比較対象となる2つ目のオブジェクト
// 自然順序によるソート
// <? super T>: クラスTまたはその上位クラス
// -> "?"は「型ワイルドカード」を表し、共変性(covariance)を利用
<T extends Comparable<? super T>> void Collections.sort(List<T> list)
// パラメータ
// list: ソート対象となるListオブジェクト
// コンパレータによるソート
<T> void sort(List<T> list, Comparator<? super T> c)
// パラメータ
// list: 「ソート対象」となるListオブジェクト
// c: 「ソート基準」となるコンパレータオブジェクト
サンプルコード(自然順序付けとコンパレータ)
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class Main {
public static void main(String[] args) {
// ArrayListオブジェクト
List<CompareTo> list = new ArrayList<>();
list.add(new CompareTo(1, 10, 9));
list.add(new CompareTo(3, 7, 11));
list.add(new CompareTo(8, 8, 5));
System.out.println("-- Before --");
for (int i = 0; i < list.size(); i++) {
System.out.print("index " + i + ": ");
System.out.println(list.toArray()[i]);
}
System.out.println();
// ユーザ定義の「自然順序付け」によるソート
Collections.sort(list);
System.out.println("-- By Natural Ordering(num2) --");
for (int i = 0; i < list.size(); i++) {
System.out.print("index " + i + ": ");
System.out.println(list.toArray()[i]);
}
System.out.println();
// "num1"プロパティの「コンパレータ」によるソート
Collections.sort(list, new Num1Comparator());
System.out.println("-- By num1 --");
for (int i = 0; i < list.size(); i++) {
System.out.print("index " + i + ": ");
System.out.println(list.toArray()[i]);
}
System.out.println();
// "num3"プロパティの「コンパレータ」によるソート
Collections.sort(list, new Num3Comparator());
System.out.println("-- By num3 --");
for (int i = 0; i < list.size(); i++) {
System.out.print("index " + i + ": ");
System.out.println(list.toArray()[i]);
}
}
}
// "num1"プロパティの値をもとに順序付けを行うコンパレータ
// -> Comparator<T>インタフェースの実装クラス
class Num1Comparator implements Comparator<CompareTo> {
// Comparator<T>インタフェースのcompare()メソッドをオーバーライド
public int compare(CompareTo x, CompareTo y) {
return (x.getNum1() - y.getNum1());
}
}
// "num2"プロパティの値をもとに順序付けを行うコンパレータ
class Num3Comparator implements Comparator<CompareTo> {
// Comparator<T>インタフェースのcompare()メソッドをオーバーライド
public int compare(CompareTo x, CompareTo y) {
return (x.getNum3() - y.getNum3());
}
}
// 大小関係を比較するクラス
// -> オブジェクトの大小関係を比較する場合、Comparable<T>インタフェースを実装し、
// compareTo()メソッドをオーバーライドすることで「自然順序付け」を定義する必要がある。
// <- Comparable<T>の型は"実装クラス名"を指定
class CompareTo implements Comparable<CompareTo> {
private int num1;
private int num2;
private int num3;
// コンストラクタ
public CompareTo(int num1, int num2, int num3) {
this.num1 = num1;
this.num2 = num2;
this.num3 = num3;
}
// ゲッタ(=アクセサ)
public int getNum1() {
return num1;
}
// ゲッタ(=アクセサ)
public int getNum3() {
return num3;
}
// toString()メソッドのオーバーライドによる出力形式の指定
@Override
public String toString() {
return ( "CompareTo(" + num1 + ", " + num2 + ", " + num3 + ")" );
}
// 自然順序付け
// -> Comparable<T>インタフェースのcompareTo()メソッドをオーバーライド
// => "num2"プロパティの値をもとに自然順序付けが行われる
@Override
public int compareTo(CompareTo obj) {
if (this.num2 < obj.num2) {
return -1;
}
else if (this.num2 > obj.num2) {
return 1;
}
// 比較対象の方が 大きくない かつ 小さくない(=「等価」である)場合
return 0;
}
}
-- Before --
index 0: CompareTo(1, 10, 9)
index 1: CompareTo(3, 7, 11)
index 2: CompareTo(8, 8, 5)
-- By Natural Ordering(num2) -- // "num2"プロパティの値をもとに昇順ソート
index 0: CompareTo(3, 7, 11)
index 1: CompareTo(8, 8, 5)
index 2: CompareTo(1, 10, 9)
-- By num1 -- // "num1"プロパティの値をもとに昇順ソート
index 0: CompareTo(1, 10, 9)
index 1: CompareTo(3, 7, 11)
index 2: CompareTo(8, 8, 5)
-- By num3 -- // "num3"プロパティの値をもとに昇順ソート
index 0: CompareTo(8, 8, 5)
index 1: CompareTo(1, 10, 9)
index 2: CompareTo(3, 7, 11)
equals()メソッドとcompareTo()メソッドの一貫性
等価
である2つのオブジェクト
について、等価判定
を行うequals()
メソッドの実行結果と大小関係比較
を行うcompareTo()
メソッドの実行結果は常に一致していることが望ましい。(=equalsとcompareToの一貫性
)
そのため、一貫性
のないBigInteger
やBigDecimal
クラスは、利用にあたって注意が必要となる。
clone()メソッド
マーカーインタフェース(marker interface)
であるCloneable
インタフェースの実装クラス
で
clone()
メソッドをオーバーライド
することで、インスタンス
の複製
が可能になる。
2種類の複製
複製
には、以下の2種類が存在する。
種類 | 内容 |
---|---|
浅い複製(shallow copy) |
インスタンス のみを複製 |
深い複製(deep copy) |
参照先インスタンス を含めた複製 |
サンプルコード(インスタンスの複製)
public class CopyInstance {
public static void main(String[] args) {
// 複製元オブジェクト
Y b1 = new Y("b1", 1, new X("a1"));
// 複製先オブジェクト
Y b2 = b1.clone();
System.out.println("-- Before --");
System.out.println("b1: " + b1);
System.out.println("b2: " + b2);
System.out.println();
// 複製先オブジェクトの値の書き換え
b2.setY("b2", 2, new X("a2"));
System.out.println("-- After --");
System.out.println("b1: " + b1);
System.out.println("b2: " + b2);
}
}
// インスタンスの複製を行うクラス
// -> Cloneableインタフェースの実装クラス
class X implements Cloneable {
private String str;
// デフォルトコンストラクタ
public X() {}
// コンストラクタ
public X(String str) {
this.str = str;
}
public String getStr() {
return str;
}
// 深い複製(deep copy)を行うclone()メソッドの定義
// -> clone()メソッドのオーバーライド(※Cloneableインタフェースでは未宣言(=マーカーインタフェース))
@Override
public X clone() {
X a = new X();
a.str = this.str;
return a;
}
}
// インスタンスの複製を行うクラス
// -> Cloneableインタフェースの実装クラス
class Y implements Cloneable {
// 参照型(特殊)プロパティ
private String str;
// 値型プロパティ
private int n;
// 参照型(一般)プロパティ
// -> 深い複製を行う場合は、参照先プロパティの値を複製する必要がある
private X aObj;
// デフォルトコンストラクタ
public Y() {}
// コンストラクタ
public Y(String str, int n, X aObj) {
this.str = str;
this.n = n;
this.aObj = aObj;
}
// セッタ(=アクセサ)
public void setY(String str, int n, X aObj) {
this.str = str;
this.n = n;
this.aObj = aObj;
}
@Override
public String toString() {
return ( "Y(" + str + ", " + n + ", " + aObj.getStr() + ")" );
}
// 深い複製(deep copy)を行うclone()メソッドの定義
@Override
public Y clone() {
Y b = new Y();
b.str = this.str;
b.n = this.n;
// 深い複製(deep copy)
// -> 参照先インスタンスを複製
b.aObj = this.aObj.clone();
return b;
}
}
-- Before --
b1: Y(b1, 1, a1)
b2: Y(b1, 1, a1)
-- After --
b1: Y(b1, 1, a1) // 複製先オブジェクト(b2)を書き換えても値はそのまま
b2: Y(b2, 2, a2)
用語集
用語 | 内容 |
---|---|
自然順序付け(natural ordering) | 任意のクラスにおいて、一般的に想定されるソート順 。 |
コンパレータ(comparator) |
ソート基準 を定義する、Comparator インタフェースの実装クラス 。 |
マーカーインタフェース(marker interface) |
メソッド やフィールド が定義されていないインタフェース 。 |