我们平常写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目前内置了四种元注解

  1. @Target

    表示该注解可以应用的地方。参数使用ElementType:

    • CONSTRUCTOR 构造器的声明;
    • FIELD 域声明;
    • LOCAL_VARIABLE局部变量的声明;
    • METHOD方法声明;
    • PACKAGE包的声明;
    • PARAMETER参数声明;
    • TYPE 类、接口、注解、枚举声明;
  2. @Retention

    表示需要在什么级别保存该注解信息。参数使用RetentionPolicy

    • SOURCE注解将被编译器丢弃;
    • CLASS注解在class文件中使用,但是会被VM丢弃;
    • RUNTIMEVM将在运行期也保留注解,因此可以通过反射机制读取注解的信息。
  3. @Documented

    将此注解包含在Javadoc中。
  4. @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编程思想》

最新文章

  1. 使用page object模式抓取几个主要城市的pm2.5并从小到大排序后写入txt文档
  2. 【腾讯Bugly干货分享】QFix探索之路—手Q热补丁轻量级方案
  3. POJ 2503 字典树
  4. Struts2 Action中的方法命名不要以get开头
  5. addresslist
  6. Linux下资源利用率监测利器—nmon使用
  7. Mybatis foreach
  8. Maven依赖
  9. hdu 4588 Count The Carries
  10. hdu 5455 Fang Fang 坑题
  11. Python基础 - 迭代
  12. C# 语言规范_版本5.0 (第13章 接口)
  13. 【Centos7】hostnamectl 设置主机名
  14. sql获取时间段内的所有日期
  15. 对抗生成网络-图像卷积-mnist数据生成(代码) 1.tf.layers.conv2d(卷积操作) 2.tf.layers.conv2d_transpose(反卷积操作) 3.tf.layers.batch_normalize(归一化操作) 4.tf.maximum(用于lrelu) 5.tf.train_variable(训练中所有参数) 6.np.random.uniform(生成正态数据
  16. JavaScript和Ajax部分(4)
  17. Unity3D修改LWRP,HDRP的几项小问题及解决
  18. WinForm中预览Office文件
  19. Python第一天:python2.x和python3.x的区别
  20. CTO 之“六脉神剑”

热门文章

  1. Linux分区和挂载(mount命令的学习)
  2. linux内核initrd文件自定义方法
  3. 利用ffmpeg将H264流 解码为RGB
  4. SQL面试题收录
  5. class-提升方法Boosting
  6. Ball HDU - 4811
  7. lvy打包到本地
  8. Modbus总结
  9. 干货:JVM 堆内存和非堆内存
  10. Java冒泡排序法升级版