Annotation
是从JDK1.5
引入的新技术; 可以在编译
、类加载
、运行
时被读取;注解处理器
在编译期间
对注解
做处理; 注解以@注解名
(可以携带参数) 在代码中存在。
包位置信息:java.lang.*
可以用在·package
、field
、method
、class
等上面;相当于给它们添加了辅助信息;可以通过反射机制
访问这些元数据信息。
注解名 | 描述 |
---|---|
@Override | 限定重写父类方法。对于子类中被@Override 修饰的方法,如果存在对应的被重写的父类方法,则正确;如果不存在则报错。@Override 只能作用于方法,不能作用于其他程序元素 |
@Deprecated | 用于表示某个程序元素(类、方法等)已过时。如果使用了被@Deprecated 修饰的类或方法等,编译器会发出警告 |
@SuppressWarnings | 抑制编译器警告 |
@SuppressWarnings
需要配合下面一个参数才能使用:
参数 | 描述 |
---|---|
deprecation |
使用了不赞成使用的类或方法时的警告(使用@Deprecated使得编译器产生的警告) |
unchecked |
执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型; 关闭编译器警告 |
fallthrough |
当 Switch 程序块直接通往下一种情况而没有 Break 时的警告 |
path |
在类路径、源文件路径等中有不存在的路径时的警告 |
serial |
当在可序列化的类上缺少 serialVersionUID 定义时的警告 |
finally |
任何 finally 子句不能正常完成时的警告 |
all |
关于以上所有情况的警告 |
案例分析:
查看SuppressWarnings源码:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
@Target
和 @Retention
这两个修饰@SuppressWarnings的注解是什么 ?
注解的注解叫
元注解
;
注解名 | 描述 |
---|---|
@Target | 指示注释类型的注释要保留多久;保留策取值 RetentionPolicy.CLASS |
@Retention | 指示注释类型所适用的程序元素的种类。如果注释类型声明中不存在 Target 元注释,则声明的类型可以用在任一程序元素上。如果存在这样的元注释,则编译器强制实施指定的使用限制 |
@Documented | 将此注解包含在Javadoc 中;生成API文档时,提前对应注解 |
@Inherited | 允许子类继承父类中的注解;使用该元注解修饰的自定义注解去修饰的类,修饰类的子类也同时拥有父类的那个注解 |
注:在进阶篇中,将给出@Inherited
一个例子。
@Retention
的取值在下:
参数 | 描述 |
---|---|
CLASS |
编译器将把注释记录在类文件中,但在运行时 VM 不需要保留注释;class文件中有效 |
RUNTIME |
编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取;即运行时有效 |
SOURCE |
只保留在源码阶段;编译阶段将抛弃;即源文件中有效 |
源文件通过javac
编译成class文件;再经过类加载器的加载、连接(验证+准备+解析+)、初始化等一系列操作到内存中;让程序能够运行使用
@Target
的取值在下:
参数 | 描述 |
---|---|
ANNOTATION_TYPE |
注解类型声明 |
CONSTRUCTOR |
构造方法声明 |
FIELD |
字段声明(包括枚举常量) |
LOCAL_VARIABLE |
局部变量声明 |
METHOD |
方法声明 |
PACKAGE |
包声明 |
PARAMETER |
参数声明;方法参数 |
TYPE |
类、接口(包括注释类型)或枚举声明 |
补充:类的Target
取值是Type
;而不是 class ?
Type
是 Java 编程语言中所有类型的公共高级接口
。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。所以使用Type 来描述更加精准。
JDK中对Type的描述:
Target()
注解是接收的值是一个 数组
类型 , 即{}
,如果值只有 一个 使用 等号也是可以的。
注解名 | 描述 |
---|---|
@Override | 一、 @Override , 保留在源程序阶段,@Override编译器查看,编译完成就没有用了;二、 用于方法重写;保留策略只有 ElementType.METHOD |
@SuppressWarnings | 一、@SuppressWarnings ,也是给编译器查看的,编译完成就没有用了,也是源码阶段; 二、 TYPE , FIELD , METHOD , PARAMETER , CONSTRUCTOR , LOCAL_VARIABLE ; 无ANNOTATION_TYPE 、package |
@Deprecated | 一、@Deprecated, 这个是程序中需要调用其他已经写好的字节码来查看是否是过时的。编译器是需要获取其他的字节码。所以需要保留在运行时; 二、除了 ANNOTATION_TYPE 所有其他元素类型 |
八个基本类型
,String
,Class
, 枚举
,Annotation
,以及前面这些类型的数组
。注解也可以作为元素类型,也就是注解嵌套;任何包装类型是不可以的。
没有任何元素的注解称为 标记注解(marker annotation)
了解了注解的基本知识点,如何自定义一个注解呢?
使用@interface
,自动继承 java.lang.annotation.Annotation
, 格式: public @interface 注解名 {.定义主体..}
其中的每一个方法实际上是声明了一个配置参数:
自定义注解:
在定义的时候是以方法的形式,使用注解的时候,以属性赋值的形式。取值的时候也像调用方法一样调用
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 自定义注解; Retention在运行时;应用在方法和构造器上;
*
* 如果只有一个参数,推荐使用value;在使用该注解的时候,可以不指定参数名,直接给参数值即可。
* 如果有多个参数,在使用的的时候,要写清楚名称和值(name=value,name=value)
*
* 在定义注解时,经常使用空字符串、0 做为默认值,经常使用负数(-1)表示不存在
*
*/
@Retention(value = RUNTIME)
@Target({METHOD, CONSTRUCTOR})
public @interface MyAnnotation {
//使用 default 给定默认值
String studentName() default "";
// 定义数组类型,数组类型的默认值{}
String[] hobbies() default {"program","game"};
//定义一个参数;不给默认值,在使用的时候就必须传递一个值;
int age();
}
class TestMyAnnotation {
@MyAnnotation(age=10,studentName = "MR.HU")
public void getInfo() {
}
}
光定义注解是没什么用的,还需要给这些注解附上解析,通过其他程序解析,这样才发挥其作用;否则,用处不大。
接口 | 描述 |
---|---|
AnnotatedElement(接口) | 是所有程序元素(Class、Method、Constructor等)的父接口 |
< T extends Annotation> T getAnnotation(Class< T > annotationClass) | 如果存在该元素的指定类型的注解,则返回这些注解,否则返回 null。 |
getAnnotations() | 返回此元素上存在的所有注解。 |
getDeclaredAnnotations() | 返回直接存在于此元素上的所有注释 |
isAnnotationPresent(Class annotationClass) | 如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。 |
注:这四个方法都是在java.lang.reflect.AnnotatedElement
接口中定义的方法
所有实现类:
AccessibleObject
,Class
,Constructor
,Field
,Method
,Package
使用注解完成类
和数据表
之间的映射关系。应用案例如下:
定义两个注解,表
跟 字段
注解
表注解
有一个表名参数(用于解析数据表名称),字段注解
有三个参数:name、type、length(三个必要属性)
通过反射解析这个两个注解,并拼接一个简单的sql语句。
(一)表注解
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
public @interface TableAnnotation {
String tableName() default "";
}
(二)字段注解
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = { ElementType.FIELD })
public @interface FieldAnnotation {
String columnName() default "";
String columnType() default "";
int columnLenth() default 0;
}
(三)注解解析
public class ParseAnnotation {
//拼接创建表的简单语句
public static String createTableSql(String className) {
Class clazz = null;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//获取的类的指定注解
TableAnnotation tableAnnotation = (TableAnnotation) clazz.getAnnotation(TableAnnotation.class);
String tableName = tableAnnotation.tableName();
//获取类属性的所有注解;拼接一个字字符串
StringBuffer sqlBody = new StringBuffer();
StringBuffer fullSql = new StringBuffer();
Field[] fields = clazz.getDeclaredFields();//获取所有属性,包括private
for(Field field:fields) {
FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class);
String name = fieldAnnotation.columnName();
String type = fieldAnnotation.columnType();
int length = fieldAnnotation.columnLenth();
sqlBody.append(name).append(" ").append(type).append(" ").append("(").append(length).append(")").append(",");
}
//去掉sqlBody 最后一个逗号
String sqlContent = (String) sqlBody.subSequence(0,sqlBody.length()-1);
//拼接一个完整的创建语句
fullSql.append("create table ").append(tableName).append("(").append(sqlContent).append(")");
return fullSql.toString();
}
}
(四) 应用测试
public class TestAnnotation {
public static void main(String[] args) throws ClassNotFoundException {
String sql = ParseAnnotation.createTableSql("orm.Student");
//create table t_student(id int (10),name varchar (20),age int (3))
System.out.print(sql);
//通过JDBC创建表了
}
}
用一张图来总结注解
图片借用地址:深入理解Java:注解(Annotation)–注解处理器