注解对代码的语意没有直接影响, 他们只负责提供信息给相关的程序使用注解永远不会改变被注解代码的含义但可以通过工具对被注解的代码进行特殊处理.


JDK 基本Annotation

注解

说明

@Override

重写

@Deprecated

已过时

@SuppressWarnings(value = "unchecked")

压制编辑器警告

@SafeVarargs

修饰”堆污染”警告

@FunctionalInterface

Java8特有的函数式接口

  • value特权
    如果使用注解时只需要为value成员变量指定值, 则使用注解时可以直接在该注解的括号中指定value值, 而无需使用name=value的形式. 如@SuppressWarnings("unchecked")(SuppressWarnings的各种参数
    请参考解析 @SuppressWarnings的各种参数)
  • 请坚持使用@Override注解: 如果在每个方法中使用Override注解来声明要覆盖父类声明, 编译器就可以替你防止大量的错误.

JDK 元Annotation

Annotation用于修饰其他的Annotation定义.

元注解

释义

@Retention

注解保留策略

@Target

注解修饰目标

@Documented

注解文档提取

@Inherited

注解继承声明

  • @Retention 注解的保留策略
1
2
3
4
5
6
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}

value为SOURCECLASSRUNTIME三值之一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,
  
    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,
  
    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}
  • @Target 指定Annotation可以放置的位置(被修饰的目标)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}
  
public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,
  
    /** Field declaration (includes enum constants) */
    FIELD,
  
    /** Method declaration */
    METHOD,
  
    /** Parameter declaration */
    PARAMETER,
  
    /** Constructor declaration */
    CONSTRUCTOR,
  
    /** Local variable declaration */
    LOCAL_VARIABLE,
  
    /** Annotation type declaration */
    ANNOTATION_TYPE,
  
    /** Package declaration */
    PACKAGE
}
  • @Documented 指定被修饰的该Annotation可以被javadoc工具提取成文档.
  • @Inherited 指定被修饰的Annotation将具有继承性
    如果某个类使用@Xxx注解(该Annotation使用了@Inherited修饰)修饰, 则其子类自动被@Xxx注解修饰.

Annotation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
 * Created by jifang on 15/12/22.
 */
@Inherited
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Testable {
}
  
 
Client
 
public class Client {
  
    @Test
    public void client(){
        new SubClass();
    }
}
  
@Testable
class SupperClass{
}
  
class SubClass extends SupperClass{
    public SubClass() {
        for (Annotation annotation : SubClass.class.getAnnotations()){
            System.out.println(annotation);
        }
    }
}

自定义注解

  • 根据Annotation是否包含成员变量,可以把Annotation分为两类:

    • 标记Annotation: 没有成员变量的Annotation; 这种Annotation仅利用自身的存在与否来提供信息;
    • 元数据Annotation: 包含成员变量的Annotation; 它们可以接受(和提供)更多的元数据;
  • 定义新注解使用@interface关键字, 其定义过程与定义接口非常类似(见上面的@Testable), 需要注意的是:Annotation的成员变量在Annotation定义中是以无参的方法形式来声明的, 其方法名返回值类型定义了该成员变量的名字类型, 而且我们还可以使用default关键字为这个成员变量设定默认值.
1
2
3
4
5
6
7
8
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Tag {
    String name() default "该叫啥才好呢?";
  
    String description() default "这家伙很懒, 啥也没留下...";
}
  • 自定义的Annotation继承了Annotation这个接口, 因此自定义注解中包含了Annotation接口中所有的方法;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface Annotation {
    /**
     * @return true if the specified object represents an annotation
     *     that is logically equivalent to this one, otherwise false
     */
    boolean equals(Object obj);
  
    /**
     * @return the hash code of this annotation
     */
    int hashCode();
  
    /**
     * @return a string representation of this annotation
     */
    String toString();
  
    /**
     * Returns the annotation type of this annotation.
     */
    Class<? extends Annotation> annotationType();
}

提取Annotation信息

  • 使用Annotation修饰了/方法/成员变量等之后,这些Annotation不会自己生效,必须由这些注解的开发者提供相应的工具来提取并处理Annotation信息(当然,只有当定义Annotation时使用了@Retention(RetentionPolicy.RUNTIME)修饰,JVM才会在装载class文件时提取保存在class文件中的Annotation,该Annotation才会在运行时可见,这样我们才能够解析).
  • Java使用Annotation接口来代表程序元素前面的注解, 用AnnotatedElement接口代表程序中可以接受注解的程序元素.像Class Constructor FieldMethod Package这些类都实现了AnnotatedElement接口.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public final
    class Class<T> implements java.io.Serializable,
                              java.lang.reflect.GenericDeclaration,
                              java.lang.reflect.Type,
                              java.lang.reflect.AnnotatedElement {
...
}
  
public interface AnnotatedElement {
    /**
     * Returns true if an annotation for the specified type
     * is present on this element, else false.  This method
     * is designed primarily for convenient access to marker annotations.
     */
     boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
  
   /**
     * Returns this element's annotation for the specified type if
     * such an annotation is present, else null.
     */
    <T extends Annotation> T getAnnotation(Class<T> annotationClass);
  
    /**
     * Returns all annotations present on this element.
     */
    Annotation[] getAnnotations();
  
    /**
     * Returns all annotations that are directly present on this
     * element.  Unlike the other methods in this interface, this method
     * ignores inherited annotations.  (Returns an array of length zero if
     * no annotations are directly present on this element.)  The caller of
     * this method is free to modify the returned array; it will have no
     * effect on the arrays returned to other callers.
     */
    Annotation[] getDeclaredAnnotations();
}

这样, 我们只需要获取到Class Method Filed等这些实现了AnnotatedElement接口的类实例, 就可以获取到我们想要的注解信息了.

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * Created by jifang on 15/12/22.
 */
public class Client {
  
    @Test
    public void client() throws NoSuchMethodException {
        Annotation[] annotations = this.getClass().getMethod("client").getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation.annotationType().getName());
        }
    }
}

如果需要获取某个注解中的元数据,则需要强转成所需的注解类型,然后通过注解对象的抽象方法来访问这些元数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Tag(name = "client")
public class Client {
  
    @Test
    public void client() throws NoSuchMethodException {
        Annotation[] annotations = this.getClass().getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof Tag) {
                Tag tag = (Tag) annotation;
                System.out.println("name: " + tag.name());
                System.out.println("description: " + tag.description());
            }
        }
    }
}

模拟Junit框架

我们用@Testable标记哪些方法是可测试的, 只有被@Testable修饰的方法才可以被执行.

1
2
3
4
5
6
7
8
/**
 * Created by jifang on 15/12/27.
 */
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Testable {
}

如下定义TestCase测试用例定义了6个方法, 其中有4个被@Testable修饰了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class TestCase {
  
    @Testable
    public void test1() {
        System.out.println("test1");
    }
  
    public void test2() throws IOException {
        System.out.println("test2");
        throw new IOException("我test2出错啦...");
    }
  
    @Testable
    public void test3() {
        System.out.println("test3");
        throw new RuntimeException("我test3出错啦...");
    }
  
    public void test4() {
        System.out.println("test4");
    }
  
    @Testable
    public void test5() {
        System.out.println("test5");
    }
  
    @Testable
    public void test6() {
        System.out.println("test6");
    }
}

为了让程序中的这些注解起作用, 必须为这些注解提供一个注解处理工具.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
 * Created by jifang on 15/12/27.
 */
public class TestableProcessor {
  
    public static void process(String clazz) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        int passed = 0;
        int failed = 0;
        Object obj = Class.forName(clazz).newInstance();
        for (Method method : Class.forName(clazz).getMethods()) {
            if (method.isAnnotationPresent(Testable.class)) {
                try {
                    method.invoke(obj);
                    ++passed;
                } catch (IllegalAccessException | InvocationTargetException e) {
                    System.out.println("method " + method.getName() + " execute error: < " + e.getCause() + " >");
                    e.printStackTrace(System.out);
                    ++failed;
                }
            }
        }
  
        System.out.println("共运行" + (failed + passed) + "个方法, 成功" + passed + "个, 失败" + failed + "个");
    }
  
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        TestableProcessor.process("com.feiqing.annotation.TestCase");
    }
}

抛出特定异常

前面介绍的只是一个标记Annotation,程序通过判断Annotation是否存在来决定是否运行指定方法,现在我们要针对只在抛出特殊异常时才成功添加支持,这样就用到了具有成员变量的注解了:

1
2
3
4
5
6
7
8
9
/**
 * Created by jifang on 15/12/28.
 */
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestableException {
    Class<? extends Throwable>[] value();
}
  • TestCase
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
 * Created by jifang on 15/12/27.
 */
public class TestCase {
  
    public void test1() {
        System.out.println("test1");
    }
  
    @TestableException(ArithmeticException.class)
    public void test2() throws IOException {
        int i = 1 / 0;
        System.out.println(i);
    }
  
    @TestableException(ArithmeticException.class)
    public void test3() {
        System.out.println("test3");
        throw new RuntimeException("我test3出错啦...");
    }
  
    public void test4() {
        System.out.println("test4");
    }
  
    @TestableException({ArithmeticException.class, IOException.class})
    public void test5() throws FileNotFoundException {
        FileInputStream stream = new FileInputStream("xxxx");
    }
  
    @Testable
    public void test6() {
        System.out.println("test6");
    }
}
  • 注解处理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class TestableExceptionProcessor {
  
    public static void process(String clazz) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        int passed = 0;
        int failed = 0;
        Object obj = Class.forName(clazz).newInstance();
        for (Method method : Class.forName(clazz).getMethods()) {
            if (method.isAnnotationPresent(TestableException.class)) {
                try {
                    method.invoke(obj, null);
                    // 没有抛出异常(失败)
                    ++failed;
                } catch (InvocationTargetException e) {
                    // 获取异常的引发原因
                    Throwable cause = e.getCause();
  
                    int oldPassed = passed;
                    for (Class excType : method.getAnnotation(TestableException.class).value()) {
                        // 是我们期望的异常类型之一(成功)
                        if (excType.isInstance(cause)) {
                            ++passed;
                            break;
                        }
                    }
                    // 并不是我们期望的异常类型(失败)
                    if (oldPassed == passed) {
                        ++failed;
                        System.out.printf("Test <%s> failed <%s> %n", method, e);
                    }
                }
            }
        }
        System.out.println("共运行" + (failed + passed) + "个方法, 成功" + passed + "个, 失败" + failed + "个");
    }
  
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        process("com.feiqing.annotation.TestCase");
    }
}

注解添加监听器

下面通过使用Annotation简化事件编程, 在传统的代码中总是需要通过addActionListener方法来为事件源绑定事件监听器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
 * Created by jifang on 15/12/27.
 */
public class SwingPro {
    private JFrame mainWin = new JFrame("使用注解绑定事件监听器");
  
    private JButton ok = new JButton("确定");
    private JButton cancel = new JButton("取消");
  
    public void init() {
        JPanel jp = new JPanel();
  
        // 为两个按钮设置监听事件
        ok.addActionListener(new OkListener());
        cancel.addActionListener(new CancelListener());
  
        jp.add(ok);
        jp.add(cancel);
        mainWin.add(jp);
        mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mainWin.pack();
        mainWin.setVisible(true);
    }
  
    public static void main(String[] args) {
        new SwingPro().init();
    }
}
  
class OkListener implements ActionListener {
  
    @Override
    public void actionPerformed(ActionEvent e) {
        JOptionPane.showMessageDialog(null, "你点击了确认按钮!");
    }
}
  
class CancelListener implements ActionListener {
  
    @Override
    public void actionPerformed(ActionEvent e) {
        JOptionPane.showMessageDialog(null, "你点击了取消按钮!");
    }
}

下面我们该用注解绑定监听器:

  • 首先, 我们需要自定义一个注解
1
2
3
4
5
6
7
8
9
/**
 * Created by jifang on 15/12/27.
 */
@Inherited
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ActionListenerFor {
    Class<? extends ActionListener> listener();
}
  • 然后还要一个注解处理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * Created by jifang on 15/12/27.
 */
public class ActionListenerInstaller {
  
    public static void install(Object targetObject) throws IllegalAccessException, InstantiationException {
        for (Field field : targetObject.getClass().getDeclaredFields()) {
            // 如果该成员变量被ActionListenerFor标记了
            if (field.isAnnotationPresent(ActionListenerFor.class)) {
                // 设置访问权限
                field.setAccessible(true);
  
                // 获取到成员变量的值
                AbstractButton targetButton = (AbstractButton) field.get(targetObject);
  
                // 获取到注解中的Listener
                Class<? extends ActionListener> listener = field.getAnnotation(ActionListenerFor.class).listener();
  
                // 添加到成员变量中
                targetButton.addActionListener(listener.newInstance());
            }
        }
    }
}
  • 主程序(注意注释处)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class SwingPro {
  
    private JFrame mainWin = new JFrame("使用注解绑定事件监听器");
  
    /**
     * 使用注解设置Listener
     */
    @ActionListenerFor(listener = OkListener.class)
    private JButton ok = new JButton("确定");
  
    @ActionListenerFor(listener = CancelListener.class)
    private JButton cancel = new JButton("取消");
  
    public SwingPro init() {
        JPanel jp = new JPanel();
  
        // 使得注解生效
        try {
            ActionListenerInstaller.install(this);
        } catch (IllegalAccessException | InstantiationException e) {
            e.printStackTrace(System.out);
        }
  
        jp.add(ok);
        jp.add(cancel);
        mainWin.add(jp);
        mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mainWin.pack();
        mainWin.setVisible(true);
  
        return this;
    }
  
    //下同
}


重复注解

在Java5到Java7这段时间里, 同一个程序元素前只能使用一个相同类型的Annotation; 如果需要在同一个元素前使用多个相同的Annotation, 则必须使用Annotation容器(在Java8中, 对这种情况做了改善, 但其实也只是一种写法上的简化, 其本质还是一样的).由于在实际开发中,Java8还未大面积的使用, 因此在此只介绍Java7中重复注解定义与使用.

  • Table Annotation定义(代表数据库表)
1
2
3
4
5
6
7
8
9
10
11
/**
 * Created by jifang on 15/12/27.
 */
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
  
    String name() default "表名是啥?";
  
    String description() default "这家伙很懒, 啥也没留下...";
}
  • Table 容器
1
2
3
4
5
6
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Tables {
  
    Table[] value();
}

注意: 容器注解的保留期必须比它所包含的注解的保留期更长, 否则JVM会丢弃容器, 相应的注解也就丢失了.

  • Client
    使用时需要用Table容器来盛装Table注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Tables({
        @Table(name = "t_user", description = "用户表"),
        @Table(name = "t_feed", description = "动态表")
})
public class Client {
  
    @Test
    public void client() {
        Tables tableArray = this.getClass().getAnnotation(Tables.class);
        Table[] tables = tableArray.value();
  
        for (Table table : tables) {
            System.out.println(table.name() + " : " + table.description());
        }
    }
}

在Java8中, 可以直接使用

1
2
@Table(name = "t_user", description = "用户表")
@Table(name = "t_feed", description = "动态表")

的形式来注解Client, 但@Tables还是需要开发者来写的, 由此可以看出, 重复注解只是一种简化写法, 这种写法只是一种假象: 多个重复注解其实会被作为容器注解的value成员.


参考:

Effective Java

疯狂Java讲义

Java核心技术

Java注解实践--annotation学习三的更多相关文章

  1. Java注解处理器--annotation学习四

    Java中的注解(Annotation)是一个很神奇的东西,特别现在有很多Android库都是使用注解的方式来实现的.一直想详细了解一下其中的原理.很有幸阅读到一篇详细解释编写注解处理器的文章.本文的 ...

  2. Java注解实践

    Java注解实践 标签 : Java基础 注解对代码的语意没有直接影响, 他们只负责提供信息给相关的程序使用. 注解永远不会改变被注解代码的含义, 但可以通过工具对被注解的代码进行特殊处理. JDK ...

  3. (转)秒懂,Java 注解 (Annotation)你可以这样学

    转自:秒懂,Java 注解 (Annotation)你可以这样学 注解如同标签 回到博文开始的地方,之前某新闻客户端的评论有盖楼的习惯,于是 “乔布斯重新定义了手机.罗永浩重新定义了傻X” 就经常极为 ...

  4. (转)深入理解Java注解类型(@Annotation)

    背景:在面试时候问过关于注解的问题,工作中也用到过该java的特性,但是也没有深入的了解. 秒懂,Java 注解 (Annotation)你可以这样学 ps:注解最通俗易懂的解释 注解是一系列元数据, ...

  5. 秒懂,Java 注解 (Annotation)你可以这样学

    转自: https://blog.csdn.net/briblue/article/details/73824058 文章开头先引入一处图片. 这处图片引自老罗的博客.为了避免不必要的麻烦,首先声明我 ...

  6. Java 注解(Annotation)秒懂,你可以这样学,

    文章开头先引入一处图片. 这处图片引自老罗的博客.为了避免不必要的麻烦,首先声明我个人比较尊敬老罗的.至于为什么放这张图,自然是为本篇博文服务,接下来我自会说明.好了,可以开始今天的博文了. Anno ...

  7. 自定义Java注解(annotation)

    https://www.imooc.com/learn/456  笔记 Java从1.5开始引进注解. 首先解决一个问题,为什么要学习Java注解? 1.看懂别人写的代码,尤其是框架的代码 2.可以是 ...

  8. (转)秒懂,Java 注解 (Annotation)你可以这样学 ---- 重要 注解定义与反射解析

    转:http://blog.csdn.net/briblue/article/details/73824058 文章开头先引入一处图片.  这处图片引自老罗的博客.为了避免不必要的麻烦,首先声明我个人 ...

  9. 10分钟学会JAVA注解(annotation)

    (原) 先认识注解(Annotation) 定义类用class,定义接口用interface,定义注解用@interface 如public @interface AnnotationTest{} 所 ...

随机推荐

  1. 打开eclipse报错:发现了以元素 'd:skin' 开头的无效内容。此处不应含有子元素。

    [错误] 打开eclipse报错:发现了以元素 ‘d:skin’ 开头的无效内容.此处不应含有子元素. [具体报错信息] Error parsing D:\Android-sdks\system-im ...

  2. js toggle事件

    参数:even (Function): 第奇数次点击时要执行的函数. odd (Function): 第偶数次点击时要执行的函数. 示例:$("p").toggle(functio ...

  3. MyISAM 和InnoDB区别

    MyISAM 和InnoDB 讲解 InnoDB和MyISAM是许多人在使用MySQL时最常用的两个表类型,这两个表类型各有优劣,视具体应用而定.基本的差别为:MyISAM类型不支持事务处理等高级处理 ...

  4. 怎么提高OCR文字识别软件的识别正确率

    在OCR文字识别软件当中,ABBYY FineReader是比较好用的程序之一,但再好的识别软件也不能保证100%的识别正确率,用户都喜欢软件的正确率高一些,以减轻识别后修正的负担,很多用户也都提过这 ...

  5. SQL Server取系统当前时间【转】

    getdate //获得系统当前日期 datepart //获取日期指定部分(年月日时分表) getdate()函数:取得系统当前的日期和时间.返回值为datetime类型的. 用法:getdate( ...

  6. OpenJudge计算概论-鸡尾酒疗法

    /*===================================== 鸡尾酒疗法 总时间限制: 1000ms 内存限制: 65536kB 描述 鸡尾酒疗法,原指“高效抗逆转录病毒治疗”(HA ...

  7. Ubuntu上安装mono并进行C#代码测试

    微软的.NET框架与Linux开发和管理,是Buider AU和一个更广泛行业的两个最流行的主题. 大多数时候,这两个主题往往会产生冲突,很少有开发者需要同时了解这两个工具.但是,许多人都没意识到, ...

  8. Linux下升级python

    本文的Linux系统为CentOS 7 64 在Linux系统的下载文件夹中邮件打开终端,输入命令: wget http://www.python.org/ftp/python/3.4.4/Pytho ...

  9. PCA的数学原理

    PCA(Principal Component Analysis)是一种常用的数据分析方法.PCA通过线性变换将原始数据变换为一组各维度线性无关的表示,可用于提取数据的主要特征分量,常用于高维 数据的 ...

  10. Pop Sequence

    题目来源:PTA02-线性结构3 Pop Sequence   (25分) Question:Given a stack which can keep M numbers at most. Push ...