web-dev-qa-db-ja.com

ラムダ式の機能インターフェースに注釈を付ける

Java 8では、 Lambda ExpressionsType Annotations の両方が導入されています。

型注釈を使用すると、次のようにJava注釈を定義できます。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface MyTypeAnnotation {
    public String value();
}

次に、この注釈を次のような任意の型参照で使用できます。

Consumer<String> consumer = new @MyTypeAnnotation("Hello ") Consumer<String>() {
    @Override
    public void accept(String str) {
        System.out.println(str);
    }
};

次に、この注釈を使用して「Hello World」を出力する完全な例を示します。

import Java.lang.annotation.ElementType;
import Java.lang.annotation.Retention;
import Java.lang.annotation.RetentionPolicy;
import Java.lang.annotation.Target;
import Java.lang.reflect.AnnotatedType;
import Java.util.Arrays;
import Java.util.List;
import Java.util.function.Consumer;

public class Java8Example {
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE_USE)
    public @interface MyTypeAnnotation {
        public String value();
    }

    public static void main(String[] args) {
        List<String> list = Arrays.asList("World!", "Type Annotations!");
        testTypeAnnotation(list, new @MyTypeAnnotation("Hello ") Consumer<String>() {
            @Override
            public void accept(String str) {
                System.out.println(str);
            }
        });
    }

    public static void testTypeAnnotation(List<String> list, Consumer<String> consumer){
        MyTypeAnnotation annotation = null;
        for (AnnotatedType t : consumer.getClass().getAnnotatedInterfaces()) {
            annotation = t.getAnnotation(MyTypeAnnotation.class);
            if (annotation != null) {
                break;
            }
        }
        for (String str : list) {
            if (annotation != null) {
                System.out.print(annotation.value());
            }
            consumer.accept(str);
        }
    }
}

出力は次のようになります。

Hello World! 
Hello Type Annotations!

Java 8では、この例の匿名クラスをラムダ式に置き換えることもできます。

public static void main(String[] args) {
    List<String> list = Arrays.asList("World!", "Type Annotations!");
    testTypeAnnotation(list, p -> System.out.println(p));
}

ただし、コンパイラはラムダ式のConsumer型の引数を推測するため、作成されたConsumerインスタンスに注釈を付けることはできなくなります。

testTypeAnnotation(list, @MyTypeAnnotation("Hello ") (p -> System.out.println(p))); // Illegal!

ラムダ式をConsumerにキャストしてから、キャスト式の型参照に注釈を付けることができます。

testTypeAnnotation(list,(@MyTypeAnnotation("Hello ") Consumer<String>) (p -> System.out.println(p))); // Legal!

ただし、作成されたConsumerクラスにはキャスト式の注釈が付けられないため、これでは目的の結果が得られません。出力:

World!
Type Annotations!

2つの質問:

  1. 対応する匿名クラスに注釈を付けるのに似たラムダ式に注釈を付ける方法はありますか。したがって、上記の例で予想される「Hello World」出力を取得できますか?

  2. 例では、ラムダ式をキャストし、キャストされた型に注釈を付けました:実行時にこの注釈インスタンスを受け取る方法はありますか、またはそのような注釈は常に暗黙的にRetentionPolicy.SOURCEに制限されていますか?

サンプルはjavacとEclipseコンパイラでテストされています。

更新

代わりにパラメーターに注釈を付ける@assyliasからの提案を試みましたが、興味深い結果が得られました。更新されたテストメソッドは次のとおりです。

public static void testTypeAnnotation(List<String> list, Consumer<String> consumer){
    MyTypeAnnotation annotation = null;
    for (AnnotatedType t :  consumer.getClass().getAnnotatedInterfaces()) {
        annotation = t.getAnnotation(MyTypeAnnotation.class);
        if (annotation != null) {
            break;
        }
    }
    if (annotation == null) {
            // search for annotated parameter instead
        loop: for (Method method : consumer.getClass().getMethods()) {
            for (AnnotatedType t : method.getAnnotatedParameterTypes()) {
                annotation = t.getAnnotation(MyTypeAnnotation.class);
                if (annotation != null) {
                    break loop;
                }
            }
        }
    }
    for (String str : list) {
        if (annotation != null) {
            System.out.print(annotation.value());
        }
        consumer.accept(str);
    }
}

これで、匿名クラスのパラメーターに注釈を付けるときに、「Hello World」結果を生成することもできます。

public static void main(String[] args) {
    List<String> list = Arrays.asList("World!", "Type Annotations!");
    testTypeAnnotation(list, new Consumer<String>() {
        @Override
        public void accept(@MyTypeAnnotation("Hello ") String str) {
            System.out.println(str);
        }
    });
}

ただし、パラメーターに注釈を付けると、ラムダ式に対してnotが機能します。

public static void main(String[] args) {
    List<String> list = Arrays.asList("World!", "Type Annotations!");
    testTypeAnnotation(list, (@MyTypeAnnotation("Hello ") String str) ->  System.out.println(str));
}

興味深いことに、ラムダ式を使用する場合、パラメーターの名前を受け取ることもできません(javac -parameterを使用してコンパイルする場合)。ただし、この動作が意図されているのか、ラムダのパラメーターアノテーションがまだ実装されていないのか、またはコンパイラのバグと見なされるのかはわかりません。

51
Balder

役立つ可能性のある回避策の1つは、ラムダが実装するインターフェイスを拡張する空のインターフェイスを定義し、注釈を使用するためだけにこの空のインターフェイスにキャストすることです。そのようです:

import Java.lang.annotation.ElementType;
import Java.lang.annotation.Retention;
import Java.lang.annotation.RetentionPolicy;
import Java.lang.annotation.Target;
import Java.util.function.Consumer;

public class Main
{
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE_USE)
    public @interface MyAnnotation {
        public String value();
    }

    @MyAnnotation("Get this")
    interface AnnotatedConsumer<T> extends Consumer<T>{};

    public static void main( String[] args )
    {
        printMyAnnotationValue( (AnnotatedConsumer<?>)value->{} );
    }

    public static void printMyAnnotationValue( Consumer<?> consumer )
    {
        Class<?> clas = consumer.getClass();
        MyAnnotation annotation = clas.getAnnotation( MyAnnotation.class );
        for( Class<?> infClass : clas.getInterfaces() ){
            annotation = infClass.getAnnotation( MyAnnotation.class );
            System.out.println( "MyAnnotation value: " + annotation.value() );
        }
    }
}

アノテーションは、クラスによって実装されたインターフェイスで使用でき、同じアノテーションを別の場所で使用する場合は再利用できます。

2
user2219808