From Effective Java 2/e by Joshua Bloch
Disadvantage of naming patterns
- Typographical errors may result in silent failures
- There is no way to ensure that they are used only on appropriate program elements
- They provide no good way to associate parameter values with program elements
Annotation
Such annotations on annotation type declarations are known as meta-annotations
@Retention(RetentionPolicy.RUNTIME)
meta-annotation indicates that Test annotations should be retained at runtime@Target(ElementType.METHOD)
meta-annotation indicates that the Test annotation is legal only on method declarations
public enum RetentionPolicy { SOURCE, CLASS, RUNTIME } public enum ElementType { TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE }
Marker annotation
// Marker annotation type declaration import java.lang.annotation.*; /** * Indicates that the annotated method is a test method. * Use only on parameterless static methods. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Test {} // Program containing marker annotations public class Sample { @Test public static void m1() { } // Test should pass public static void m2() { } @Test public static void m3() { // Test Should fail throw new RuntimeException("Boom"); } public static void m4() { } @Test public void m5() { } // INVALID USE: nonstatic method public static void m6() { } @Test public static void m7() { // Test should fail throw new RuntimeException("Crash"); } public static void m8() { } } // Program to process marker annotations import java.lang.reflect.*; public class RunTests { public static void main(String[] args) throws Exception { int tests = 0; int passed = 0; Class testClass = Class.forName(args[0]); for (Method m : testClass.getDeclaredMethods()) { if (m.isAnnotationPresent(Test.class)) { tests++; try { m.invoke(null); passed++; } catch (InvocationTargetException wrappedExc) { Throwable exc = wrappedExc.getCause(); System.out.println(m + " failed: " + exc); } catch (Exception exc) { System.out.println("INVALID @Test: " + m); } } } System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed); } }
Annotation type with a parameter
// Annotation type with a parameter import java.lang.annotation.*; /** * Indicates that the annotated method is a test method that * must throw the designated exception to succeed. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ExceptionTest { Class<? extends Exception> value(); } // Program containing annotations with a parameter public class Sample2 { @ExceptionTest(ArithmeticException.class) public static void m1() { // Test should pass int i = 0; i = i / i; } @ExceptionTest(ArithmeticException.class) public static void m2() { // Should fail (wrong exception) int[] a = new int[0]; int i = a[1]; } @ExceptionTest(ArithmeticException.class) public static void m3() { } // Should fail (no exception) } if (m.isAnnotationPresent(ExceptionTest.class)) { tests++; try { m.invoke(null); System.out.printf("Test %s failed: no exception%n", m); } catch (InvocationTargetException wrappedEx) { Throwable exc = wrappedEx.getCause(); Class<? extends Exception> excType = m.getAnnotation(ExceptionTest.class).value(); if (excType.isInstance(exc)) { passed++; } else { System.out.printf("Test %s failed: expected %s, got %s%n", m, excType.getName(), exc); } } catch (Exception exc) { System.out.println("INVALID @Test: " + m); } }
Annotation type with an array parameter
// Annotation type with an array parameter @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ExceptionTest { Class<? extends Exception>[] value(); } // Code containing an annotation with an array parameter @ExceptionTest({ IndexOutOfBoundsException.class, NullPointerException.class }) public static void doublyBad() { List<String> list = new ArrayList<String>(); // The spec permits this method to throw either // IndexOutOfBoundsException or NullPointerException list.addAll(5, null); } if (m.isAnnotationPresent(ExceptionTest.class)) { tests++; try { m.invoke(null); System.out.printf("Test %s failed: no exception%n", m); } catch (Throwable wrappedExc) { Throwable exc = wrappedExc.getCause(); Class<? extends Exception>[] excTypes = m.getAnnotation(ExceptionTest.class).value(); int oldPassed = passed; for (Class<? extends Exception> excType : excTypes) { if (excType.isInstance(exc)) { passed++; break; } } if (passed == oldPassed) System.out.printf("Test %s failed: %s %n", m, exc); } }