🌟

「ネストしたクラス」まとめ[Java入門]

に公開

はじめに

こんにちは。
プログラミング初心者wakinozaと申します。
Java勉強中に調べたことを記事にまとめています。

十分気をつけて執筆していますが、なにぶん初心者が書いた記事なので、理解が浅い点などあるかと思います。
間違い等あれば、指摘いただけると助かります。

記事を参考にされる方は、初心者の記事であることを念頭において、お読みいただけると幸いです。

対象読者

  • Javaを勉強中の方
  • Java SE11 Gold試験を勉強中の方
  • Javaの「ネストしたクラス」について知りたい方

目次

1. 入れ子クラスとは
2. インナークラス
3. staticインナークラス
4. ローカルクラス
5. 匿名クラス

本文

1. 入れ子クラスとは

Java言語では、クラス宣言ブロックの中に、さらにクラス宣言を書くことができます。このクラス宣言の内側に定義するクラスのことを、「入れ子クラス」と言います。

入れ子クラスは、4種類に分類されます。

  • インナークラス
  • staticインナークラス
  • ローカルクラス
  • 匿名クラス

4種類のうち、「インナークラス」「ローカルクラス」「匿名クラス」の非staticな入れ子クラスをまとめて「インナークラス」と呼ぶ場合もあります。

入れ子クラスを囲む外側のクラスのことを、「囲んでいるクラス」という意味で、「エンクロージングクラス」と呼びます。

入れ子クラスの利用頻度は低いですが、これらをうまく利用することで「カプセル化」や「情報隠蔽」の強化を実現できます。

「カプセル化」とは、関連するものを1つにまとめることです。
例えば、特定のクラスAと密接に関係があるクラスBを、クラスAの入れ子クラスにすることで、両者の密接な関係をコード上で表現できます。関連するコードをまとめると、コードを変更する際に修正箇所を把握するのが容易になります。

また、「情報隠蔽」とは、カプセル化された内部情報を隠蔽し、不必要に利用されることを防ぐ仕組みです。
インナークラスやstaticインナークラスは、「private」を利用できます。privateな入れ子クラスは、エンクロージングクラスの外側からはアクセスができません。また、ローカルクラスや匿名クラスは、入れ子クラスを囲むメソッドの外部からはアクセスできません。これら入れ子クラスの特性を利用することで、情報隠蔽の強化が実現できるのです。

入れ子クラスは、種類によって、利用方法・アクセス修飾子・アクセスできる範囲が違います。それぞれの違いと特徴を理解した上で、適切な入れ子クラスを選ぶ必要があります。

次の節から、それぞれの入れ子クラスの特徴を見ていきます。

2. インナークラス

インナークラスは、エンクロージングクラスのメンバとして定義するクラスです。

宣言場所は、クラスブロックの内側で、かつメソッドブロックの外側です。

アクセス制御も可能です。
アクセス修飾子は、「public」、「無指定」の他に「protected」と「private」も利用でき、finalやabstract・extends・implementsも利用できます。「private」のアクセス修飾子を使うと、 エンクロージングクラス以外からはアクセスできなくなります。

また、 インナークラスからは、エンクロージングクラスのインスタンスメンバやstaticメンバ両方にアクセスすることができます。

インナークラスを利用する場合、 利用するクラスがエンクロージングクラスか無関係なクラスかによって利用の仕方が違います。

コードを見てみましょう。

public class Outer { // エンクロージングクラス
  int outerField;  // エンクロージングクラスのフィールド

  class Inner { // インナークラス
    void innerMethod(){
      outerField = 10;
    }
  }

  void outerMethod(){ // エンクロージングクラスのメソッド
    Inner ic = new Inner();
  }
}
public class Main {
  public static void main(String[] args){
    Outer o = new Outer();  // エンクロージングクラスのインスタンスを生成
    Outer.Inner oi = o.new Inner();
    // エンクロージングクラスのインスタンスを利用して、インナークラスのインスタンスを生成
  }
}

上のコードで、 Outerクラスの中でInnerクラスが定義されています。Outerクラスが、インナークラスのエンクロージングクラスです。
エンクロージングクラスからインナークラスを利用する場合は、クラス名で利用可能です。そのため、OuterクラスのouterMethod()では、 クラス名のみでInnerのインスタンスが生成されています。
しかし、エンクロージングクラスと無関係なクラスからはインナークラスを利用する場合は、 エンクロージングクラスのインスタンスが必要となります。 それは、インナークラスがエンクロージングクラスのインスタンスと強い結びつきを持っているからです。
無関係なクラスからインナークラスを利用する場合は、 まず、エンクロージングクラスのインスタンスを生成し、 そのインスタンスと結びつく形で、インナークラスのインスタンスを生成します。

3. staticインナークラス

staticインナークラスは、 インナークラスと同様、エンクロージングクラスのメンバとして定義するクラスです。
クラス宣言時に「static」キーワードを付与すると、staticインナークラスとなります。

宣言場所もインナークラスと同様、クラスブロックの内側で、かつメソッドブロックの外側です。

アクセス制御も インナークラスと同じです。
アクセス修飾子は、「public」、「無指定」の他に「protected」と「private」も利用でき、finalやabstract・extends・implementsも利用できます。「private」のアクセス修飾子を使うと、 エンクロージングクラス以外からはアクセスできなくなります。

インナークラスとの違いは、「アクセスできる範囲」と「利用方法」です。

まず、 アクセスできる範囲の違いについて説明します。
インナークラスは、エンクロージングクラスのインスタンスメンバとstaticメンバ両方にアクセスが可能でした。
もう一方のstaticインナークラスは、エンクロージングクラスのstaticメンバーにはアクセスできますが、インスタンスメンバにはアクセスできません。

これは、「static」の制約によるものです。
staticメンバは、クラスがロードされたタイミングですぐに利用可能になります。しかし、インスタンスメンバは、インスタンスを生成して初めて使えるようになります。このように、staticメンバとインスタンスメンバは、利用可能なタイミングが違っています。このタイミングの差に由来するエラーを防ぐため、staticメンバはインスタンスメンバにアクセスできない仕様となっているのです。
この制約は、staticインナークラスにも当てはまります。staticインナークラスは、「static」 であるが故に、 エンロージングクラスのインスタンスメンバにアクセスできないのです。

次に、利用方法の違いについて説明します。

public class Outer { // エンクロージングクラス
  int outerField;  // エンクロージングクラスのインスタンスフィールド
  static int outerStaticField; // エンクロージングクラスのstaticフィールド

  static class Inner { // インナークラス
    void innerMethod(){
      outerField = 10;  //エラー:インスタンスメンバにアクセスしている
      outerStaticField = 20;
    }
  }

  void outerMethod(){ // エンクロージングクラスのメソッド
    Inner ic = new Inner();
  }
}
public class Main {
  public static void main(String[] args){
    Outer.Inner ic = new Outer.Inner();
  }
}

無関係なクラスからインナークラスを利用する場合は、 エンクロージングクラスのインスタンスを生成し、そのインスタンスを利用して、インナークラスのインスタンスを生成していました。
しかし、「static」インナークラスは、 エンクロージングクラスの インスタンスを必要としません。 無関係なクラスから利用する場合は、「エンクロージングクラス名.staticインナークラス名」で利用できます。

4. ローカルクラス

ローカルクラスは、メソッドブロック内で宣言されるクラスです。
ローカルクラスはメソッド内に定義しているため、たとえ同じクラスであったとしても、他のメソッドからアクセスできません。これはローカル変数が同じクラスの別のメソッドからアクセスできないのと同じ理由です。

アクセス制御を指定できないため、アクセス修飾子はつけられません。
final・abstract・extends・implementsなどを宣言することは可能です。

また、 ローカルクラスからは、エンクロージングクラスのインスタンスメンバやstaticメンバにアクセスすることができます。

public class Outer { //エンクロージングクラス
  int outerMember = 2;
  int outerStaticMember = 4;
  void outerMethod() { //ローカルクラスを囲むメソッド
    int a = 0;  
    class Inner { // ローカルクラス
      public void innerMethod(){
        System.out.println("innerMethodです");
        System.out.println(outerMember + outerStaticMember + a);
      }
    }
    Inner ic = new Inner(); // ローカルクラスのインスタンス生成
    ic.innerMethod(); // ローカルクラスのインスタンスの利用
  }
}

ローカルクラスで注意が必要なのは、ローカルクラスを取り囲むメソッド内のローカル変数を利用する場合です。
ローカルクラスは、ローカルクラスを取り囲むメソッド内のローカル変数を利用することができますが、利用できるのは「final」もしくは「事実上final」の変数に限られます。
「事実上final」とは、finalは宣言されていないが、コード中に値が再代入されていない変数を、暗黙的にfinalとみなすという仕組みです。 もしローカルクラス内で利用しているローカル変数が、どこかで再代入されていると、コンパイルエラーとなります。

ローカルクラスが参照するローカル変数を再代入できない理由は、 ローカル変数とローカルクラスのインスタンスのライフサイクルの違いにあります。

ローカル変数は、メソッドの処理が終わると、メモリ上から消えてしまいます。 一方のローカルクラスのインスタンスは、その参照が残っていれば、メソッドの処理が終了しても、メモリ上に存在し続けます。
このライフサイクルの違いにより、ローカルクラスのインスタンスからローカル変数にアクセスしても、すでにメモリから消去されていて値を取得できないと言う事態が発生してしまいます。
このような事態を防ぐため、ローカルクラスをインスタンスを生成したときに、ローカル変数の値をコピーしたものをインスタンスに渡します。このローカル変数の値のコピーを、インスタンスの内部に保存する仕組みを「クロージャ」と言います。こうすることで、ローカル変数が消えてしまっても、ローカルクラスのインスタンスが正常に作動できるのです。

もしローカルクラスが参照しているローカル変数に 再代入が可能だとすると、ローカル変数とローカルクラスのインスタンス内のローカル変数のコピーが食い違うと言う事態が発生してしまいます。 このような食い違いを防ぐため、ローカルクラスが参照しているローカル変数は、再代入が禁止されているのです。

5. 匿名クラス

匿名クラスとは、インターフェースの実装やクラスの継承を、クラス名を定義することなく、インスタンス生成と同時に行うための構文です。
匿名クラスは宣言したその場で利用するため、あるメソッドの中で1度しか使わない使い捨てのクラスを宣言するのに用いられます。

まずは、コードを確認しましょう。

public class Main {
  public static void main (String[] args){
    Runnable runner = new Runnable() {
    @Override
    public void run() {
        System.out.println("匿名クラスの実装");
    }
    };
    runner.run();
  }
}

new Runnable(){...}の部分で、匿名クラスの宣言とインスタンス生成を行っています。new Runnableと記述されていますが、Runnableインターフェースをnewしている訳ではないという点に注意が必要です。Runnableインターフェースを継承して、メンバを追加したり、オーバーライドしたりして、実装クラスを定義してから、その実装クラスをnewしているのです。

匿名クラスは、クラス宣言と同時にインスタンスも生成するため、クラスの名前を定義する必要はありません。その代わり、「どのクラスやインターフェースを継承しているのか」を宣言する決まりになっているのです。

匿名クラスは、アクセス修飾子もなく、finalやabstractも利用できません。extendsやimplementsは利用できます。

また、 エンクロージングクラスのインスタンスメンバやstaticメンバは利用できます。
匿名クラスを取り囲むメソッドのローカル変数は、ローカルクラスと同様、finalか事実上finalの時のみ利用できます。

匿名クラスで注意が必要なのは、コンストラクタが定義できない点です。
コンストラクタは、クラス名と同名で定義しますが、匿名クラスはクラス名を定義しないため、コンストラクタが定義できません。初期化処理を記載したい場合は、初期化子{}を利用します。

また、現在のJavaでは関数型インターフェースを用いたラムダ式という記述方法が可能です。関数型インターフェースを実装する場合は、匿名クラスではなく、より簡易なラムダ式での記述が推奨されています。
しかし、ラムダ式では複数の抽象メソッドを持つインターフェースやクラスは扱えません。ラムダ式では記述できないケースにおいて、現在も匿名クラスが利用さています。

まとめ

  • Javaの入れ子クラスは、クラスの内部で別のクラスを定義する機能で、コードのカプセル化や情報隠蔽を強化するのに役立つ

  • インナークラスは、エンクロージングクラスのインスタンスに紐づくインナークラスと、インスタンスに依存しないstaticインナークラスに分けられる

  • ローカルクラスは、メソッド内で定義され、定義されたメソッド内でのみ利用できるのが特徴
    å

  • 匿名クラスは名前を持たず、インターフェースの実装やクラスの継承とインスタンス生成を同時に行う。使い捨てのクラスとして利用される


記事は以上です。
最後までお読みいただき、ありがとうございました。

参考情報一覧

この記事は以下の情報を参考にして執筆しました。

GitHubで編集を提案

Discussion