【Java】JavaのObjectクラスの役割と共通メソッドを理解する

Java

こんにちは、かつコーチです。
「継承していないのに、なぜどのクラスでもequals()toString()が呼べるのか」と疑問に思ったことはありませんか。
今回はすべてのクラスの頂点にあるObjectクラスの役割を整理します。

Objectクラスとは?すべてのクラスの親

暗黙のextends

Javaでは、extendsを書かずにクラスを定義しても、暗黙的にjava.lang.Objectを継承しています。

public class Point {
    // 実際には public class Point extends Object と同じ
}

そのため、自分で定義したどんなクラスのインスタンスからでも、Objectが持つメソッドを呼び出せます。

Point p = new Point();
System.out.println(p.toString()); // Objectのメソッドが呼べる
System.out.println(p.hashCode());
System.out.println(p.equals(p));

これがJavaの単一継承(クラスは1つの親クラスしか持てない仕組み)の起点であり、すべてのオブジェクトが共通の基盤メソッドを持つことを保証しています。

Objectクラスが提供する主要メソッド

equals・hashCode・toString

これらは前回の記事で詳しく解説した通り、多くのクラスでオーバーライドして使うメソッドです。
デフォルトの実装は次のようになっています。

  • equals(Object o):参照が同じかどうか(==)を比較する
  • hashCode():オブジェクトごとに(基本的には)異なる整数値を返す
  • toString()クラス名@ハッシュ値の16進数という文字列を返す

getClass()で実行時の型を取得する

getClass()は、そのオブジェクトの実行時のクラス情報(Classオブジェクト)を返します。

Object obj = "hello";
System.out.println(obj.getClass().getName()); // java.lang.String

リフレクション(実行時にクラスの構造を調べたり操作したりする仕組み)の入り口として使われるほか、equals()の型チェックでもよく使われます。
getClass()finalメソッドなので、サブクラスでオーバーライドすることはできません。

wait・notify・notifyAllはマルチスレッド制御用

Objectにはwait(), notify(), notifyAll()という、一見用途が分かりにくいメソッドもあります。
これらはマルチスレッドプログラミングにおいて、synchronizedブロックの中でスレッド間の待ち合わせを行うためのメソッドです。

synchronized (lock) {
    while (!condition) {
        lock.wait(); // 条件が満たされるまで待機
    }
    // 処理を続行
}

初中級のうちはあまり直接使う機会がないかもしれませんが、「なぜObjectにこんなメソッドがあるのか」という疑問への答えとして知っておくと理解が深まります。
並行処理編で詳しく扱う予定です。

clone()は使いどころが限定的

clone()はオブジェクトの複製を作るメソッドですが、Cloneableインターフェースを実装していないクラスで呼ぶとCloneNotSupportedExceptionが発生する上、内部的な仕組みが分かりにくく、Effective Javaでも「コピーコンストラクタやコピーファクトリの使用を推奨する」とされています。

// ✅よりシンプルで意図が明確な代替手段:コピーコンストラクタ
public class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public Point(Point source) {
        this(source.x, source.y);
    }
}

実務ではclone()を使う場面はほとんどなく、コピーコンストラクタやrecordを使う方が意図が明確になります。

つまずいたポイント:finalizeの罠

finalize()に処理を書いて後悔した話

以前、ファイルハンドルを閉じ忘れないようにと、finalize()にクローズ処理を書いたことがあります。

// ❌Before:finalizeにリソース解放処理を書く
public class FileResource {
    private FileInputStream stream;

    @Override
    protected void finalize() throws Throwable {
        if (stream != null) {
            stream.close();
        }
        super.finalize();
    }
}

このコードは一見動いているように見えましたが、実際にはファイルハンドルがなかなか解放されず、大量のファイルを処理するバッチ処理で「too many open files」エラーが発生しました。

java.io.FileNotFoundException: /data/output.csv (Too many open files)

原因は、finalize()が呼ばれるタイミングはガベージコレクタに完全に依存しており、いつ実行されるか(そもそも実行されるか)が保証されていないためでした。
finalize()はJava 9で非推奨になり、後継のクリーンアップ手段としてtry-with-resourcesAutoCloseableが推奨されています。

// ✅After:try-with-resourcesで確実にクローズする
try (FileInputStream stream = new FileInputStream("input.txt")) {
    // 処理
} // ブロックを抜けると自動的にclose()が呼ばれる

リソース管理をfinalize()に頼るのは避け、try-with-resourcesを使うのが現在のベストプラクティスです。

Objectクラスのメソッド一覧と使用頻度

実務での使用頻度で整理する

メソッド使用頻度用途
equals()オブジェクトの等価性を判定
hashCode()ハッシュベースのコレクションで使用
toString()デバッグ・ログ出力
getClass()実行時の型情報の取得、equals内での型チェック
wait/notify/notifyAllマルチスレッドの待ち合わせ制御
clone()ほぼ使わないオブジェクトの複製(コピーコンストラクタで代替可)
finalize()非推奨ガベージコレクト時の後処理(try-with-resourcesで代替)

初中級のうちはequalshashCodetoStringの3つを重点的に理解し、その他は「こういう仕組みがある」と知っておく程度で十分です。

まとめ

この記事のポイント

  • すべてのクラスは暗黙的にObjectクラスを継承しており、共通メソッドを持つ
  • equalshashCodetoStringは実務での使用頻度が高く、正しい実装が重要
  • waitnotifynotifyAllはマルチスレッド制御のための仕組み
  • clone()finalize()は現在では推奨されず、コピーコンストラクタやtry-with-resourcesで代替する
  • finalize()は実行タイミングが保証されないため、リソース解放処理をここに書いてはいけない

次に読むべき記事

  • equals・hashCode・toStringの詳しい実装方法は前回の記事「equals・hashCode・toStringの実装」へ
  • カプセル化やgetter・setterの基本は「カプセル化とgetter・setter」の記事へ

タグ: Java, 中級者向け, オブジェクト指向

タイトルとURLをコピーしました