「Javaを学び始めたけど、コンストラクタの役割がよくわからない…」
「メソッドと何が違うの?」
「this()とかsuper()とか、似たようなものがあって混乱する」
私がJavaを学び始めた頃、つまずいたのがコンストラクタの概念でした。
当時は「おまじないのようなもの」としか理解できず、何度もエラーを出しては頭を抱えたものです。
しかし、実務で多くのコードを書き、レビューをする中で、コンストラクタがオブジェクト指向プログラミングの心臓部とも言えるほど重要な役割を担っていると痛感しました。
良いコンストラクタは、バグが少なく、保守性の高いコードを生み出します。
この記事は、過去の私と同じように悩んでいる「Javaのコンストラクタについて知りたい方」に向けて書きました。
この記事を読み終える頃には、あなたは次の状態になっています。
この記事でわかること
- コンストラクタの役割と必要性を、自分の言葉で説明できる
- コンストラクタの正しい書き方を理解し、自分でコードが書ける
- オーバーロードや
this()を使いこなし、よりスマートなコードを設計できる
Javaの学習を次のステップに進めるために、一緒にコンストラクタをマスターしていきましょう!
この記事は、Java入門ロードマップの「STEP4|オブジェクト指向」に対応しています。全体の学習順は次の記事で確認できます。
PARTS LIST /目次
コンストラクタとは?Javaの基本をおさらい
Javaにおけるコンストラクタとは、オブジェクトを生成する(インスタンス化する)ときにだけ呼び出される、特別な処理のことです。
主な役割は、オブジェクトが作られた直後に、そのオブジェクトが持つフィールドに初期値を設定すること。
これにより、オブジェクトが不完全な状態になるのを防ぎ、必ず初期化された状態で利用できるようになります。
クラスとオブジェクトの関係
コンストラクタを理解するには、まずクラスとオブジェクトの関係をおさらいするのが近道です。
- クラス: オブジェクトの「設計図」です。例えば、「車」クラスには、色や速度といった属性(フィールド)や、走る・止まるといった操作(メソッド)が定義されます。
- オブジェクト: クラスという設計図をもとに作られた「実体」です。例えば、「赤い色の時速60kmで走る車」や「青い色の止まっている車」がオブジェクトにあたります。
この設計図(クラス)から実体(オブジェクト)を作り出す工程で、特別な組み立て作業を行うのがコンストラクタの役目なのです。
// 「車」という設計図(クラス)
class Car {
String color; // 色
int speed; // 速度
// これがコンストラクタ!
// オブジェクトが作られるときに色と速度を設定する
Car(String c, int s) {
color = c;
speed = s;
System.out.println("色:" + color + "、速度:" + speed + "の車が作られました。");
}
}
public class Main {
public static void main(String[] args) {
// new Car(...)でコンストラクタが呼び出され、オブジェクトが生成される
Car myCar = new Car("赤", 60);
Car yourCar = new Car("青", 0);
}
}
このコードでは、new Car(...) と書いた瞬間にCarクラスのコンストラクタが呼び出され、それぞれの車の色と速度が設定されています。
メソッドとの違いをわかりやすく解説
コンストラクタはメソッドと形が似ていますが、明確な違いが3つあります。
一番大きな違いは、コンストラクタはオブジェクトが生まれる瞬間に、システムによって自動的に一度だけ呼び出される点です。
一方で、メソッドは私たちの好きなタイミングで何度でも呼び出すことができます。
コンストラクタは「誕生の儀式」、メソッドは「誕生後の行動」とイメージすると分かりやすいかもしれません。
Javaのコンストラクタの書き方と基本構文
コンストラクタの書き方には、いくつかのルールがあります。
コンストラクタの定義方法
コンストラクタの基本的な構文は以下の通りです。
修飾子 クラス名(引数リスト) {
// オブジェクトの初期化処理
}
- 修飾子:
publicやprivateなどのアクセス修飾子を指定します。 - クラス名: 必ず、所属するクラスと全く同じ名前にします。
- 引数リスト: オブジェクトの初期化に必要な値を受け取ります。不要な場合は空にします。
これがJavaの世界で「この部分はコンストラクタです」と示すための厳格なルールです。
コンストラクタ名はクラス名と同じになる理由
コンストラクタ名がクラス名と同じでなければならないのには理由があります。
Javaコンパイラ(プログラムを機械がわかる言葉に翻訳する仕組み)が、メソッドとコンストラクタを明確に区別するためです。
もし自由に名前をつけられると、どれが初期化用の特別な処理なのか判断できません。また、戻り値がないという特徴も、この区別に一役買っています。
この2つのルールがあるからこそ、new演算子が使われたときに、正確に対応するコンストラクタを呼び出せるのです。
引数あり・なしの違い(デフォルトコンストラクタ)
コンストラクタには、引数がないものと、引数があるものの2種類があります。
引数なしコンストラクタclass Book {
String title;
// 引数なしコンストラクタ
Book() {
this.title = "タイトル未設定";
System.out.println("本が作られました。");
}
}
引数ありコンストラクタ
class Book {
String title;
// 引数ありコンストラクタ
Book(String title) {
this.title = title; // 引数で受け取った値で初期化
System.out.println("「" + title + "」というタイトルの本が作られました。");
}
}
ここでひとつ、重要な注意点があります。
それは、プログラマがコンストラクタを1つも定義しなかった場合の挙動です。
Javaコンパイラが自動的に、引数なしの空のコンストラクタ(デフォルトコンストラクタ)を追加してくれます。
// コンストラクタを何も書いていない
class Pen {
String color;
}
// 内部的には、コンパイラが以下を追加してくれる
// Pen() {
// }
しかし、引数ありのコンストラクタを1つでも定義すると、この自動追加は行われません。そのため、引数なしでオブジェクトを作ろうとするとエラーになります。
class Pen {
String color;
// 引数ありコンストラクタを定義
Pen(String color) {
this.color = color;
}
}
public class Main {
public static void main(String[] args) {
// OK: 引数ありコンストラクタを呼び出す
Pen redPen = new Pen("赤");
// エラー!: 引数なしコンストラクタはもう存在しない
// Pen bluePen = new Pen();
}
}
この仕様は初心者がつまずきやすいポイントなので、しっかり覚えておきましょう。
recordのコンパクトコンストラクタ
Java 16で標準導入されたrecordを使うと、フィールドを宣言するだけでコンストラクタが自動生成されます。
同時にアクセサメソッド(x()のような、フィールドと同名のメソッド)・equals()・hashCode()・toString()も生成されます。
// フィールドを宣言するだけでコンストラクタが自動生成される
record Point(int x, int y) {
}
public class Main {
public static void main(String[] args) {
Point p = new Point(3, 5); // 自動生成されたコンストラクタが呼ばれる
System.out.println(p.x() + ", " + p.y());
}
}
通常のコンストラクタと違い、引数リストを省略した「コンパクトコンストラクタ」という書き方で、値の検証(バリデーション)処理だけを追加できます。
record Point(int x, int y) {
// コンパクトコンストラクタ:引数リストを省略して書ける
Point {
if (x < 0 || y < 0) {
throw new IllegalArgumentException("座標は0以上にしてください。");
}
}
}
enumのコンストラクタ
enum(列挙型)にもコンストラクタを定義できます。各列挙定数が持つ固有の値を初期化するために使われます。
enum Season {
SPRING(15), SUMMER(28), AUTUMN(18), WINTER(5);
private final int averageTemp;
// enumのコンストラクタ
Season(int averageTemp) {
this.averageTemp = averageTemp;
}
int getAverageTemp() {
return this.averageTemp;
}
}
enumのコンストラクタは暗黙的にprivateです。
列挙定数はクラス内で定義された分しか存在せず、外部から新しいインスタンスをnewで作ることはできません。
複数のコンストラクタを使い分ける「オーバーロード」
Javaでは、同じクラス内に引数の数や型、並び順が異なるコンストラクタを複数定義できます。これをコンストラクタのオーバーロードと呼びます。
オーバーロードを利用すると、オブジェクトを様々な方法で初期化できるようになり、クラスの柔軟性が大きく向上します。
オーバーロードの具体例
例えば、会員情報を管理するMemberクラスを考えてみましょう。
- IDだけで仮登録したい場合
- IDと名前で本登録したい場合
- ID、名前、年齢で詳細に登録したい場合
など、状況によって初期化したい情報が異なります。こんなときにオーバーロードが役立ちます。
class Member {
long id;
String name;
int age;
// 1. IDだけで初期化するコンストラクタ
Member(long id) {
this.id = id;
this.name = "(ゲスト)"; // 名前はデフォルト値
this.age = -1; // 年齢は無効な値
}
// 2. IDと名前で初期化するコンストラクタ
Member(long id, String name) {
this.id = id;
this.name = name;
this.age = -1;
}
// 3. ID、名前、年齢で初期化するコンストラクタ
Member(long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
void printInfo() {
System.out.println("ID: " + id + ", 名前: " + name + ", 年齢: " + age);
}
}
引数の数・型が違うとどう動く?
呼び出す側がnew Member(...)と書いたとき、Javaは渡された引数の数と型の組み合わせを自動的に判断します。そして、最も適したコンストラクタを選んで実行します。たとえばnew Member(101)の101はintですが、intからlongへの拡張変換が行われ、Member(long)が選ばれます。
public class Main {
public static void main(String[] args) {
// Member(long) が呼び出される
Member guest = new Member(101);
guest.printInfo(); // -> ID: 101, 名前: (ゲスト), 年齢: -1
// Member(long, String) が呼び出される
Member user1 = new Member(205, "田中");
user1.printInfo(); // -> ID: 205, 名前: 田中, 年齢: -1
// Member(long, String, int) が呼び出される
Member user2 = new Member(315, "鈴木", 25);
user2.printInfo(); // -> ID: 315, 名前: 鈴木, 年齢: 25
}
}
Javaが賢く判断してくれるおかげで、私たちは状況に応じて最適な初期化方法を選ぶことができるのです。
new Member(101)引数1つ1. Member(long id)101(int)は long に拡張変換されるID: 101, 名前: (ゲスト), 年齢: -1new Member(205, "田中")引数2つ2. Member(long id, String name)ID: 205, 名前: 田中, 年齢: -1new Member(315, "鈴木", 25)引数3つ3. Member(long id, String name, int age)ID: 315, 名前: 鈴木, 年齢: 25this()を使ったコンストラクタの呼び出し方
オーバーロードは便利です。
しかし先ほどのMemberクラスの例では、this.id = id; や this.name = name; といった同じようなコードが何度も出てきました。
このようなコードの重複をなくし、もっとスマートに記述するための仕組みがthis()です。
this()を使うと、同じクラス内にある別のコンストラクタを呼び出すことができます。
this()の基本的な使い方
this()は、Java 24以前ではコンストラクタの先頭行でのみ使用できます(Java 25では、生成中のオブジェクト自身を参照しない文であれば、this()やsuper()より前に書けるようになりました)。
先ほどのMemberクラスをthis()を使って書き換えてみましょう。初期化処理を引数が一番多いコンストラクタに集約するのがポイントです。
class Member {
long id;
String name;
int age;
// 3. メインとなるコンストラクタ(処理を集約)
Member(long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
System.out.println("3つの引数を持つコンストラクタが動きました。");
}
// 2. IDと名前で初期化するコンストラクタ
Member(long id, String name) {
// this(...)で、3つの引数を持つコンストラクタを呼び出す
this(id, name, -1); // 年齢にデフォルト値を渡す
}
// 1. IDだけで初期化するコンストラクタ
Member(long id) {
// this(...)で、2つの引数を持つコンストラクタを呼び出す
this(id, "(ゲスト)");
}
}
Member(long id)this(id, "(ゲスト)") で次へMember(long id, String name)this(id, name, -1) で次へMember(long id, String name, int age)フィールドへの代入はここだけで行うこのようにthis()を使うと、初期化ロジックが1箇所にまとまります。
将来、「会員IDの採番ルールが変わった」といった仕様変更があっても、修正は1箇所で済み、コードの保守性が格段に向上します。
this()とsuper()の違い
this()とよく似たものにsuper()があります。この2つは混同しやすいため、違いをはっきりさせておきましょう。
this(): 同じクラス内の、別のコンストラクタを呼び出す。super(): 親クラス(スーパークラス)のコンストラクタを呼び出す。
どちらも他のコンストラクタを呼び出す記述で、1つのコンストラクタ内には1回しか書けないため、this()とsuper()を同じコンストラクタ内で同時に使うことはできません(Java 24以前では、どちらも先頭行に書く必要があるためです)。
super()については、次の章で詳しく解説します。
new Person("A", 20)オブジェクトを作るコンストラクタを使うメリットと注意点
コンストラクタを正しく使うことで、コードの品質は大きく向上します。
初期化処理をまとめて管理できる
コンストラクタを使う最大のメリットは、オブジェクト生成時の初期化処理を1箇所に集約できることです。
もしコンストラクタがなければ、newでオブジェクトを作った後、setterメソッドなどを使って一つ一つ値を設定しなければなりません。
// コンストラクタがない場合のコード
Product pc = new Product();
pc.setName("ノートPC");
pc.setPrice(150000); // もしこの行を忘れたら?
pc.setCategory("家電");
この方法では、必要な初期化処理を忘れてしまう可能性があります。
価格が設定されていない商品オブジェクトが生まれてしまうと、後の処理でエラー(NullPointerExceptionなど)を引き起こす原因となります。
実際に、新人時代は1日にNullPointerExceptionを10回以上出していました。
原因の多くは、setterで値を設定し忘れたフィールドを、後の処理でそのまま使ってしまうことでした。
コンストラクタで必須項目の設定を強制する設計を徹底するようになってから、同種のバグは目に見えて減りました。
コンストラクタを使えば、必要な値を引数で受け取ることを強制できるため、初期化漏れのない、安全なオブジェクトの生成を保証できます。
new時に自動で呼ばれる仕組み
コンストラクタは、new演算子でインスタンスを生成する際に必ず自動的に呼び出されます。この「自動的」というのが重要なポイントです。
プログラマが初期化処理の呼び出しを意識する必要がなくなるため、ヒューマンエラーを防ぐことができます。
クラスを設計する側は「newで生成されるこのクラスのオブジェクトは、必ずコンストラクタを通って初期化される」という前提で、安心してロジックを組み立てることが可能です。
継承時の注意点(親クラスの呼び出し)
クラスの継承を使う場合、コンストラクタの動きに注意が必要です。
子クラスのオブジェクトが作られるとき、メモリ上のオブジェクトは1つだけですが、そのうち親クラスから受け継いだ部分が、親クラスのコンストラクタによって先に初期化されます。
そのため、子クラスのコンストラクタが動く前に、必ず親クラスのコンストラクタが呼び出されなければなりません。
この呼び出しは、子クラスのコンストラクタの先頭でsuper()と書くことで明示的に行えます。
何も書かれていない場合は、コンパイラが自動的に親クラスの引数なしコンストラクタを呼び出すsuper()を挿入します。
class Parent {
Parent() {
System.out.println("親クラスのコンストラクタが呼ばれました。");
}
}
class Child extends Parent {
Child() {
// コンパイラがここに super(); を自動で追加する
System.out.println("子クラスのコンストラクタが呼ばれました。");
}
}
public class Main {
public static void main(String[] args) {
new Child();
// 実行結果:
// 親クラスのコンストラクタが呼ばれました。
// 子クラスのコンストラクタが呼ばれました。
}
}
ここで注意したいのは、親クラスに引数なしのコンストラクタ(デフォルトコンストラクタ)が存在しない場合です。
この場合、コンパイラはsuper()を自動挿入できず、コンパイルエラーとなります。
class Parent {
String name;
// 引数ありコンストラクタしかない
Parent(String name) {
this.name = name;
}
}
class Child extends Parent {
// エラー!
// 親クラスのParent(String)を呼び出す super(name) を明示的に書く必要がある
Child() {
// 暗黙的なsuper()呼び出しが失敗する
}
}
このエラーを解決するには、子クラスのコンストラクタで、親クラスのコンストラクタを明示的にsuper("...")のように呼び出す必要があります。
継承を使う際は、親クラスのコンストラクタがどうなっているかを常に意識することが重要です。
privateコンストラクタでインスタンス化を制限する
コンストラクタにprivateを付けると、クラス外部からのnewを禁止できます。
public class MathUtils {
// インスタンス化させたくないのでコンストラクタをprivateにする
private MathUtils() {
}
public static int square(int n) {
return n * n;
}
}
// MathUtils utils = new MathUtils(); // コンパイルエラー
int result = MathUtils.square(5); // OK: staticメソッドはインスタンス不要で呼べる
この特性は、インスタンスを1つに制限する「シングルトンパターン」や、staticメソッドだけを提供するユーティリティクラスでよく使われます。
コンストラクタを使った実践コード例
理論を学んだ後は、実践的なコードで理解を深めましょう。ここでは、よく使われる2つのパターンを紹介します。
ユーザー情報クラスでの初期化例
Webアプリケーションなどで頻繁に登場する、ユーザー情報を扱うUserクラスです。
IDや名前など、生成時に必須となる情報はコンストラクタで設定を強制するのが良い設計です。
さらに、フィールドをfinalにすることで、一度設定した値を変更できなくなり、より安全なクラス(不変オブジェクト)になります。
public class User {
private final long id; // ID (変更不可)
private final String name; // 名前 (変更不可)
private String email; // メールアドレス (変更可能)
/**
* Userオブジェクトを生成するためのコンストラクタ
* @param id ユーザーID
* @param name ユーザー名
*/
public User(long id, String name) {
// 値が不正でないかチェックする(ガード節)
if (id <= 0) {
throw new IllegalArgumentException("IDは正の数でなければなりません。");
}
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("名前は空にできません。");
}
this.id = id;
this.name = name;
this.email = "未設定"; // emailは任意なのでデフォルト値を設定
}
public void setEmail(String email) {
this.email = email;
}
public void printProfile() {
System.out.println("--- ユーザー情報 ---");
System.out.println("ID: " + this.id);
System.out.println("名前: " + this.name);
System.out.println("Email: " + this.email);
System.out.println("--------------------");
}
}
このクラスでは、コンストラクタ内でIDや名前が不正な値でないかをチェックしています。
このように初期化の段階で不正なデータを弾くことで、オブジェクトが常に正しい状態であることを保証できるのです。
オーバーロードとthis()を組み合わせる例
ECサイトの商品クラスItemを例に、オーバーロードとthis()を組み合わせたスマートな実装を見てみましょう。
- 必須項目:商品ID、商品名、価格
- 任意項目:商品説明
public class Item {
private final int id;
private final String name;
private final int price;
private String description;
/**
* 全ての情報を指定して初期化するメインのコンストラクタ
*/
public Item(int id, String name, int price, String description) {
if (id <= 0 || name == null || name.isEmpty() || price < 0) {
throw new IllegalArgumentException("必須項目が不正です。");
}
this.id = id;
this.name = name;
this.price = price;
this.description = description;
}
/**
* 必須項目だけで初期化するコンストラクタ
* 商品説明はデフォルト値「説明なし」が入る
*/
public Item(int id, String name, int price) {
// this()を使ってメインのコンストラクタを呼び出す
this(id, name, price, "説明なし");
}
public void display() {
System.out.println(String.format("[%d] %s : %,d円", id, name, price));
System.out.println(" " + description);
}
}
呼び出し側のコードは次のようになります。
public class Main {
public static void main(String[] args) {
// 必須項目だけでオブジェクトを生成
Item book = new Item(1001, "Java入門", 3000);
// 全ての情報を指定してオブジェクトを生成
Item pc = new Item(2005, "高性能ノートPC", 250000, "最新CPU搭載モデルです。");
book.display();
System.out.println();
pc.display();
}
}
this()を使うことで初期化ロジックの重複がなくなり、コードがスッキリしました。
ロジックの修正が必要になった場合も、修正箇所が1つで済むため、メンテナンスが容易になります。
よくある質問
Q1. Javaのコンストラクタとは何ですか?
A1. オブジェクトを生成(インスタンス化)するときにだけ自動的に呼び出される特別な処理です。クラス名と同じ名前を持ち、戻り値を書かないのが特徴です。
Q2. コンストラクタとメソッドの違いは何ですか?
A2. コンストラクタはオブジェクト生成時にnew演算子で自動的に一度だけ呼ばれ、戻り値を持ちません。メソッドは好きなタイミングで何度でも呼び出せ、戻り値を持つことができます。
Q3. コンストラクタを1つも定義しないとどうなりますか?
A3. Javaコンパイラが自動的に引数なしの空のコンストラクタ(デフォルトコンストラクタ)を追加します。ただし、引数ありのコンストラクタを1つでも定義すると、この自動追加は行われません。
Q4. this()とsuper()の違いは何ですか?
A4. this()は同じクラス内の別のコンストラクタを呼び出し、super()は親クラスのコンストラクタを呼び出します。どちらもコンストラクタ内で1回だけ使え(Java 24以前は先頭行のみ)、同時に使うことはできません。
Q5. コンストラクタのオーバーロードとは何ですか?
A5. 同じクラス内に、引数の数や型、並び順が異なるコンストラクタを複数定義することです。オブジェクトを様々な方法で初期化できるようになります。
Q6. recordやenumにもコンストラクタはありますか?
A6. あります。recordはフィールド宣言だけでコンストラクタが自動生成され、コンパクトコンストラクタでバリデーションを追加できます。enumのコンストラクタは暗黙的にprivateで、外部からnewすることはできません。
まとめ:コンストラクタで設計力を上げる
今回は、Javaのコンストラクタについて、基本から実践的な使い方までを徹底的に解説しました。
最後に、この記事の要点をまとめます。
- コンストラクタは、オブジェクト生成時に自動で呼ばれる初期化専門の処理である。
- クラス名と同じ名前で、戻り値がないのが書き方のルール。
- オーバーロードで、様々な初期化パターンに対応できる。
this()でコンストラクタを連結し、コードの重複をなくせる。- コンストラクタは、オブジェクトの状態を正常に保つための重要な仕組み。
Javaのコンストラクタは、単にフィールドに値を代入するだけのものではありません。
オブジェクトが「生まれてから一度も不正な状態にならない」ことを保証するための、重要な砦なのです。
コンストラクタを使いこなせるようになると、クラスの設計思想が明確になり、コード全体の安定性や保守性が飛躍的に向上します。
| REV. | DATE | 内容 |
|---|---|---|
| 5 | 2026-10-16 | 図解を描き直し(図面のデザインにそろえた) |
| 4 | 2026-10-14 | 雰囲気だけの挿絵を外し、図解に置き換え |
| 3 | 2026-10-04 | 記載内容の事実確認と修正 |
| 2 | 2026-09-04 | 2026年9月時点の情報で全体を見直し |
| 1 | 2025-10-31 | 初版 |





