一、 前言
我们大家都知道Java代码中使用注释是为了向以后阅读这份代码的人解释说明一些事情,注解是注释的升级版,它可以向编译器、虚拟机等解释说明一些事情。比如我们非常熟悉的@Override就是一种元注解,它的作用是告诉编译器它所注解的方法是重写父类的方法,这样编译器就会去检查父类是否存在这个方法,以及这个方法的签名与父类是否相同。
我们为什么学习注解?学习注解有什么好处?学完能做什么?
(1)、能够读懂别人的代码,特别是框架相关的代码。
(2)、让编程更加简洁,代码更加清晰。
(3)、让别人高看一眼。
二、 Java中的常见注解
1、JDK自带注解@Override、@Deprecated、@Suppvisewarnings
(1)、@Override
定义
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
从这个注解类的定义我们可以看到,这个注解可以被用来修饰方法,并且它只在源码时有效,在编译后的class文件中便不存在。
这个注解的主要作用是告诉编译器被修饰的方法是重写的父类中的相同签名的方法,编译器会对此做出检查,若发现父类中不存在这个方法或是存在的方法签名不同,则会报错。
(2)、@Deprecated
定义
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
从它的定义我们可以知道,它会被文档化,能够保留到运行时,能够修饰构造方法、属性、局部变量、方法、包、参数、类或接口。
这个注解的作用是告诉编译器被修饰的程序元素已被“废弃”,不再建议用户使用。
(3)、@Suppvisewarnings
定义
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
它能够修饰的程序元素包括类或接口、属性、方法、参数、构造器、局部变量,只能存活在源码时,取值为String[]。它的作用是告诉编译器忽略指定的警告信息,它可以取的值如下所示:
deprecation:忽略使用了废弃的类或方法时的警告;
unchecked:执行了未检查的转换
fallthrough:swich语句款中case忘加break从而直接“落入”下一个case;
path:类路径或原文件路径等不存在;
all:以上所有情况产生的警告均忽略。
(4)、实际使用介绍
自定义一个接口
/**
* 动物类接口
*/
public interface Animal {
public void eat();
public void sleep();
public void speak();
public void sing();
}
定义Dog类实现Animal接口
/**
* 狗的实现类
*
* @author YUBIN
*/
public class Dog implements Animal {
@Override
public void eat() {
}
@Override
public void sleep() {
}
@Override
public void speak() {
}
@Override
public void sing() {
}
}
此时你们在Dog类上的方法中看到@Override注解,表示该方法是重写或实现父类或接口的方法,如果此时你在Dog类中再书写一个接口中不存在的方法时,然后在方法上使用@Override注解,则编译检查会报错,如下图
我们仔细分析Animal这个接口,可以发现sing这个唱歌方法并不适用于所有的动物,我们应该把它去除,但是可能已经有人使用了我们的这个接口,这时我们应该使用@Deprecated注解来告知别人该方法已经过时了。
效果:
但是这样会导致代码中出现警告图标,对于有要求的公司来说,是需要去除警告的,可以使用@SuppressWarnings("deprecation")来去除这个警告,这样一来这个警告就消失了
三、 注解分类
1、按照运行机制分
(1)、源码注解:注解只在源码中存在,编译成.class文件就不存在了。
(2)、编译时注解:注解在源码和.class文件中都存在。
(3)、在运行阶段还起作用,甚至会影响运行逻辑的注解。
元注解:注解的注解叫做元注解
四、 自定义注解
1、自定义注解分语法要求
(1)、使用@interface关键字定义注解
(2)、成员以无参数无异常方式声明
(3)、可以使用default为成员指定默认值
(4)、成员类型是受限的,合法的类型包括基本数据类型及String、Class、Annotation、Enumeration
(5)、如果注解只有一个成员,则成员建议取名为value(),在使用的时可以忽略成员名和赋值号(=)
2、注解的作用域
使用@Target来指明注解的作用域,表示该注解可以使用在什么地方,如下图
CONSTRUCATOR:构造方法声明
FIELD:字段声明
LOCAL_VARIABLE:局部变量声明
METHOD:方法声明
PACKAGE:包声明
PARAMETER:参数声明
TYPE:类或接口
3、注解的生命周期
使用@Retention来指明定义的注解的生命周期
SOURCE:只在源码中显示,编译时会丢弃
CLASS:编译时会记录到class中,运行时忽略
RUNTIME:运行时存在,可以通过反射读取
4、@Inherited 允许子类继承
使用该注解,可以让定义在父类的注解被子类所获取,注意对接口无效。
5、@Documented
生成javadoc时会包含注解
五、 解析注解
通过反射获取类、函数或成员上的运行时注解信息,从而实现动态控制程序运行的逻辑。在java.lang.redlect反射包中提供了一个接口AnnotatedElement,该接口提供了获取注解信息的几个方法,Class、Constructor、Field、Method、Package等都实现了该接口。
boolean isAnnotationPresent(Class extends Annotation> annotationClass); // 判断指定元素上是否存在指定注解
T getAnnotation(Class annotationClass); // 获取指定元素的指定注解
Annotation[] getAnnotations(); // 获取指定元素上存在的注释
Annotation[] getDeclaredAnnotations(); // 获取指定元素上存在的直接注释,非父类的
运行时注解解析案例:
public class Test {
@SuppressWarnings("deprecation")
public static void main(String[] args) {
Class bigDogClass = BigDog.class;
// 判断类上是否存在MyAnnotation注解
boolean isExist = bigDogClass.isAnnotationPresent(MyAnnotation.class);
if (isExist) {
MyAnnotation annotation = bigDogClass.getAnnotation(MyAnnotation.class);
System.out.println(annotation.desc());
}
}
}
六、 注解应用实战
下面我们以一个通过注解来实现生成sql为例,来理解自定义注解的使用
1、定义@Table注解
@Target(ElementType.TYPE) // 注解的作用范围 作用在类或接口上
@Retention(RetentionPolicy.RUNTIME) // 注解的生命周期 运行时
public @interface Table {
String value();
}
2、定义@Column注解
@Target(ElementType.FIELD) // 注解的作用范围 作用在字段上
@Retention(RetentionPolicy.RUNTIME) // 注解的生命周期 运行时
public @interface Column {
String value(); // 只有一个成员,则定义成value,在使用的时候无参使用参数名和赋值符号=
}
3、定义实体类
/**
* 用户的实体类
*
* @author YUBIN
*/
@Table("tb_user")
public class User {
@Column("id")
private Long id;
@Column("user_name")
private String userName;
@Column("sex")
private Integer sex;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getSex() {
return sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
}
4、定义UserDao
/**
* 用户的持久层
*
* @author YUBIN
*/
public class UserDao {
public String query(User user) {
StringBuffer sb = new StringBuffer("select * from ");
Class c = User.class;
// 获取类上面的Table注解
boolean isExistTableName = c.isAnnotationPresent(Table.class);
if (!isExistTableName) {
return null;
}
// 获取表名
Table table = (Table) c.getAnnotation(Table.class);
sb.append(table.value()).append(" where 1=1");
Field[] fields = c.getDeclaredFields();
if (fields != null && fields.length > 0 && user != null) {
for (Field field : fields) {
// 获取字段上的Column注解
boolean isExistField = field.isAnnotationPresent(Column.class);
if (isExistField) {
// 判断该字段是否有值
// 获取getXxx方法
String getMethodName = "get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
try {
Method method = c.getMethod(getMethodName);
Object result = method.invoke(user, null);
if (result != null) {
Column column = field.getAnnotation(Column.class);
sb.append(" and " + column.value());
if (result instanceof String) {
sb.append("='").append(result).append("'");
} else {
sb.append("=").append(result);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return sb.toString();
}
}
5、测试类
/**
* 测试类
*
* @author YUBIN
*/
public class Test {
public static void main(String[] args) {
UserDao userDao = new UserDao();
User user = new User();
user.setId(1L);
user.setUserName("zhangsan");
System.out.println(userDao.query(user));
}
}
6、测试结果
七、 结语
希望我的这篇文章能对大家理解注解、使用注解并且能够自定义注解有所帮助,如有疑问可在评论区交流,谢谢大家的支持。