JPCERT コーディネーションセンター

MET07-J. スーパークラスやスーパーインタフェースで宣言されているメソッドを隠蔽するようなクラスメソッドを宣言しない

クラスが静的メソッド m を宣言するとき、m の宣言は、m の宣言が含まれるクラスのスーパークラスやスーパーインタフェースで宣言されたメソッドのうち、m のシグネチャがサブシグネチャになっており、m が宣言されるクラスでアクセス可能な任意のメソッドを隠蔽してしまう(Java言語仕様, §8.4.8.2 "(クラスメソッドによる)隠蔽"[JLS 2005])。

サブクラスのインスタンスメソッドは、同じ名前、引数の数と型、返り値の型を持つスーパークラスのメソッドをオーバーライドする。

隠蔽(hiding)とオーバーライド(overriding)の違いは、呼び出すメソッドの特定方法にある。オーバーライドの場合、呼び出すメソッドはオブジェクトインスタンスの状態に応じて実行時に决まる。隠蔽の場合、呼び出すメソッドは修飾名やメソッド呼び出す式に応じてコンパイル時に决まる。Java 言語では、どちらのメソッドが呼び出されるかを規定するルールを明確に定めているが、ルールの結果はしばしば予期せぬものとなる。さらにプログラマは、メソッドが隠蔽される場面でメソッドのオーバーライドが起こると考えることがある。したがって、スーパークラスやスーパーインタフェースで宣言されているメソッドを隠蔽するようなクラスメソッドを宣言してはならない。

違反コード

以下の違反コード例では、static メソッドをオーバーライドではなく隠蔽している。そのため、2つの displayAccountStatus() メソッド呼び出しは、一方がスーパークラスのメソッド、他方がサブクラスのメソッドを呼び出すのではなく、どちらもスーパークラスのメソッドを呼び出す。

class GrantAccess {
  public static void displayAccountStatus() {
    System.out.println("Account details for admin: XX");
  }
}

class GrantUserAccess extends GrantAccess {
  public static void displayAccountStatus() {
    System.out.println("Account details for user: XX");
  }
}

public class StatMethod {
  public static void choose(String username) {
    GrantAccess admin = new GrantAccess();
    GrantAccess user = new GrantUserAccess();
    if (username.equals("admin")) {
      admin.displayAccountStatus();
    } else {
      user.displayAccountStatus();
    }
  }

  public static void main(String[] args) {
    choose("user");
  }
}
適合コード

以下の適合コードでは、static キーワードを削除して displayAccountStatus() メソッドをインスタンスメソッドとして宣言している。その結果、呼び出し側における動的ディスパッチが行われ、意図した結果が得られる。@Override アノテーションは親メソッドを意図的にオーバーライドしていることを示す。

class GrantAccess {
  public void displayAccountStatus() {
    System.out.print("Account details for admin: XX");
  }
}

class GrantUserAccess extends GrantAccess {
  @Override
  public void displayAccountStatus() {
    System.out.print("Account details for user: XX");
  }
}

public class StatMethod {
  public static void choose(String username) {
    GrantAccess admin = new GrantAccess();
    GrantAccess user = new GrantUserAccess();

    if (username.equals("admin")) {
      admin.displayAccountStatus();
    } else {
      user.displayAccountStatus();
    }
  }

  public static void main(String[] args) {
    choose("user");
  }
}

スーパークラスから継承したメソッドをサブクラスでオーバロードすることもできる。オーバロードしたメソッドはサブクラス内でユニークなメソッドであり、スーパークラスのメソッドを隠蔽したりオーバーライドしたりするものではない[Tutorials 2008]。

技術的には、private メソッドは隠蔽もオーバーライドもできない。スーパークラスとサブクラスに同一のシグネチャを持つ private メソッドがあっても、返り値の型や throws 節が同じであるという隠蔽に必要な条件を持たねばならないというきまりはない[JLS 2005]。そのため、private メソッドが異なる返り値の型や throws 節を持つ場合、隠蔽は発生しない。

例外

API が隠蔽されたメソッドを提供することがある。そのようなメソッドのすべての呼び出しが、修飾名による呼び出しや、どのメソッドを呼び出しているか明示的に指定した呼び出し式によるものであれば、このルールの違反には当たらない。たとえば displayAccountStatus() が隠蔽されたメソッドであるならば、以下の choose() メソッドの実装は許される。

public static void choose(String username) {
    if (username.equals("admin")) {
      GrantAccess.displayAccountStatus();
    } else {
      GrantUserAccess.displayAccountStatus();
    }
  }
リスク評価

オーバーライドと隠蔽を混同すると予期せぬ結果を招くことがある。

ルール 深刻度 可能性 修正コスト 優先度 レベル
MET07-J P2 L3
自動検出

このルールへの違反の自動検出は簡単であるが、メソッド隠蔽が避けられないことを判別するのは困難である。しかし、他のメソッドを隠蔽するメソッドや、他のメソッドによって隠蔽されるメソッドがどのメソッドを呼び出すかを判別するのは簡単である。

参考文献
[Bloch 2005] Puzzle 48. All I get is static
[JLS 2005] §8.4.8.2, Hiding (by Class Methods)
[Tutorials 2008] Overriding and Hiding Methods
翻訳元

これは以下のページを翻訳したものです。

MET07-J. Never declare a class method that hides a method declared in a superclass or superinterface (revision 60)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter