こんにちは、かつコーチです。
前回の記事でクラスとオブジェクトの違いを解説しましたが、「オブジェクトを作るたびにフィールドを1つずつ代入するのが面倒」と感じませんでしたか?
今回は、その悩みを解決するコンストラクタと、セットで登場するthisキーワードについて解説します。
コンストラクタとは?
オブジェクト生成時に呼ばれる特別なメソッド
コンストラクタとは、newでオブジェクトを作成する瞬間に自動的に呼ばれる特別なメソッドです。
主に、フィールドの初期値を設定する目的で使います。
コンストラクタには2つの特徴があります。
- クラス名と同じ名前を持つ
- 戻り値の型を書かない(
voidすら書かない)
public class Dog {
String name;
int age;
// コンストラクタ
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
void bark() {
System.out.println(name + "が吠えました!");
}
}
このコンストラクタがあれば、オブジェクト生成と同時に初期値を渡せます。
public class Main {
public static void main(String[] args) {
Dog dog1 = new Dog("ポチ", 3);
Dog dog2 = new Dog("コロ", 1);
dog1.bark(); // ポチが吠えました!
dog2.bark(); // コロが吠えました!
}
}
なぜコンストラクタが必要なのか
コンストラクタを使わない場合、以下のようにオブジェクト生成後に1行ずつ代入する必要があります。
Dog dog1 = new Dog();
dog1.name = "ポチ";
dog1.age = 3;
行数が増えるだけでなく、nameやageへの代入を忘れてもコンパイルエラーにならないという問題があります。
コンストラクタで必須の引数として受け取るようにすれば、「初期化し忘れ」を防ぐ設計にできます。
thisキーワードの役割
引数とフィールドの名前が同じときに区別する
thisは「このオブジェクト自身」を指すキーワードです。
先ほどのコンストラクタの中で使われていたthis.nameは「このオブジェクトが持つフィールドのname」を意味します。
public Dog(String name, int age) {
this.name = name; // 左のthis.nameはフィールド、右のnameは引数
this.age = age;
}
もしthisを付けずに書くとどうなるでしょうか。
❌Before
public Dog(String name, int age) {
name = name; // 引数のnameに引数のname自身を代入しているだけ
age = age; // フィールドには何も設定されない
}
このコードはコンパイルエラーにはなりませんが、フィールドのnameとageはずっと初期値(nullと0)のままになってしまいます。
これは初心者が非常にハマりやすい落とし穴です。
✅After
public Dog(String name, int age) {
this.name = name; // フィールドに引数の値を正しく代入
this.age = age;
}
this.を付けることで、Javaコンパイラに対して「これはフィールドの方だ」と明示的に伝えられます。
私が実際にハマったthisの落とし穴
私が実際に開発中に遭遇したのが、引数名をフィールド名とわざと変えていたコードを、後から「分かりやすくしよう」とフィールド名と同じ名前にリネームした際のバグです。
// リネーム前(正しく動いていた)
public Dog(String dogName) {
this.name = dogName;
}
// リネーム後(thisを付け忘れてバグ化)
public Dog(String name) {
name = name; // コンパイルは通るが、フィールドが初期化されない
}
コンパイルエラーが出ないため気付くのが遅れ、dog.nameを出力したらnullになっているという現象で数十分悩みました。
このケースでは、IDE(IntelliJ IDEA)が「代入した値が使われていません」という警告を出してくれるので、リネーム後は必ず警告欄を確認する習慣をつけると防げます。
よくあるつまずきポイント・エラー対処
デフォルトコンストラクタが消える問題
コンストラクタを1つも定義していないクラスには、Javaが自動的に引数なしの「デフォルトコンストラクタ」を用意してくれます。
しかし、引数ありのコンストラクタを1つでも定義すると、このデフォルトコンストラクタは自動生成されなくなります。
❌Before
public class Dog {
String name;
public Dog(String name) {
this.name = name;
}
}
// Main側
Dog dog = new Dog(); // コンパイルエラー! 引数なしのコンストラクタが存在しない
エラーメッセージは次のようになります。
error: constructor Dog in class Dog cannot be applied to given types;
required: String
found: no arguments
✅After
引数なしでも生成したい場合は、明示的に引数なしのコンストラクタも用意します。
public class Dog {
String name;
public Dog() {
this.name = "名無し";
}
public Dog(String name) {
this.name = name;
}
}
このように、同じクラスに引数の異なる複数のコンストラクタを定義できる仕組みを「コンストラクタのオーバーロード」と呼びます。
応用・一歩先の使い方
thisを使ってコンストラクタ同士を呼び出す
コンストラクタの中から、同じクラスの別のコンストラクタをthis(...)という形で呼び出すこともできます。
これにより、初期化処理の重複を減らせます。
public class Dog {
String name;
int age;
public Dog(String name) {
this(name, 0); // 引数2つのコンストラクタを呼び出す
}
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
}
this(name, 0)は必ずコンストラクタの最初の行に書く必要がある点に注意してください。
まとめ
この記事のポイント
- コンストラクタは
new時に自動で呼ばれる、初期化専用の特別なメソッド - コンストラクタ名はクラス名と同じにし、戻り値の型は書かない
- thisは「このオブジェクト自身」を指し、引数とフィールドの名前が同じ場合に区別するために使う
this.を付け忘れると、コンパイルは通るがフィールドが初期化されないバグになる- 引数ありのコンストラクタを定義すると、デフォルトコンストラクタは自動生成されなくなる
次に読むべき記事
コンストラクタでフィールドを初期化できるようになったら、次はフィールドを外部から直接触らせない設計である「カプセル化とgetter・setter」に進みましょう。
タグ: Java, 初心者向け, オブジェクト指向