Java でプログラムを書くうえで、まず知っておきたいのが java.lang パッケージ です。Java の開発者であれば、一度は耳にしたことがあるかもしれません。
java.lang パッケージはJava の もっとも基本的かつ頻繁に使われるクラス がまとめられているパッケージでJavaのコーディングに欠かせない最低限のパッケージです。

例えば・・・以下のようなクラスを含みます。
String: 文字列を扱うためのクラスSystem: 標準入力・標準出力やシステムプロパティへのアクセス手段を提供するクラスMath: 数学計算(累乗、平方根、三角関数など)のためのクラスInteger/Doubleなどのラッパークラス: 基本データ型(int, double など)をオブジェクトとして扱うために用意されているクラスObject: Java のすべてのクラスの最上位に位置するクラス
なかでも Object は全クラスの親 となる非常に重要な存在です。Java のプログラムは小規模から大規模まで幅広いケースで使われますが、この java.lang パッケージが提供する基本機能が土台となっているおかげで、どのような規模のアプリケーションを書いていても便利に活用できるようになっています。
このページではjava.lang パッケージ について初心者向けに1からわかりやすく解説します。
java.langパッケージはインポート不要
Java にはパッケージ(参考 パッケージとは?)ごとにクラスが整理されていますが、通常は別のパッケージのクラスを使う際には import 文を記述します。たとえば、java.util.List や java.util.ArrayList を使いたい場合、
import java.util.ArrayList; import java.util.List;
のようにimport文で明示的にパッケージをインポートしてあげる必要があります。しかし、String や System といったクラスを使うときは
import java.lang.String; import java.lang.System;
というコードを書く必要がありません(書いてもOK)。
それは 言語仕様で java.lang.* が自動的にインポートされる と定義されているためです。Javaを開発したサン・マイクロシステムズ(現オラクル)の設計時点で、
「もっとも基本的なクラスを扱うときに、いちいち
importするのは煩雑になるから、最初から読み込まれていたほうが便利だろう」
という判断が行われています。そのため、System.out.println("Hello"); のように System クラスをそのまま呼び出せるわけです。もしこの仕様がなければ、些細なプログラムを書くたびに import java.lang.*; といった宣言が必要になり、開発効率が落ちてしまいます。
一方で java.util.* や java.io.* など、他のパッケージは用途が明確かつ専用性が高いため、必要に応じて明示的に import する仕組みが適用されているというわけ。これにより、同じクラス名が複数のパッケージに存在しても、パッケージ名を付けて識別できるメリットも確保されています。
Objectクラス
java.lang.Object は、Java のすべてのクラスが必ず継承 している特別なクラスです。これは自分でクラスを定義するときに extends Object と書いていなくても、コンパイラが自動的に Object を親として認識します。これを 暗黙的な継承 と呼びます。
public class MyClass extends Object {
// 実際にはこうなっている
}
一般にクラスの継承とは、「あるクラス(サブクラス)が別のクラス(スーパークラス)を引き継いで機能を拡張している」というイメージです。しかし Object は「すべてのクラスの最上位」に位置しているため、Java のどんなクラスを見ても、最終的に辿っていけば必ず Object クラスに行き着く構造になっています。
Object の暗黙的継承による恩恵
この仕組みによって、Java のオブジェクトであれば必ず使える共通メソッドが存在するという大きなメリットがあります。具体的には後述の toString(), equals(), hashCode() など、どのクラスでも必ず呼び出せるようになっているため、開発者同士が「オブジェクトといえばこういうメソッドを持っているはずだ」という共通認識を持てるのです。
さらに、Java が持つ 多態性(ポリモーフィズム) の考え方と相まって、「あるメソッドの引数に Object を受け取る」と宣言しておけば、どんなクラスのオブジェクトでも渡すことができるようになります。これはフレームワークやライブラリの設計において非常に強力な手法となります。
Objectクラスの代表的なメソッド
それでは、具体的に Object クラスが提供するメソッドをもう少し詳しく見ていきましょう。

いずれも Java を使っていくなら必ず耳にする重要なメソッドです。
toString()- オブジェクトの文字列表現を返すためのメソッドです。デフォルトの実装ではクラス名とハッシュコードが文字列として返されますが、通常は目的に合わせてオーバーライドします。
- 例:
MyClass@5e2de80cといった値がデフォルトのtoString()の戻り値。分かりにくいので、"MyClass: {id: " + this.id + "}"のように独自定義することがよくあります。
equals(Object obj)- 同値性の判定 を行うメソッドです。デフォルトの実装は 参照 が同じオブジェクトかどうかのみをチェックしますが、クラスによってはフィールドの値で比較したいケースが多々あります。
- 例:
Personクラスでnameやageの値が等しければ同じ人物とみなしたい場合、equals()を上書きし、(this.name.equals(other.name) && this.age == other.age)のようなロジックを書くことになります。
hashCode()- オブジェクトのハッシュコード(数値) を返します。通常、
equals()とセットでオーバーライドしなければなりません。equals()がtrueを返すオブジェクト同士は、hashCode()も同じ値になるようにするのがJavaのルールです。 - コレクション(
HashMapやHashSet)で重複判定や高速検索を実現するうえで非常に重要です。
- オブジェクトのハッシュコード(数値) を返します。通常、
getClass()- オブジェクトの実行時のクラス情報を取得するメソッド。たとえば、
obj.getClass().getName()でクラス名の文字列表現を取得可能です。ダイナミックにクラスを識別する際に役立ちます。
- オブジェクトの実行時のクラス情報を取得するメソッド。たとえば、
notify(),notifyAll(),wait()- Java のスレッド同期を行う低レベルなメソッド群です。
synchronizedキーワードと組み合わせて、あるオブジェクトをロックする仕組みに利用します。 - たとえば、
wait()でスレッドを待機状態にし、別のスレッドがnotify()でそれを解除するといった流れを実現できます。
- Java のスレッド同期を行う低レベルなメソッド群です。
以上のように、Object クラスは 「Java オブジェクトなら共通してこれくらいの機能は備えている」 という最低限の仕様を定義しているわけです。
サンプルコードで学ぶクラス継承の実践例
それでは、実際にクラスを定義し、Object クラスのメソッドをオーバーライドしてみるコード例を見てみましょう。
public class InheritanceSample {
public static void main(String[] args) {
Person p1 = new Person("Alice", 20);
Person p2 = new Person("Alice", 20);
Person p3 = new Person("Bob", 25);
// toString() の呼び出し(自動的に呼ばれるケースも多い)
System.out.println(p1);
// => Person{name='Alice', age=20}
// equals() の比較
System.out.println(p1.equals(p2)); // => true
System.out.println(p1.equals(p3)); // => false
// hashCode() の比較(p1 と p2 は同じ値が返されるはず)
System.out.println(p1.hashCode());
System.out.println(p2.hashCode());
System.out.println(p3.hashCode());
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// toString() をオーバーライドして分かりやすい文字列表現に
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
// equals() で同名・同年齢なら同じとみなすロジックを定義
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 同じ参照なら true
if (obj == null) return false; // null なら false
if (getClass() != obj.getClass()) return false; // クラス型が違えば false
Person other = (Person) obj; // キャスト
return this.name.equals(other.name) && this.age == other.age;
}
// equals() の内容にあわせて hashCode() もオーバーライド
@Override
public int hashCode() {
int result = 17;
// 31 は計算上の衝突を抑える「マジックナンバー」
result = 31 * result + name.hashCode();
result = 31 * result + age;
return result;
}
}
main()メソッドからSystem.out.println(p1);と書くと、内部的にはp1.toString()が自動的に呼び出されます。ここでオーバーライドしていない場合はPerson@xxx...のような表示になりますが、オーバーライドすると 「Person{name='Alice', age=20}」 といった分かりやすい文字列を返せるようになります。equals()をカスタマイズすることで、同じnameとageを持つPersonインスタンス同士を同一とみなすことができます。hashCode()はequals()とセットで整合性を保つように実装する必要があります。equals()がtrueになるオブジェクトは、同じhashCode()を返すのが鉄則です。
この例のように、Object クラスから継承しているメソッドをオーバーライドすることで、オブジェクトの扱い方を柔軟に定義できるわけです。
まとめ:基礎を押さえればJavaの理解が深まる
java.langパッケージ
Java でもっとも基本的なクラスをまとめたパッケージであり、自動的にインポート されるため、import文を書かなくても自由にクラスを利用できます。Objectクラス
すべてのクラスの最上位に位置する、いわば “頂点” のようなクラス。暗黙的に継承されているため、Java のクラスはすべて、Objectが提供する各種メソッド(toString(),equals(),hashCode(), など)を利用可能です。- 各メソッドのオーバーライド
equals()とhashCode()は 論理的に「同じ」オブジェクトであるかを定義 するうえで不可欠です。デフォルトの実装は参照比較でしかありませんが、実際のアプリケーションではフィールド値などを考慮してオーバーライドすることが多々あります。またtoString()をオーバーライドすると、ログ出力やデバッグ時にクラスの状態を簡潔に確認できるため便利です。 - オブジェクト指向プログラミングへのつながり
この基礎的な仕組みを理解しておくと、継承 や ポリモーフィズム といったオブジェクト指向のコア概念がより深く腑に落ちます。ライブラリやフレームワークが「Object を親とした引数」に対してどんな使われ方をしているかをイメージできるようになり、Java のコードを読み解く力が高まるのです。
Java を使ってアプリケーションを作っていると、java.lang パッケージ内のクラスに触れる機会は非常に多いでしょう。それだけに、なぜ自動インポートされるのか、どうして Object が最上位クラスなのか、といった基本をきちんと理解しておくと、日々のプログラミングが一段とスムーズになるはずです。
