JavaSE
提供额外信息(用Java代码来表示不太合适)。
Java提供了注释,可是注释有些不好的地方:
- 人为约定。
- javac在编译时,对注释“视而不见”
- 只有固定语法(如:// ),没有标准形式(所以无法通过代码来解析)
所以有了注解,为我们的代码提供额外的信息。
注解:
一.综述:
注解为我们的代码添加额外信息,提供了一种形式化的方法,能方便我们在代码中获得并使用这些信息。
常使用的注解(Java提供的注解很少):
@Override // 覆盖父类方法
@Deprecated //添加了Deprecated注解的方法,说明其已经过时。(过时是指JDK官方已经不推荐,或者未来要淘汰了)
1.注意事项:
- 注解本身只是说明了一些额外的信息和因为注解而产生的特殊效果没有关系。
- 这些特殊效果是需要专门注解处理器来处理产生的。
- 注解:
注解的定义(有标准形式);
注解的使用(添加额外信息);
注解的处理(注解处理器)
二.定义
public @interface 注解名 { //信息
// 第一条(名称,取值类型)
// 第二条(名称,取值类型)
...
}
//与类,接口定义相似的是注解也代表一个数据类型,即一种额外信息的类型。
//注解之间不能继承
格式:
@interface 注解名 {
返回值类型1 方法名1(); //只能取String或Class之外的类对象
返回值类型2 方法名2();
...
}
1. 格式类似于接口中的方法,但是含义完全不同
2. 方法名是一条信息的名称,返回值类型是一条信息的取值类型
3. 取值类型:
基本数据类型
String类型
Class类型
注解类型及以上类型的数组。
例:
//定义注解类,即定义某种注解的标准化形式
@interface AgeConstraint { //年龄取值范围
int lowerBound();
int upperBound();
}
三.注解的使用
类比于类的使用,需要创建实例和为成员变量赋值。注解的使用也需要注解实例和为注解实例种的每个属性(每条信息)赋值。
@注解的类型(属性名1=属性值1,属性名2=属性值2....) // 注解实例的创建过程
// 给每个属性赋值是重点。必须确保每个属性有值。
赋值方式:
1. 显式赋值
2. 默认值:可以无需显式赋值。(String的默认值不能为null,可以为"")
3. 若在注解类种只剩下一个叫做value的属性没有赋默认值或者只有这个value属性。可以在注解实例中直接----->@AgeConstraint(要赋的值),省略属性名。
@interface AgeConstraint {
int maxAge() default 26;
int minAge();
}
class Student {
@AgeConstraint(maxAge = 25, minAge = 18) //注解实例,表示一条具体的额外信息,并没有什么效果。
int age;
}
四.注解的处理
注解只表示额外信息,至于拿额外信息来处理则需要注解处理器(利用反射,从Class中获取注解实例信息)来完成。
1.元注解 (meta 元)
元数据:描述数据的数据
元注解:描述注解的注解
- @Rentention元注解:定义注解的保留级别
a.RententionPolicy.RUNTIME
b.RetentionPolicy.CLASS
c.RetentionPolicy.SOURCE
- @Target元注解:限定注解作用的目标
a.整个类 ElementType.TYPE
b.成员变量 ElementType.FIELD
c.构造方法 ElementType.CONSTRUCTOR
d.成员方法 ElementType.METHOD
注解实例可以作用于类,成员变量,成员方法,构造方法。
因此要获取注解实例,则该注解作用于谁就通过谁获取。
class TargetClass {
@MyAnnotation(description = "111") //报错
private int intValue;
@MyAnnotation(description = "111") //不报错
public void test(){
}
}
@Target({ElementType.METHOD,ElementType.CONSTRUCTOR}) //数组类型的属性的赋值(静态初始化)
@interface MyAnnotation {
String description();
}
/*
利用注解,结合自定义的注解处理器,实现需求:
1.定义一个Student类,包含name和age两个成员
name中包含的字符个数不得超过指定值(注解)
age必须在指定范围内(具体的约束条件信息-> 注解)
2.name和age都满足条件才能创建Student对象,否则抛出异常。(该效果由注解处理器来实现)
*/
public class Demo1 {
public static void main(String[] args) {
StudentFactory studentProcessor = new StudentFactory();
int age = 50;
String name = "张三";
Student student = studentProcessor.generateStudent(age,name);
}
}
@interface AgeConstraint { //定义一个注解类,描述年龄限制
int maxAge();
int minAge();
}
@interface NameConstraint { //描述学生名字长度上限的额外信息
int lengthLimit();
}
class Student{
@Rentation(RetentionPolicy.RUNTIME) //定义该注解实例的保留级别。RUNTIME级别是在内存中还会保留
@NameConstraint(lengthLimit = 5) // 创建该注解实例
String name;
@Rentation(RetentionPolicy.RUNTIME)
@AgeConstraint(maxAge = 25, minAge = 18)
int age;
private Student(int age, String name) { //约定Student对象由StudentFactory创建
this.age = age;
this.name = name;
}
}
//需要一个注解处理器,来获取注解额外信息的具体取值,根据对象创建的值的情况,来产生处理效果。
class StudentFactory { //注解处理器。获取注解信息,并根据业务逻辑处理。
private Class stuClz;
private Constructor stuConstructor;
public StudentFactory() { //放在构造方法里,所以只需要创建一次
this.stuClz = Student.class;
stuConstructor = stuClz.getDeclaredConstructor(int.class, String.class); //利用反射技术,获取构造方法信息。
stuConstructor.setAccessible(true); //绕过权限检测
}
public Student generateStruden(int age, String name) { //创建Student对象
judgeAge(age); //判断age是否满足注解的要求
judgeName(name);
Student stu = (Student) stuConstructor.newInstance(age,name);
}
private void judgeAge(int age) {
Field ageField = stuClz.getDeclaredField("age"); //(注解实例作用在age成员变量上)所以利用反射技术获取成员变量的信息。
ageField.setAccessible(true); //绕过权限检测
if(ageField.isAnnotationPresent(AgeConstraint.class)) { //判断是否有该注解类作用在成员变量上
AgeConstraint ageConstraint = ageField.getAnnotation(AgeConstraint.class); //获取该注解实例的信息。
int maxAge = ageConstraint.maxAge(); //获取注解实例中的属性值
int minAge = ageConstraint.minAge();
if(age < minAge || age > maxAge)
throw new IllegalArgumentException("age参数非法!" + age);
}
private void judgeName(String name) {
Field nameField = stuClz.getDeclaredField("name");
nameField.setAccessible(true);
if (nameField.isAnnotationPresent(NameConstraint.class)) {
NameConstraint nameAnnotation = nameField.getAnnotation(NameConstraint.class);
int nameLengthLimit = nameAnnotation.lengthLimit();
if(nameLengthLimit < name.length())
throw new IllegalArgumentException("name参数非法!" + name);
}
}
}
五.注解与配置文件
当注解表示配置信息时,功能和配置文件相同
配置文件
优点:可配置,不用改源码。
缺点:不直观,开发效率低。
注解
优点:直观开发效率高(注解和它作用的对象是写在一起)
缺点:硬编码,修改之后需要重新编译运行
使用场景,粗略分:
1. 和代码直接相关的配置,使用注解的方式,添加配置
2. 和运行环境相关的配置,使用配置文件