【Java発展学習1日目】等価と等値の違い、インスタンスの複製

# 大半のインスタンスの共通メソッド
以下のメソッドは利用機会の多いクラスの`上位クラス`であり、特に`Object`クラスは**全てのクラス**の`上位クラス`であるため、
`ユーザ定義クラス`を実装する場合は基本的に`オーバーライド`が推奨される。

|メソッド|内容|関連クラス|
|:--|:--|:--|
|`toString()`|`String型`への変換|`Object`|
|`equals()`|`等価`判定|`Object`|
|`hashCode()`|`ハッシュ値`の取得|`Object`|
|`clone()`|`オブジェクト`の複製|`Object`, `Cloneable`|
|`compareTo()`|`大小関係`の判定|`Comparable`|

***

# equals()メソッドとhashCode()メソッド
`Object.equals()`メソッドは`ハッシュ値`が等しい`オブジェクト`間の`等価判定`を行い、
`Object.hashCode()`メソッドは`オブジェクト`の`ハッシュ値`の生成を行う。

## 定義

```java
// オブジェクト間の等価判定
boolean Object.equals(Object obj)
// パラメータ
// obj: 等価判定を行う参照型オブジェクト

// オブジェクトのハッシュ値の生成
int Object.hashCode()
```

## 等価(equivalent)と等値(equality)
`等価(equals)`は、`ポインタ`に格納される**値が同じ**であることを指し、
`等値(==)`は、`ポインタ`が同じであることを指す。

### サンプルコード1(String型の等値判定)

```java:EquivalentAndEquality.java
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.");
        }
    }
}
```

```java:実行結果
Both strings are equivalent.
Both strings have equality.  // 文字列リテラルを代入した場合は「参照先」も同じ
Both strings are equivalent.
Both strings do not have equality.  // 実行時にオブジェクトを生成した場合は「参照先」が異なる
```

### サンプルコード2(配列の等価判定)

```java:ArraysEquivalent.java
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の等価判定)

```java:HashSetEquivalent.java
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);
    }
}
```

```java:実行結果
components: 1
components: 0  // 「等価」判定が正常に行われ、構成要素が削除される
```

***

# compareTo()メソッド
`Comparable<T>`インタフェースの`compareTo()`メソッドを実装することで、`自然順序付け(natural ordering)`を定義でき、
`Comparator<T>`インタフェースの`compare()`メソッドを実装することで、`コンパレータ(comparator)`を作成することができる。

`自然順序付け`や`コンパレータ`によって、`大小関係`を比較する`インスタンス変数`を定義することができる。
ただし、`大小関係`を比較するクラスは`Comparable<T>`インタフェースの`実装クラス`でなければならない。

また、定義した`ソート基準`に基づいて、`Collections`クラスの`sort()`メソッドで`ソート`することができる。

なお、`compareTo()`および`compare()`メソッドの`int型`返り値には、以下の規則がある。

|値|内容|
|:--|:--|
|`負`の数|`自身 < 比較対象`|
|`0`|`自身 = 比較対象`|
|`正`の数|`自身 > 比較対象`|

## 定義

```java
// 「自然順序付け」をもとに大小関係を比較
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: 「ソート基準」となるコンパレータオブジェクト
```

## サンプルコード(自然順序付けとコンパレータ)

```java:NaturalOrderingAndComparator.java
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;
    }
}
```

```java:実行結果
-- 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)|`参照先インスタンス`を含めた複製|

## サンプルコード(インスタンスの複製)

```java:CopyInstance.java
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;
    }
}
```

```java:実行結果
-- 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)|`メソッド`や`フィールド`が定義されていない`インタフェース`。|