2014-03-13 33 views
45

Java 8引入了Lambda ExpressionsType Annotations註釋Lambda表達式的功能接口

隨着類型的註釋,就可以定義諸如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); 
     } 
    } 
} 

o安輸出將是:

Hello World! 
Hello Type Annotations! 

在Java 8一個也可以用lambda表達式替換匿名類在這個例子中:

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

但由於編譯器推斷消費者類型參數爲λ表達式,一個不再能夠註釋所創建的消費者實例:

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

人們可以投lambda表達式到消費者,然後註釋CA的類型參考ST表達:

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

但這不會產生期望的結果,因爲創建Consumer類不會投表達的註解。輸出:

World! 
Type Annotations! 

兩個問題:

  1. 有什麼辦法來標註類似標註相應的匿名類lambda表達式,所以一得到預期的「Hello World」輸出在上面的例子?

  2. 在這個例子中,我確實投了lambda表達式並註釋了鑄造類型:有沒有什麼辦法在運行時接收這個註解實例,或者這樣的註解總是隱式地限制到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); 
     } 
    }); 
} 

但標註的參數不不lambda表達式工作:

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

有趣的是,它也不可能接收參數的名稱(當compil在使用lambda表達式時使用javac -parameter)。我不確定,如果這種行爲是有意的,如果lambda參數註釋尚未實現,或者這應該被視爲編譯器的錯誤。

+3

您也可以嘗試'(@MyTypeAnnotation( 「你好」)的String) - >的System.out.println(S)'我雖然沒有設法訪問註釋值... – assylias

+0

@assylias感謝您的輸入 - 好主意。我做了一些帶有註釋參數的測試,並將結果添加到我的問題的更新部分。 – Balder

+2

@assylias更新:無法接收類型註釋的lambda表達式的形式參數,如在您的示例中,最有可能與[JDK bug 8027181](https://bugs.openjdk.java.net/瀏覽/ JDK-8027181)和[Eclipse bug 430571](https://bugs.eclipse.org/bugs/show_bug.cgi?id=430571)。 – Balder

回答

32

挖掘到Java SE 8 Final Specification後,我可以回答我的問題。

(1)在回答我的第一個問題

有什麼辦法來標註類似註釋 相應的匿名類lambda表達式,所以一得到預期的「Hello 世界」輸出在上面的例子中?

當註釋匿名類型的Class Instance Creation Expression (§15.9)號,則該註釋將被存儲在類文件或者用於延伸接口或延伸類匿名類型的。

對於以下匿名界面註釋

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

類型標註然後可以在運行時通過調用Class#getAnnotatedInterfaces()訪問:

MyTypeAnnotation a = c.getClass().getAnnotatedInterfaces()[0].getAnnotation(MyTypeAnnotation.class); 

如果用空體這樣創建匿名類:

class MyClass implements Consumer<String>{ 
    @Override 
    public void accept(String str) { 
     System.out.println(str); 
    } 
} 
Consumer<String> c = new @MyTypeAnnotation("Hello ") MyClass(){/*empty body!*/}; 

類型註釋也可以在運行時通過調用Class#getAnnotatedSuperclass()訪問:

MyTypeAnnotation a = c.getClass().getAnnotatedSuperclass().getAnnotation(MyTypeAnnotation.class); 

這種類型的註釋是可能lambda表達式。

在一個側面說明,這種標註的也是普通的類實例創建表達式這樣不可能的:

Consumer<String> c = new @MyTypeAnnotation("Hello ") MyClass(); 

在這種情況下,該類型的註釋將被存儲在該方法的method_info structure,表達式發生的地方,而不是作爲類型本身(或其任何超類型)的註釋。

這個區別很重要,因爲存儲在method_info中的註釋而不是可以在運行時由Java反射API訪問。當與ASM生成的字節碼看,差異是這樣的:

上的匿名接口實例創建類型標註:

NEW Java8Example$MyClass 
@Java8Example$MyTypeAnnotation(value="Hello ") : NEW, null 

@Java8Example$MyTypeAnnotation(value="Hello ") : CLASS_EXTENDS 0, null 
// access flags 0x0 
INNERCLASS Java8Example$1 

上一個普通的類實例的創建類型的註釋

雖然在第一種情況下,註釋與內部類別相關聯,但在第二種情況下,註釋與實例相關聯在方法字節碼裏面創建表達式。

(2)在回答@assylias評論

你也可以試試(@MyTypeAnnotation( 「你好」)的String) - > 的System.out.println(S)雖然我沒有設法訪問 註釋值...

是的,這實際上可以根據Java 8規範。但目前不可能通過Java反射API接收lambda表達式的形式參數的類型註釋,這很可能與此JDK bug有關:Type Annotations Cleanup。此外Eclipse編譯器還沒有存儲相關運行[在] VisibleTypeAnnotations在類文件屬性 - 相應的錯誤是在這裏找到:Lambda parameter names and annotations don't make it to class files.

(3)在回答我的第二個問題

在這個例子中,我確實投了lambda表達式並註釋了鑄造類型:有沒有什麼辦法在運行時接收這個註解實例 ,或者這樣的註解總是隱式地限制爲 RetentionPolicy.SOURCE?

註釋轉換表達式的類型時,該信息也存儲在類文件的method_info結構中。其他可能的類型註釋位置也是如此,例如, if(c instanceof @MyTypeAnnotation Consumer)。目前沒有公共的Java反射API來訪問這些代碼註釋。但是,由於它們存儲在類文件中,所以至少有可能在運行時訪問它們 - 例如,通過使用外部庫如ASM讀取類的字節碼。

其實,我設法讓我的「Hello World」示例解析使用ASM調用方法的字節碼轉換表達式那樣工作

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

。但是代碼非常黑客而且效率低下,在生產代碼中可能永遠不會做這樣的事情。反正,只是爲了完整,這裏是整個工作的「Hello World」的例子:

import java.lang.annotation.Annotation; 
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.lang.reflect.Method; 
import java.net.URL; 
import java.util.Arrays; 
import java.util.List; 
import java.util.function.Consumer; 

import org.objectweb.asm.AnnotationVisitor; 
import org.objectweb.asm.ClassReader; 
import org.objectweb.asm.ClassVisitor; 
import org.objectweb.asm.Label; 
import org.objectweb.asm.MethodVisitor; 
import org.objectweb.asm.Opcodes; 
import org.objectweb.asm.TypePath; 
import org.objectweb.asm.TypeReference; 

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); 
      } 
     }); 
     list = Arrays.asList("Type-Cast Annotations!"); 
     testTypeAnnotation(list,(@MyTypeAnnotation("Hello ") Consumer<String>) (p -> System.out.println(p))); 
    } 

    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; 
        } 
       } 
      } 
     } 
     if (annotation == null) { 
      annotation = findCastAnnotation(); 
     } 
     for (String str : list) { 
      if (annotation != null) { 
       System.out.print(annotation.value()); 
      } 
      consumer.accept(str); 
     } 
    } 

    private static MyTypeAnnotation findCastAnnotation() { 
     // foundException gets thrown, when the cast annotation is found or the search ends. 
     // The found annotation will then be stored at foundAnnotation[0] 
     final RuntimeException foundException = new RuntimeException(); 
     MyTypeAnnotation[] foundAnnotation = new MyTypeAnnotation[1]; 
     try { 
      // (1) find the calling method 
      StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); 
      StackTraceElement previous = null; 
      for (int i = 0; i < stackTraceElements.length; i++) { 
       if (stackTraceElements[i].getMethodName().equals("testTypeAnnotation")) { 
        previous = stackTraceElements[i+1]; 
       } 
      } 
      if (previous == null) { 
       // shouldn't happen 
       return null; 
      } 
      final String callingClassName = previous.getClassName(); 
      final String callingMethodName = previous.getMethodName(); 
      final int callingLineNumber = previous.getLineNumber(); 
      // (2) read and visit the calling class 
      ClassReader cr = new ClassReader(callingClassName); 
      cr.accept(new ClassVisitor(Opcodes.ASM5) { 
       @Override 
       public MethodVisitor visitMethod(int access, String name,String desc, String signature, String[] exceptions) { 
        if (name.equals(callingMethodName)) { 
         // (3) visit the calling method 
         return new MethodVisitor(Opcodes.ASM5) { 
          int lineNumber; 
          String type; 
          public void visitLineNumber(int line, Label start) { 
           this.lineNumber = line; 
          }; 
          public void visitTypeInsn(int opcode, String type) { 
           if (opcode == Opcodes.CHECKCAST) { 
            this.type = type; 
           } else{ 
            this.type = null; 
           } 
          }; 
          public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { 
           if (lineNumber == callingLineNumber) { 
            // (4) visit the annotation, if this is the calling line number AND the annotation is 
            // of type MyTypeAnnotation AND it was a cast expression to "java.util.function.Consumer" 
            if (desc.endsWith("Java8Example$MyTypeAnnotation;") && this.type != null && this.type.equals("java/util/function/Consumer")) { 
             TypeReference reference = new TypeReference(typeRef); 
             if (reference.getSort() == TypeReference.CAST) { 
              return new AnnotationVisitor(Opcodes.ASM5) { 
               public void visit(String name, final Object value) { 
                if (name.equals("value")) { 
                 // Heureka! - we found the Cast Annotation 
                 foundAnnotation[0] = new MyTypeAnnotation() { 
                  @Override 
                  public Class<? extends Annotation> annotationType() { 
                   return MyTypeAnnotation.class; 
                  } 
                  @Override 
                  public String value() { 
                   return value.toString(); 
                  } 
                 }; 
                 // stop search (Annotation found) 
                 throw foundException; 
                } 
               }; 
              }; 
             } 
            } 
           } else if (lineNumber > callingLineNumber) { 
            // stop search (Annotation not found) 
            throw foundException; 
           } 
           return null; 
          }; 

         }; 
        } 
        return null; 
       } 
      }, 0); 
     } catch (Exception e) { 
      if (foundException == e) { 
       return foundAnnotation[0]; 
      } else{ 
       e.printStackTrace(); 
      } 
     } 
     return null; 
    } 
} 
+4

感謝分享!我可以大量使用更好的lambda表達式反射功能......我真的希望Oracle很快解決這個問題。 – Philip

2

一個可能的解決辦法可能是使用的是定義擴展的拉姆達是要實現接口和空接口然後將其轉換爲空白界面以使用註釋。像這樣:

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()); 
     } 
    } 
} 

的註釋然後由該類實現的接口可用,如果你想在其他地方同樣的註釋是可重複使用的。

+0

但是這種打樣類型註釋的用處呢?每次聲明一個新的帶註釋的接口,只是使用lambda表達式而不是註解的匿名接口看起來有點矯枉過正;)您能否想到您的建議方法的任何實際用例? – Balder

+0

我自己使用它,我想傳遞一個lambda到一個方法,該方法使用註釋爲傳入的對象(lambda)實例化代理接口,但由於我沒有擁有JDK類的功能接口,將註釋添加到它。所有這些與你最初想要的基本上不同的是,註解被聲明爲遠離它使用的地方有點像變量。 – user2219808

+0

但是,爲什麼不只是使用註釋的匿名接口而不是lambda表達式呢?或者我錯過了什麼? – Balder

-1
public class Calculator { 

public static void main(String[] args) { 

    try(Scanner scanner=new Scanner(System.in)){ 
    System.out.println("Enter the operation to perform"); 
    String key=scanner.next(); 
    int i,j; 
    switch (key) { 
    case "Add": 
     System.out.println("Input 2 values for addtion"); 
     i =scanner.nextInt(); 
     j=scanner.nextInt(); 
     Calculate<Integer> add=(Integer a, Integer b)-> a+b; 
     System.out.println("After Addition of values="+add.calculate(i, j)); 
     break; 

    case "Multiply": 
     System.out.println("Input 2 values for Multiplication"); 
     i=scanner.nextInt(); 
     j=scanner.nextInt(); 
     Calculate<Integer> multiplication=(a,b)->a*b; 
     System.out.println("After Multiplication of values="+multiplication.calculate(i, j)); 
     break; 
    case "Subtract": 
     System.out.println("Input 2 values for Subtraction"); 
     i=scanner.nextInt(); 
     j=scanner.nextInt(); 
     Calculate<Integer> subtract=(a,b)->a-b; 
     System.out.println("After Subtraction of values="+subtract.calculate(i, j)); 
     break; 
    case "Division": 
     System.out.println("Input 2 values for Division"); 
     i=scanner.nextInt(); 
     j=scanner.nextInt(); 
     Calculate<Integer> division=(a,b)->a/b; 
     if(j>0){ 
     System.out.println("After Division of values="+division.calculate(i, j)); 
     }else{ 
      throw new Exception("Second value is 0. Please change the value"); 
     } 
     break; 

    default: 
     break; 
    } 
    }catch(Exception e){ 
     e.printStackTrace(); 
    } 
} 
} 

**使用的界面**

@FunctionalInterface 
    public interface Calculate<T> { 

    public T calculate(T a,T b); 
} 
+0

完全不相關的代碼轉儲。 – shmosel