【Java】Objectクラス

#Objectクラスとは
* クラス宣言の際に**extends句を省略した時に暗黙的に継承されるクラス**
* **全クラスが最終的にObjectクラスをルートにもつ**
* 全クラスでObjectクラスのメソッドが利用できる（必要に応じ派生クラスでOverride）
    * **clone()**：オブジェクトのコピー作成
    * **finalize()**：破棄する時に実行
        * オブジェクト破棄される時にガベージコレクションに呼ばれるメソッド<br>保証されてないので実際のアプリでは使うべきではない
    * **equals(object obj)**：オブジェクトobjと等しいか？
    * **getClass()**：オブジェクトのクラスを取得
    * **hashCode()**：ハッシュコードを取得
    * **toString()**：オブジェクトを文字列で取得

##toSrtingメソッド
* **可能なら全クラスで実装すべき**
* 適切な文字列表現を用意することでテスト時などでオブジェクトの概要確認できる
* `System.out.println(obj);` <br>//printlnは内部的にtoStringメソッド呼ぶ
* **クラスを特徴付けるフィールドを選別して文字列化**するのがポイント（全フィールドを書くのが目的でない）
* 使いやすいようにtoStringで利用した情報（フィールド）は**個別のゲッター用意し取得**できるようにする

```java
//PersonクラスにtoStringメソッドを実装
public class Person {
  private String firstName;
  private String lastName;
  public Person(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  @Override
  //クラスを特徴付けるフィールドを選別し文字列化
  public String toString() {
    return String.format("彼女は、%s %s です。",
        this.lastName, this.firstName);
  }
  //個別のゲッターでも取得できるようにする
  public String getLastName() {
    return this.lastName;
  }
  public String getFirstName() {
    return this.firstName;
  }
}
```

```java
public class ToStringBasic {
  public static void main(String[] args) {
    var p = new Person("Eilish", "Billie");
    System.out.println(p); //彼女は、Billie Eilish です。
  }
}
```

##equalsメソッド
* オブジェクト同士が等しいかどうか**同一性**判定
    * オブジェクトの**参照**が同じかどうか
* 意味の等価判定にはequalメソッドのオーバーライド必要
    * 中身が複雑なので必要時のみ実装しよう

```java
//firstName/lastNameフィールドのPersonクラスにequalsメソッドを実装
public class Person {
  private String firstName;
  private String lastName;

  public Person(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  //姓名共に等しい場合等値
  @Override
  public boolean equals(Object obj) {
    //同一性の判定
    if (this == obj) {
      return true;
    }
    //同値性の判定
    //instanceofで型チェックし型キャスト(ダウンキャスト)
    if (obj instanceof Person) {
      var p  = (Person)obj;
      //フィールドの同値を判定(Stringクラスのequalsメソッド)
      return this.firstName.equals(p.firstName) &&
          this.lastName.equals(p.lastName);
    }
    //引数objの型が違う場合false
    return false;
  }
}
```

###equalsで守るべきルール
* **反射性**：x.equals(x)はtrue
* **対称性**：x.equals(x)がtrueならy.equals(x)はtrue
    * **対称性に違反してしまいがち**なので注意
* **推移性**：x.equals(x)とy.equals(z)がtrueならx.equals(z)はtrue
* **一貫性**：x,yに変更なければ、x.equals(y)を複数呼び出しても常にtrue/false結果は変化しない

```java
//対称性違反になる問題のあるコード
public class BusinessPerson extends Person {
  private String department;

//部署比較追加
  public BusinessPerson(String firstName, String lastName, String department) {
    super(firstName, lastName);
    this.department = department;
  }

  @Override
  public boolean equals(Object obj) {
    //同一性
    if (this == obj) {
      return true;
    }
    //同値性
    if (obj instanceof BusinessPerson) {
      var bp = (BusinessPerson)obj;
      return super.equals(bp) &&
          this.department.equals(bp.department);
    }
    return false;
  }
}
```

```java
public class EqualsBasic {
  public static void main(String[] args) {
    var p = new Person("由実", "松任谷");
    var bp = new BusinessPerson("由実", "松任谷", "作曲家");
    System.out.println(p.equals(bp));  //true
    System.out.println(bp.equals(p));  //false
  }
}
```
* 問題点：
    * Personクラスのequalsメソッドでは**titleフィールドを無視して比較する**のでpとbpは同値
    * BusinessPersonクラスのequalsメソッドでは**pがBusinessPersonでないので同値とみなせない**
    * objにPersonオブジェクトが渡された場合にdepartmentフィールドを無視して比較
    * →対称性違反は解消するが推移性違反になる(委譲を用いることも検討)
    
```java
//推移性違反になる問題のあるコード
public class BusinessPerson extends Person {
  private String department;

  public BusinessPerson(String firstName, String lastName, String department) {
    super(firstName, lastName);
    this.department = department;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }

    if (obj instanceof Person) {
      //BusinessPerson型の場合全フィールドで比較
      if (obj instanceof BusinessPerson) {
        var bp  = (BusinessPerson)obj;
        return super.equals(bp) &&
            this.department.equals(bp.department);
      //Person型ではdepartmentフィールドを無視して比較
      } else {
        return super.equals(obj);
      }
    }
    return false;
  }
}
```
```java
public class EqualsBasic {
  public static void main(String[] args) {
    var p = new Person("由実", "松任谷");
    var bp1 = new BusinessPerson("由実", "松任谷", "作曲家"); 
    var bp2 = new BusinessPerson("由実", "松任谷", "歌手");
    System.out.println(bp1.equals(p)); //true
    System.out.println(p.equals(bp1)); //true
    System.out.println(p.equals(bp2)); //true
    System.out.println(bp1.equals(bp2)); //false 推移性違反！
  }
}
```

##オブジェクトのハッシュ値取得
* **hashCodeメソッド**
* **オブジェクトハッシュ値**（オブジェクトデータを元に生成されたint値）を返す
    * HashMap/HashSetなどのハッシュ表で値を正しく管理するための情報
    * **同値のオブジェクトは同じハッシュ値を返す**
    * equalsメソッドをオーバーライドした場合hashCodeメソッドもオーバーライドする
* クラスが不変ならメソッド呼び出しの度に計算するのではなく**フィールド値としてハッシュ値をキャッシュ**


```java
public class ObjectHash {
  private boolean boolValue;
  private int intValue;
  private long longValue;
  private float floatValue;
  private double doubleValue;
  private String stringValue;
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;

    //ハッシュ値を使って以下の計算
    //hash = result*31^(n) + v_1*31^(n-1) +..+ v_n
    //v_1~v_nはフィールドのint値
    //nはフィールド数
    result = prime * result + (boolValue ? 1231 : 1237);
    long temp;
    //doubleToLongBitsメソッドの戻り値はlong型
    temp = Double.doubleToLongBits(doubleValue);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    result = prime * result + Float.floatToIntBits(floatValue);
    result = prime * result + intValue;
    result = prime * result + (int) (longValue ^ (longValue >>> 32));
    result = prime * result + ((stringValue == null) ? 0 : stringValue.hashCode());
    return result;
  }
  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    ObjectHash other = (ObjectHash) obj;
    if (boolValue != other.boolValue)
      return false;
    if (Double.doubleToLongBits(doubleValue) != Double.doubleToLongBits(other.doubleValue))
      return false;
    if (Float.floatToIntBits(floatValue) != Float.floatToIntBits(other.floatValue))
      return false;
    if (intValue != other.intValue)
      return false;
    if (longValue != other.longValue)
      return false;
    if (stringValue == null) {
      if (other.stringValue != null)
        return false;
    } else if (!stringValue.equals(other.stringValue))
      return false;
    return true;
  }
}
```

##オブジェクトを比較する
* **compareToメソッド**
    * Arrays.sortメソッドによるオブジェクトのソート
    * TreeMap/TreeSetの順序付きのキー管理
* ObjectクラスではなくComparableインターフェースに属するメソッド
* 値の大小に意味を持ったクラスなら基本実装する
* `Comparable<T>`で実装
    * `<T>`は比較対象のオブジェクト
    * `Comparable<Person>`
* 大小に応じて以下ルールで値を返す
    * this>引数：**整数**
    * this=引数：**0**
    * this<引数：**負数**
* 最初に`lastNameKanaフィールド`で大小比較
* 等しい場合`firstNameKanaフィールド`で大小比較


```java
//PersonクラスにcompareToメソッドを実装する例
//<Person>オブジェクトが比較対象
//姓の辞書順に並べる
public class Person implements Comparable<Person> {
  private String firstNameKana;
  private String lastNameKana;

  public Person(String firstNameKana, String lastNameKana) {
    this.firstNameKana = firstNameKana;
    this.lastNameKana = lastNameKana;
  }
  //名字、名前カナで大小判定
  @Override
  //最初に`lastNameKanaフィールド`で大小比較
  public int compareTo(Person o) {
    //等しい場合`firstNameKanaフィールド`で大小比較
    if (this.lastNameKana.equals(o.lastNameKana)) {
      return this.firstNameKana.compareTo(o.firstNameKana);
    } else {
      return this.lastNameKana.compareTo(o.lastNameKana);
    }
  }

  @Override
  public String toString() {
    return this.lastNameKana + " " + this.firstNameKana;
  }
}
```
```java
import java.util.Arrays;
import java.util.Arrays;

public class CompareBasic {
    public static void main(String[] args) {
        var data = new Person[] {
                new Person("Harry", "Potter"),
                new Person("Ron", "Weasley"),
                new Person("Hermione", "Granger"),
                new Person("Albus", "Dumbledore"),
                new Person("Severus", "Snape"),
                new Person("Serius", "Black"),
        };
        Arrays.sort(data);
        System.out.println(Arrays.toString(data));
        //[Black Serius, Dumbledore Albus, Granger Hermione, Potter Harry, Snape Severus, Weasley Ron]
    }
}
```

##オブジェクトを複製
* **cloneメソッド**
    * `x.clone()!= x` 異なる参照であること
    * `x.clone().getClass() == x.getClass()` 型が一致してること
    * `x.clone().equals(x)` 同値性を満たすこと
* 注意：代入演算子`=`は参照のコピーのみで複製にならない
    * `var x_copy = x;`
* **シャローコピー**
* **Cloneableインターフェース**：それ自体は意味を持たないインターフェース (**マーカーインターフェース**)
    * Cloneableインターフェースを実装している限りCloneNotSupportedExceptionは発生しない
    * **AssertionError**：プログラムが本来起こりえない予期しない動作をしていることを表すエラー

```java
//Cloneableインターフェースを実装して明示的に複製を許可
public class Person implements Cloneable {
  private String firstName;
  private String lastName;

  public Person(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  //戻り値を本来のObjectではなくPerson型に合わせ利用者側でキャストの手間を省く
  @Override
  public Person clone() {
    Person p = null;
    try {
      //Objectクラスのcloneメソッドを呼び出す
      p = (Person)super.clone();
    } catch (CloneNotSupportedException e) {
      throw new AssertionError();
    }
    return p;
  }

  @Override
  public String toString() {
    return this.lastName + this.firstName;
  }
}
```
###フィールドに可変型を含んでいる場合のコピー
* **ディープコピー**
* 参照型のコピーで配列コピーにcloneメソッドを使う
    * cf: よりパフォーマンスに優れる`Arrays.copyOf/copyOfRangeメソッド` がおすすめ
 
```java
public class Person implements Cloneable {
  private String firstName;
  private String lastName;
  private String[] memos;

  public Person(String firstName, String lastName, String[] memos) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.memos = memos;
  }

  @Override
  public Person clone() {
    Person p = null;
    try {
      p = (Person) super.clone();
      //該当フィールドのcloneメソッドを呼び出すことで配列のオブジェクトを明示的に複製
      p.memos = this.memos.clone();
    } catch (CloneNotSupportedException e) {
      throw new AssertionError();
    }
    return p;
  }

  @Override
  public String toString() {
    return  String.format("%s%s（%s）",
        this.lastName, this.firstName, this.memos[1]);
  }
}
```