Java注解学习笔记
我们平常写Java代码,对其中的注解并不是很陌生,比如说写继承关系的时候经常用到@Override来修饰方法。但是@Override是用来做什么的,为什么写继承方法的时候要加上它,不加行不行。如果对Java的注解没有了解过,很难回答这些问题。并且,现在越来越多的第三方库开始使用注解,不了解注解的话很难理解他们的逻辑。趁着五一假期,赶紧补习一下什么是注解。
概况
注解是Java5之后引入的新特性,它与class,interface,enum处于同一层次。可以理解为在代码中插入一段元数据。它们是在实际的源代码级别保存信息,而不是某种注释性质的文字,这样能够使源代码整洁,便于维护。它可以在三个时期起作用,分别是编译时,构建时和运行时。他们可以在编译时使用预编译工具进行处理,也可以在构建时影响到Ant,Maven等打包工具,还可以在运行期使用反射机制进行处理。
基本用法
不带参数:
@Override
public void onCreate(Bundle savedInstanceState){
    //...
}
带参数:
@CustomizeAnnotation( name = "wakaka", age = 22)
//...
只有一个参数(可以不指定字段名):
@CustomizeAnnotation("wakaka")
java自带的标准注解:
- @Deprecated标记这个元素被弃用,如果在其它地方对它引用/使用,编译器会发出警告信息。
- @Override表示当前的方法覆盖父类中定义的方法。如果不小心拼写错误,或者方法签名对应不上父类的方法,编译器会报出错误提示。
- @SuppressWarnings关闭警告信息。
定义注解
直接上例子:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TestAnnotation {
}
使用方法:
public class MainAnnotation {
    @TestAnnotation
    public void testMethod() {
    }
}
定义注解跟定义接口差不多,只不过关键字要换成 @interface。定义注解时需要用到元注解,比如 @Target, @Retention。@Target用来定义注解的使用位置,包括类,方法等;@Retention定义注解在哪一个级别可用,源代码、类、运行时。
注解中一般都包含某些元素来表示某些值。分析注解的时候,主程序或者构建工具可以获取到这些信息。没有元素的注解称为标记注解,比如说@Override @Deprecated。
定义注解元素的方式类似于定义接口中的方法,区别在于可以为注解中的元素添加默认值。例子:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
    public int id();
    public String description() default "no description";
}
用法:
public class PasswordUtils {
    @UseCase(id = 47, description = "Password must contain at least one numeric")
    public boolean validatePassword(String password) {
        return password.matches("\\w*\\d\\w");
    }
    @UseCase(id = 48)
    public String encryptPassword(String password) {
        return new StringBuilder(password).reverse().toString();
    }
    @UseCase(id = 49, description = "New password can't equal previously used ones")
    public boolean checkForNewPassword(List<String> prevPasswords, String password) {
        return !prevPasswords.contains(password);
    }
}
从上面的例子可以看到,元素的定义类似于方法的定义。方法名就是元素名。使用default关键字可以为一个元素增加一个默认值。
使用的时候除了带有默认值的元素,需要把所有的元素的值填满。
元注解
Java目前内置了四种元注解。
- @Target
 表示该注解可以应用的地方。参数使用ElementType:- CONSTRUCTOR 构造器的声明;
- FIELD 域声明;
- LOCAL_VARIABLE局部变量的声明;
- METHOD方法声明;
- PACKAGE包的声明;
- PARAMETER参数声明;
- TYPE 类、接口、注解、枚举声明;
 
- @Retention
 表示需要在什么级别保存该注解信息。参数使用RetentionPolicy:- SOURCE注解将被编译器丢弃;
- CLASS注解在class文件中使用,但是会被VM丢弃;
- RUNTIMEVM将在运行期也保留注解,因此可以通过反射机制读取注解的信息。
 
- @Documented
 将此注解包含在Javadoc中。
- @Inherited
 允许子类继承父类的注解
大多数时候,我们都需要定义自己注解,并编写自己的处理器来处理他们。
注解元素
注解元素可用的类型如下:
- 所有基本类型(int, boolean, char, long, byte...)
- String
- Class
- enum
- Annotation
- 以上类型的数组
使用这些类型以外的类型会报错。不允许使用
Integer,Character等包装类型。
默认值的限制
- 所有元素要么有指定的值,要么有默认值;
- 非基本类型的值,无论是指定值还是默认值都不能用null。
注解不支持继承
不能使用extend来继承某个@interface类型。
编写注解处理器
如果没有读取注解的逻辑,那注解跟注释是差不多的。我们可以利用Java的反射机制构造注解处理器,或者利用工具apt解析带有注解的Java源代码。
例子:
public class UseCaseTracker {
    public static void trackUseCases(List<Integer> usecases, Class<?> cl) {
        for (Method m : cl.getDeclaredMethods()) {
            UseCase uc = m.getDeclaredAnnotation(UseCase.class);
            if (uc != null) {
                System.out.printf("Found Use Case: %d %s\n", uc.id(), uc.description());
                usecases.remove(new Integer(uc.id()));
            }
        }
        for (int i : usecases) {
            System.out.printf("Warning: Missiong use case-%d", i);
        }
    }
    public static void main(String[] args) {
        List<Integer> useCases = new ArrayList<>();
        Collections.addAll(useCases, 47, 48, 49, 50);
        trackUseCases(useCases, PasswordUtils.class);
    }
}
UseCase注解已经在之前的例子中定义。
这个例子功能就是简单比较一下是否缺少一些没有编写的测试用例。其中useCases列表包含了所有应当包含的测试用例,PasswodUtils是所有UseCase测试源代码所在的类。
检测过程中使用了反射方法getDeclaredMethods()和getDeclaredAnnotation()。先获取PasswordUtils类中的所有方法,并遍历这个列表中的所有方法。如果一个方法被UseCase注解修饰,获取这个UseCase对象,并取出它的所有元素值。打印UseCase的信息,并在useCases中删除这Usecase编号。最后打印所有没有编写的用例编号。
生成信息
有些框架除了需要写java代码之外还需要一些额外的配置文件才能协同工作,这种情况最能体现出注解的价值。
比如说像EJB,Hibernate这样的框架,一般都需要一份xml描述文件。他们提供了Java源文件中类和包的原始信息。如果没有注解,我们在写完java代码之后需要额外再写一份关于Java类的配置问文件。
如果我们想添加一个实体类,建立一份基本的的对象/关系的映射,达到自动生成数据库表的目的。我们可以使用注解,它可以清晰的保存在Java源文件中,方便我们了解实体与的关系。
例子:
数据库中的所有属性都通过注解来传递,所以我们需要定义一些数据库中的‘类型’。这里我们简单的做一个例子,并没有定义全部的属性和类型。
//对应数据库中的表, 只有一个属性,表名;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
    public String name() default "";
}
//表中每个字段的约束,只写了3个
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
    boolean primaryKey() default false;
    boolean allowNull() default true;
    boolean unique() default false;
}
//对应数据库中的 INT 类型
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
    String name() default "";
    Constraints contraints() default @Constraints();
}
//对应数据库中的 VARCHAR 类型
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
    int value() default 0;
    String name() default "";
    Constraints constraints() default @Constraints();
}
/**
 * Created by yuxiaofei on 2016/5/7.
 * 实体类,被注解修饰的成员变量会被加入到数据库中
 */
@DBTable(name = "Member")
public class Member {
    @SQLString(value = 30)
    String firstName;
    @SQLString(value = 50)
    String lastName;
    @SQLInteger
    Integer age;
    @SQLString(value = 30, constraints = @Constraints(primaryKey = true))
    String handle;
    static int memberCount;
}
/**
 * Created by yuxiaofei on 2016/5/7.
 * 根据目标实体类,声称创建表的SQL
 */
public class TableCreator {
    public static void main(String[] args) {
        Class<?> targetClass = Member.class;
        DBTable dbTable = targetClass.getAnnotation(DBTable.class);
        if (dbTable == null) {
            System.out.printf("No DBTable in class %s. \n", targetClass.getSimpleName());
            System.exit(-1);
        }
        String tableName = dbTable.name();
        if (tableName.length() < 1) {
            //默认名使用类名的全字母全大写
            tableName = targetClass.getName().toUpperCase();
        }
        List<String> columnDefs = new ArrayList<String>();
        getColumnDefs(targetClass, columnDefs);
        String SQL = createSQL(tableName, columnDefs);
        System.out.println(SQL);
    }
    //根据表名和字段声明,生成创建表的SQL
    private static String createSQL(String tableName, List<String> columnDefs) {
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("CREATE TABLE %s (\n", tableName));
        for (int i = 0; i < columnDefs.size(); i++) {
            String column = columnDefs.get(i);
            if (i != columnDefs.size() - 1) {
                sb.append(String.format("   %s,\n", column));
            } else {
                sb.append(String.format("   %s\n);", column));
            }
        }
        return sb.toString();
    }
    /**
     * 根据实体类生成创建表的字段
     *
     * @param targetClass 目标实体类
     * @param columnDefs  字段声明列表
     */
    private static void getColumnDefs(Class<?> targetClass, List<String> columnDefs) {
        for (Field field : targetClass.getDeclaredFields()) {
            String columnName = null;
            Annotation[] anns = field.getDeclaredAnnotations();
            if (anns.length < 1) {
                continue;//没有被注解修饰,非数据库表内字段
            }
            if (anns[0] instanceof SQLInteger) {
                SQLInteger sqlInteger = (SQLInteger) anns[0];
                if (sqlInteger.name().length() < 1) {//默认名,用变量名全大写形式代替
                    columnName = field.getName().toUpperCase();
                } else {
                    columnName = sqlInteger.name();
                }
                Constraints constraints = sqlInteger.contraints();
                columnDefs.add(String.format("%s INT %s", columnName, getConstraints(constraints)));
            } else if (anns[0] instanceof SQLString) {
                SQLString sqlString = (SQLString) anns[0];
                if (sqlString.name().length() < 1) {//默认名用变量名全字母大写
                    columnName = field.getName().toUpperCase();
                } else {
                    columnName = sqlString.name();
                }
                Constraints constraints = sqlString.constraints();
                columnDefs.add(
                        String.format(
                                "%s VARCHAR(%d) %s",
                                columnName, sqlString.value(), getConstraints(constraints)
                        )
                );
            }
        }
    }
    /**
     * 根据注解中的配置声称字段的约束
     *
     * @param constraints 注解约束配置
     * @return 字段约束
     */
    private static String getConstraints(Constraints constraints) {
        StringBuilder sb = new StringBuilder();
        if (!constraints.allowNull()) {
            sb.append(" NOT NULL");
        }
        if (constraints.primaryKey()) {
            sb.append(" PRIMARY KEY");
        }
        if (constraints.unique()) {
            sb.append(" UNIQUE");
        }
        return sb.toString();
    }
}
输出结果就是一条sql语句:
CREATE TABLE Member (
   FIRSTNAME VARCHAR(30) ,
   LASTNAME VARCHAR(50) ,
   AGE INT ,
   HANDLE VARCHAR(30)  PRIMARY KEY
);
例子比较简单,运行一下就可以看到结果。虽然创建一个实体的代码变多了,但是以后每次添加一个实体,一张表都很方便。
对于注解的学习就到这里了,有什么疑问可以在回复中一起交流。
参考文献: 《Java编程思想》
Java注解学习笔记的更多相关文章
- java注解学习笔记总结
		注解的理解 ① jdk 5.0 新增的功能 ② Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理.通过使用 Annotation,程序 ... 
- Kotlin for Java Developers 学习笔记
		Kotlin for Java Developers 学习笔记 ★ Coursera 课程 Kotlin for Java Developers(由 JetBrains 提供)的学习笔记 " ... 
- Java IO学习笔记八:Netty入门
		作者:Grey 原文地址:Java IO学习笔记八:Netty入门 多路复用多线程方式还是有点麻烦,Netty帮我们做了封装,大大简化了编码的复杂度,接下来熟悉一下netty的基本使用. Netty+ ... 
- 20145213《Java程序设计学习笔记》第六周学习总结
		20145213<Java程序设计学习笔记>第六周学习总结 说在前面的话 上篇博客中娄老师指出我因为数据结构基础薄弱,才导致对第九章内容浅尝遏止地认知.在这里我还要自我批评一下,其实我事后 ... 
- [原创]java WEB学习笔记95:Hibernate 目录
		本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ... 
- java JDK8 学习笔记——助教学习博客汇总
		java JDK8 学习笔记——助教学习博客汇总 1-6章 (by肖昱) Java学习笔记第一章——Java平台概论 Java学习笔记第二章——从JDK到IDEJava学习笔记第三章——基础语法Jav ... 
- java JDK8 学习笔记——第16章 整合数据库
		第十六章 整合数据库 16.1 JDBC入门 16.1.1 JDBC简介 1.JDBC是java联机数据库的标准规范.它定义了一组标准类与接口,标准API中的接口会有数据库厂商操作,称为JDBC驱动程 ... 
- [原创]java WEB学习笔记75:Struts2 学习之路-- 总结 和 目录
		本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ... 
- [原创]java WEB学习笔记66:Struts2 学习之路--Struts的CRUD操作( 查看 / 删除/ 添加) 使用 paramsPrepareParamsStack 重构代码 ,PrepareInterceptor拦截器,paramsPrepareParamsStack 拦截器栈
		本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ... 
随机推荐
- 【java学习笔记】序列化、反序列化
			序列化 是将对象的完整信息保存起来的过程(持久化). 序列化流:ObjectOutputStream 反序列化 是将对象进行还原的过程(反持久化). 反序列化流:Ob ... 
- Node 定时器详解
			JavaScript 是单线程运行,异步操作特别重要. 只要用到引擎之外的功能,就需要跟外部交互,从而形成异步操作.由于异步操作实在太多,JavaScript 不得不提供很多异步语法.这就好比,有些人 ... 
- eclipse中maven的run as打war包失败的问题
			场景一: 由于某些原因,有的时候需要暂时在断网的情况下,或者更标准的说,是在连不上公司的maven公有仓库的情况下打包. 很长一段时间,我打包都是在eclipse中用run as在线打包,直到前不久一 ... 
- 好用的Markdown编辑器汇总
			Markdown 是一种简单的.轻量级的标记语法.用户可以使用诸如 * # 等简单的标记符号以最小的输入代价生成极富表现力的文档. Markdown具有很多优点: 写作中添加简单符号即完成排版,所见即 ... 
- mysql常用基础操作语法(十)~~子查询【命令行模式】
			mysql中虽然有连接查询实现多表连接查询,但是连接查询的性能很差,因此便出现了子查询. 1.理论上,子查询可以出现在查询语句的任何位置,但实际应用中多出现在from后和where后.出现在from后 ... 
- javascript 获取滚动条距离顶部的位置(兼容所有的)。
			function getScrollTop() { var scrollPos; if (window.pageYOffset) { scrollPos = window.pageYOffset; } ... 
- fineuploader使用实例
			1.Fine Uploader特点 Fine Uploader Features: A:支持文件上传进度显示. B:文件拖拽浏览器上传方式 C:Ajax页面无刷新. D:多文件上传. F:跨浏览器. ... 
- TCP粘包、拆包及解决
			TCP属于传输层的协议,传输层除了有TCP协议外还有UDP协议.那么UDP是否会发生粘包或拆包的现象呢?答案是不会.UDP是基于报文发送的,从UDP的帧结构可以看出,在UDP首部采用了16bit来指示 ... 
- [Luogu3066][USACO12DEC]逃跑的BarnRunning Away From…
			题面 题目描述 给出以1号点为根的一棵有根树,问每个点的子树中与它距离小于等于l的点有多少个. 输入格式: Line 1: 2 integers, N and L (1 <= N <= 2 ... 
- 学习Javascript闭包(Closure)及几个经典面试题理解
			今天遇到一个面试题,结果让我百思不得其解.后来在查阅了各种文档后,理清了来龙去脉.让我们先来看看这道题: function Foo( ){ var i = 0; return function( ){ ... 
