PR

Java:this の意味と使い方を3分でわかりやすく整理

Java

Javaのthisは「今、実行しているメソッドが属しているオブジェクト自身」を指し示すための特別なキーワードです。あるクラスのインスタンスメソッド内でthisを使うと、「メソッドを呼び出しているインスタンスそのもの」を表します。

このページでは、thisとは何か?thisを使うメリットなどを初心者向けに1からわかりやすく順を追って解説します。

スポンサーリンク

Java:thisとは?

Javaにおけるthisキーワードは、「今、実行しているメソッドが属しているオブジェクト自身」を指し示すための特別なキーワードです。たとえば、あるクラスのインスタンスメソッド内でthisを使うと、「メソッドを呼び出しているインスタンスそのもの」を表します。

メソッドやコンストラクタの中だけで使える

thisはstaticメソッドの中やクラスの外で直接使うことはできません。「インスタンスが存在する」場面でのみ利用可能な点が重要です。(※this はインスタンスを指し示すキーワードであることを再度念押ししておきます。)

イメージとしては、「自分自身(=このオブジェクト)を指し示す人差し指」のようなもの。あるオブジェクトのインスタンスメソッド内でthisを呼ぶと、まさにそのオブジェクトを参照していると考えられます。

thisを使うメリット

フィールドとローカル変数の区別を明確にする

Javaでは、クラス内部に定義されるフィールド(メンバ変数)と、メソッド内で定義されるローカル変数が存在します。もし、フィールド名とローカル変数名が同じだった場合、それらを区別するためにthisを用いて「これはフィールドの方です」と明示することがよくあります。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class Sample {
private String name; // フィールド
public Sample(String name) {
// 引数 name と フィールド name が同名
this.name = name;
// this.name は フィールドの name
// 右辺の name は 引数の name (ローカル変数)
}
}
public class Sample { private String name; // フィールド public Sample(String name) { // 引数 name と フィールド name が同名 this.name = name; // this.name は フィールドの name // 右辺の name は 引数の name (ローカル変数) } }
public class Sample {
    private String name; // フィールド

    public Sample(String name) {
        // 引数 name と フィールド name が同名
        this.name = name; 
        // this.name は フィールドの name
        // 右辺の name は 引数の name (ローカル変数)
    }
}

上記のように、もしthisを付けなかった場合は「近いスコープにある変数」、つまり引数のnameが優先されてしまいます。その結果、思ったとおりに代入ができなくなる可能性があります。そうした混乱を避けるためにthisがよく利用されるのです。

同じクラス内のメソッド呼び出しを明確化する

同じクラス内のメソッド同士であっても、あえてthisを付けることで「同じオブジェクトの別メソッドを呼んでいる」という意図を明確にできます。短いクラスであればメリットは感じづらいかもしれませんが、規模の大きなクラスでは可読性の向上につながるケースもあります。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class MethodCallExample {
// メソッド1
public void methodA() {
System.out.println("methodAが呼び出されました。");
}
// メソッド2
public void methodB() {
System.out.println("methodBが呼び出されました。");
}
// 同じクラス内のメソッドを呼び出すメソッド
public void start() {
// thisを使ってmethodAを呼び出す
this.methodA();
// thisを使ってmethodBを呼び出す
this.methodB();
}
public static void main(String[] args) {
// インスタンスを生成
MethodCallExample example = new MethodCallExample();
// startメソッドを呼び出す
example.start();
}
}
public class MethodCallExample { // メソッド1 public void methodA() { System.out.println("methodAが呼び出されました。"); } // メソッド2 public void methodB() { System.out.println("methodBが呼び出されました。"); } // 同じクラス内のメソッドを呼び出すメソッド public void start() { // thisを使ってmethodAを呼び出す this.methodA(); // thisを使ってmethodBを呼び出す this.methodB(); } public static void main(String[] args) { // インスタンスを生成 MethodCallExample example = new MethodCallExample(); // startメソッドを呼び出す example.start(); } }
public class MethodCallExample {

    // メソッド1
    public void methodA() {
        System.out.println("methodAが呼び出されました。");
    }

    // メソッド2
    public void methodB() {
        System.out.println("methodBが呼び出されました。");
    }

    // 同じクラス内のメソッドを呼び出すメソッド
    public void start() {
        // thisを使ってmethodAを呼び出す
        this.methodA();

        // thisを使ってmethodBを呼び出す
        this.methodB();
    }

    public static void main(String[] args) {
        // インスタンスを生成
        MethodCallExample example = new MethodCallExample();

        // startメソッドを呼び出す
        example.start();
    }
}

フィールドとローカル変数の名前が重複した場合

前述のように、クラスのフィールドをローカル変数と同じ名前にしてしまうと、メソッド内でその名前を使ったときに、いったいどちらの変数を参照するのか分かりづらくなります。Javaでは、ローカル変数が優先されるルールになっているため、そのままではフィールドが正しく参照されないことになります。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class Book {
private String title; // フィールド
public void setTitle(String title) {
// ここで title = title; と書くと、
// すべてがローカル変数の title を指すため、
// フィールドの title は変更されない。
this.title = title;
}
}
public class Book { private String title; // フィールド public void setTitle(String title) { // ここで title = title; と書くと、 // すべてがローカル変数の title を指すため、 // フィールドの title は変更されない。 this.title = title; } }
public class Book {
    private String title; // フィールド

    public void setTitle(String title) {
        // ここで title = title; と書くと、
        // すべてがローカル変数の title を指すため、
        // フィールドの title は変更されない。
        this.title = title;
    }
}

this.title = title;と書くことで、左辺のtitleが**フィールドのtitle**であることを明示的に示しています。こうすることで、引数を正しくフィールドに代入できるわけです。

thisの有無でどう変わる?

  • this.title:インスタンスのフィールドtitle
  • title:メソッド(引数)のtitleまたはローカル変数title

この区別が初心者の方にとっては重要なポイントです。

メソッド内でthisを使うケース

単にthisだけを書く

インスタンスメソッド内でthisと書くと、そのメソッドを呼んでいるオブジェクト自体を参照します。具体的には、以下のように自分自身を他のメソッドやコンストラクタに引数として渡す、という使い方が挙げられます。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class User {
private String name;
public User(String name) {
this.name = name;
}
public void printUser() {
// ここでthisを使って自分自身を showInfo メソッドに渡す
showInfo(this);
}
public void showInfo(User user) {
System.out.println("ユーザー名: " + user.name);
}
}
public class User { private String name; public User(String name) { this.name = name; } public void printUser() { // ここでthisを使って自分自身を showInfo メソッドに渡す showInfo(this); } public void showInfo(User user) { System.out.println("ユーザー名: " + user.name); } }
public class User {
    private String name;

    public User(String name) {
        this.name = name;
    }

    public void printUser() {
        // ここでthisを使って自分自身を showInfo メソッドに渡す
        showInfo(this);
    }

    public void showInfo(User user) {
        System.out.println("ユーザー名: " + user.name);
    }
}

このように、同じクラス内でなくても、別のメソッドに「このインスタンス自体を渡したい」といった場合に使えます。

他のメソッドを呼び出す

同じクラス内の別メソッドを呼ぶとき、thisを付けても付けなくても動作は同じです。しかし、意図を明確にしたい場合は以下のように書くこともできます。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class Example {
private int count;
public void increment() {
count++;
}
public void printCount() {
// thisを使ってメソッドを呼び出す例
this.increment();
System.out.println("Count: " + this.count);
}
}
public class Example { private int count; public void increment() { count++; } public void printCount() { // thisを使ってメソッドを呼び出す例 this.increment(); System.out.println("Count: " + this.count); } }
public class Example {
    private int count;

    public void increment() {
        count++;
    }

    public void printCount() {
        // thisを使ってメソッドを呼び出す例
        this.increment();
        System.out.println("Count: " + this.count);
    }
}

機能的には同じでも「同じオブジェクトのメソッドやフィールドを使っている」という読み手への明示的アピールになります。

コンストラクタでthisを使うケース

フィールド初期化

コンストラクタでは、よく上記のように同名の引数を用いてフィールドを初期化するケースがあります。そこでthisを使うと、フィールドと引数の衝突が回避され、かつ分かりやすいコードになります。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class User {
private String name;
private int age;
public User(String name, int age) {
// フィールドと引数が同名の場合
// thisを使わないとフィールドに代入されない
this.name = name;
this.age = age;
}
}
public class User { private String name; private int age; public User(String name, int age) { // フィールドと引数が同名の場合 // thisを使わないとフィールドに代入されない this.name = name; this.age = age; } }
public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        // フィールドと引数が同名の場合
        // thisを使わないとフィールドに代入されない
        this.name = name;
        this.age = age;
    }
}

コンストラクタ引数が増えるにつれ、どうしてもフィールドと同じ名前を使いたい場面が出てきます。これを避けるために、ローカル変数の方の名前を多少変えて書く人もいますが、this.name = name;のように書くのが一般的です。今何が代入されているのか一目で分かるため、初心者のうちはこの書き方で慣れておくと良いでしょう。

コンストラクタの呼び出し(this())

他のコンストラクタを呼ぶためのthis()

コンストラクタの中から、同じクラス内の別のコンストラクタを呼び出す場合にもthisを使います。ただしこのときは、メソッドではなく「コンストラクタ」を呼び出すために、this(...)のような書き方をします。引数の数や型を変えることで、任意の別コンストラクタを呼び出すことが可能です。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class Person {
private String name;
private int age;
public Person() {
// 引数なしコンストラクタから
// 引数ありコンストラクタを呼び出す
this("No name", 0);
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Person { private String name; private int age; public Person() { // 引数なしコンストラクタから // 引数ありコンストラクタを呼び出す this("No name", 0); } public Person(String name, int age) { this.name = name; this.age = age; } }
public class Person {
    private String name;
    private int age;

    public Person() {
        // 引数なしコンストラクタから
        // 引数ありコンストラクタを呼び出す
        this("No name", 0);
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

上記例では、引数なしコンストラクタが呼ばれると、自動的にthis("No name", 0);によって別のコンストラクタに処理を委譲しています。初期化のロジックを1か所にまとめられるため、同じ初期化コードを二重に書く必要がなくなり、保守性が向上します。

注意点

  • this()呼び出しはコンストラクタの先頭行でしか書けないという制約があります。実行コードをこの呼び出しの前に書いてはいけません。
  • this(...)の呼び出しができるのは1箇所だけです。連続して複数のコンストラクタを呼び出すことはできません。

メソッドチェーンとthis

メソッドチェーンとは、「あるメソッドを呼び出した結果として自分自身(同じオブジェクト)を返すことで、その返り値に対してさらにメソッドをつなげて呼べる」という書き方です。イメージとしては、

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
object.methodA().methodB().methodC();
object.methodA().methodB().methodC();
object.methodA().methodB().methodC();

というように、連鎖的にメソッドを呼び出すデザインパターンです。ここでもthisを活用することで、自分自身を戻すメソッドを定義することが多いです。

thisを返す

メソッドチェーンを行うためには、メソッドの返り値をクラス型(自分自身の型)にし、そのメソッドの最後でreturn this;と書きます。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class Counter {
private int count;
public Counter increment() {
this.count++;
return this;
}
public Counter add(int value) {
this.count += value;
return this;
}
public void print() {
System.out.println("Count = " + this.count);
}
}
public class Counter { private int count; public Counter increment() { this.count++; return this; } public Counter add(int value) { this.count += value; return this; } public void print() { System.out.println("Count = " + this.count); } }
public class Counter {
    private int count;

    public Counter increment() {
        this.count++;
        return this; 
    }

    public Counter add(int value) {
        this.count += value;
        return this;
    }

    public void print() {
        System.out.println("Count = " + this.count);
    }
}

上記の例では、Counterクラスのインスタンスは、以下のようなメソッドチェーンが可能になります。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class Main {
public static void main(String[] args) {
Counter c = new Counter();
c.increment().add(5).increment().print();
}
}
public class Main { public static void main(String[] args) { Counter c = new Counter(); c.increment().add(5).increment().print(); } }
public class Main {
    public static void main(String[] args) {
        Counter c = new Counter();
        c.increment().add(5).increment().print();
    }
}

increment()add()の戻り値にthisを返すことで、同じインスタンスに対して連続でメソッドを呼び出す実現ができるわけです。

staticメンバとの関係

参考 staticメンバとは?

staticメソッドやstaticフィールドは、インスタンスではなくクラスに属しています。したがって、staticメソッドの中でthisを使うことはできません。なぜならthisは「インスタンス(具体的なオブジェクト)」を指すものだからです。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class TestStatic {
private int num;
public static void doSomething() {
// エラー:staticメソッド内ではthisが使えない
// System.out.println(this.num);
}
}
public class TestStatic { private int num; public static void doSomething() { // エラー:staticメソッド内ではthisが使えない // System.out.println(this.num); } }
public class TestStatic {
    private int num;

    public static void doSomething() {
        // エラー:staticメソッド内ではthisが使えない
        // System.out.println(this.num);
    }
}
タイトルとURLをコピーしました