0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

実行時にアノテーション@Overrideが見つからなかった話

Posted at

これはなんですか

Java屋さん向けのjava.lang.Classやjava.lang.Methodやアノテーションを使ったあれやこれやする浅瀬くらいの深みな話。
ソース中でstream構文等を使用しているためjava9未満のバージョンをお使いの方はよくわからないかもしれませんが、心の目で見ればわかる、かも。

やりたいこと

  1. インターフェースに書いてあるメソッドをimplementsしたメソッドに @Override を書いてない輩を見つけたらあんじょうよろしゅうしたい
  2. そうだ、単体テストで見つけよう!
  3. 結論:だめでした

検証その1 指定したクラスのメソッド一覧を取得する

単体テストのコード

import java.util.Arrays;
import org.junit.jupiter.api.Test;

public class FindOverrideTest {

  @Test
  public void test() {
    Arrays.stream(Class1.class.getMethods())
    .forEach(method -> System.out.println(method.getName()));
  }

  interface Interface1 {

    void methodHasOverrideAnno();

    void methodHasNoOverrideAnno();
  }

  class Class1 implements Interface1 {

    @Override
    @Annotation1
    public void methodHasOverrideAnno() {

    }

    // @Override
    @Annotation1
    public void methodHasNoOverrideAnno() {

    }
  }
}
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Annotation1 {
}

実行すると得られる出力

methodHasOverrideAnno
methodHasNoOverrideAnno
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

すべてのクラスは暗黙で java.lang.Object を継承しているため何も extends してないクラスでも wait, equals, toString等の Object のメソッドが見える

検証その2 Class1 に記述されたメソッドを検出したい場合どうしたらいいか?

  @Test
  public void test() {
    Arrays.stream(Class1.class.getMethods())
        .filter(method -> Class1.class.equals(method.getDeclaringClass())) // 後述のフィルタを追加 ※1
        .forEach(method -> System.out.println(method.getName()));
  }

method.getDeclaringClass() がそのメソッドが宣言されたクラスを返してくれるため、対象のクラスと同一であるかをチェックするフィルタを足します。

実行すると得られる出力

methodHasOverrideAnno
methodHasNoOverrideAnno

検証その3 あのアノテーションをアレせよ

メソッドにくっついてるアノテーションを列挙してみます。

  @Test
  public void test() {
    Arrays.stream(Class1.class.getMethods())
        .filter(method -> Class1.class.equals(method.getDeclaringClass()))
        .forEach(method -> {
          System.out.println(method.getName());
          System.out.println(" Annotations = " + Arrays.stream(method.getDeclaringAnnotations()) // 追加
              .map(annotation -> annotation.annotationType().getName()).collect(                 // 追加
              Collectors.joining(",")));                                                         // 追加
        });
  }

補足: 追加2行目の annotation.annotationType() で正しいクラスが取得できた

実行すると得られる出力

methodHasOverrideAnno
 Annotations = Annotation1
methodHasNoOverrideAnno
 Annotations = Annotation1

あれ、おかしいな java.lang.Override がいない・・なぜだ。

・・・・・・

どうやら java.lang.Override@Retention(RetentionPolicy.SOURCE) による宣言で、ソースファイルのみで保持されるものでした。※2

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

これを単体テストなどの実行時に検出するためには、RetentionPolicy は Runtime である必要がある。※2

本来ならこの処理はソースコードをチェックするSonarCubeとか、CodeGuruあたりでチェックすべきところだということがわかりました。

めでたしめでたし。

あとがき なぜOverrideを書いてない輩を誅したいか、について

openapi-code-generator(※3) でコントローラーのインターフェースを生成すると、defaultなメソッドが作成されるため、コンパイルが通ってしまい、インターフェースと実装されたクラスの乖離に気づかないことがあります。
openapi-code-generator側にはdefault宣言を抑止するフラグがなかったように思いますが、本当にそうだったかは要再確認。

このアプローチとは違うやり方ですが、実装されていないインターフェースのメソッドを発見したらあんじょうよろしゅうするという単体テストは記述できたので、結果としてよしとしてます。

参照

※1 https://stackoverflow.com/questions/2315445/how-to-quickly-determine-if-a-method-is-overridden-in-java 宣言されたクラス名と拾ったクラス名が一致しないと対象のクラスで実装されたことにならないということを教えてくれた記事 他に方法があれば教えて下さい

※2 https://itsakura.com/java-annotation-make アノテーションの説明です めったに使わないので RetentionPolicy.SOURCE の存在を失念していました。

※3 https://github.com/OpenAPITools/openapi-generator

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?