十五、注解
1、注解概述
注解可以做到在不改变代码逻辑的前提下在代码中嵌入补充信息
注解是JDK1.5才引入的
默认情况下注解可以出现在类上、方法上、属性上、构造方法上、方法参数上等…
注解与注释
-
注释:给程序员看的,编译器编译时会忽略注释
-
注解:给编译器看的,或给其它程序看的,程序根据有没有这个注解来决定不同的处理方式
框架是如何实现的:框架 = 反射 + 注解 + 设计模式
2、Java预置注解
Java内置的注解中常用的有以下几个
-
@Deprecated
-
@Override
-
@SuppressWarnings
-
@FunctionalInterface
15.2.1、@Deprecated
用来标记过时的元素
在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素(比如过时的类、过时的方法、过时的属性等)
该注解有两个属性
-
since属性值表示从哪个版本开始已过时
-
forRemoval属性值如果是true表示已移除
【示例】
package com.java.neizhi;
/*
* JDK的内置注解:@Deprecated
* 1. 被这个注解标注的元素表示已过时
* 2. 这个注解是给编译器看的,编译器看到这个注解之后会有警告提示信息
* 3. 经过测试 @Deprecated 注解可以标注的元素很多。例如:类上,方法上,属性上....
*/
public class AnnotationTest01 {
public static void main(String[] args) {
MyClass1 myClass1 = new MyClass1();
System.out.println(myClass1.num);
myClass1.doSome();
}
}
// 标注这个类已过时,不建议使用了
@Deprecated
class MyClass1 {
// since属性值表示从哪个版本开始已过时。
// forRemoval属性值如果是true表示已移除。
@Deprecated(since = "9", forRemoval = true)
public int num = 100;
@Deprecated
public void doSome() {
}
}
15.2.2、@Override
该注解用于修饰实例方法,编译器会在编译阶段对标注了该注解的方法进行检查,检查这个方法是否重写了父类方法,如果没有重写父类方法则报错
这个@Override注解只能使用在实例方法上,其他位置不能应用
【示例】
package com.java.neizhi;
/*
* JDK内置的注解:@Override
* 1. 给编译器看的
* 2. 这个注解标注实例方法,被标注的方法必须是重写父类的方法
* 3. 这个注解就是在编译阶段进行方法检查的,检查这个方法是否重写了父类方法,如果没有重写父类方法则报错。
* 4. 通过测试这个@Override注解只能使用在实例方法上,其他位置不能应用。
*/
//@Override不能标注类
public class AnnotationTest02 {
//@Override不能标注实例属性
public int name = 100;
//@Override不能标注静态属性
public static int num = 100;
@Override
public boolean equals(Object obj) {
return false;
}
//@Override不能用在静态方法上
public static void m() {
}
}
15.2.3、@SuppressWarnings(抑制警告的注解)
在实际开发中建议尽量不要忽略警告而是真正的去解决警告
@SuppressWarnings(“rawtypes”):抑制未使用泛型的警告
@SuppressWarnings(“resource”):抑制未关闭资源的警告
@SuppressWarnings(“deprecation”):抑制使用了已过时资源时的警告
@SuppressWarnings(“all”):抑制所有警告
【示例】
package com.java.neizhi;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;
/*
* Java内置注解:@SuppressWarnings
* 1. 主要作用是:抑制警告
* 2. 该注解常见的属性值:
* rawtypes:抑制未使用泛型的警告
* resource: 抑制未关闭资源的警告
* deprecation: 抑制使用了已过时资源时的警告
* all:抑制所有警告
*/
@SuppressWarnings("all")
public class AnnotationTest03 {
public static void main(String[] args) throws Exception {
// 抑制未使用泛型的警告
@SuppressWarnings("rawtypes")
List list = new ArrayList();
// 抑制未关闭资源的警告
@SuppressWarnings("resource")
FileInputStream in = new FileInputStream("e:/file.txt");
// 抑制使用了已过时资源时的警告
@SuppressWarnings("deprecation")
MyClass3 myClass3 = new MyClass3();
}
}
// 标注这个类已过时,不建议使用了
@Deprecated
class MyClass3 {
// since属性值表示从哪个版本开始已过时。
// forRemoval属性值如果是true表示已移除。
@Deprecated(since = "9", forRemoval = true)
public int num = 100;
@Deprecated
public void doSome() {
}
}
15.2.4、@FunctionalInterface
"函数式接口"的注解
这个是 JDK1.8 版本引入的新特性
使用@FunctionalInterface标注的接口有且只能存在一个抽象方法,否则就会发生编译错误(注意:接口中的默认方法或静态方法可以有多个)
【示例】
package com.java.neizhi;
/*
* 关于Java内置注解:@FunctionalInterface
* 1. 这个注解是专门用来标注接口的
* 2. 被标注的接口必须是一个函数式接口,如果不是函数式接口,则编译器报错。
* 3. 这个注解也是给编译器看的
* 4. 如果这个接口中抽象方法只有一个(有且仅有一个)称为函数式接口
* 5. 被 @FunctionalInterface 标注的接口中允许有多个默认方法和静态方法
*/
public class AnnotationTest04 {
}
@FunctionalInterface
interface Flyable {
void fly();
//void run();
default void run() {
System.out.println("默认方法是可以的");
}
static void doSome() {
System.out.println("静态方法");
}
}
3、自定义注解
15.3.1、自定义注解
-
使用 @interface 来定义注解
所有自定义的注解的父类是"java.lang.annotation.Annotation"
-
注解也可以定义属性
属性定义时属性名后面必须加一个小括号
-
属性的类型只能是
-
byte
-
short
-
int
-
long
-
float
-
double
-
boolean
-
char
-
String
-
Class
-
枚举类型
-
注解类型
-
以上所有类型或者其一维数组形式
【示例】
package com.java.dingyi;
import com.java.MyAnnotation;
import com.java.Season;
/**
* 自定义的注解
* 1、使用"@interface"定义注解
*/
public @interface DataBaseInfo {
/*
* 注解也可以定义属性,但是属性定义时有要求,属性名后面必须添加:()
* 语法:
* 属性的类型 属性的名字();
*/
byte b() default 0;// 使用 default 关键字来指定属性的默认值
short s() default 0;// 使用 default 关键字来指定属性的默认值
int i() default 0;// 使用 default 关键字来指定属性的默认值
long l() default 0L;// 使用 default 关键字来指定属性的默认值
float f() default 0.0F;// 使用 default 关键字来指定属性的默认值
double d() default 0.0;// 使用 default 关键字来指定属性的默认值
boolean flag() default false;// 使用 default 关键字来指定属性的默认值
char c() default '0';// 使用 default 关键字来指定属性的默认值
Class clazz() default String.class;// 使用 default 关键字来指定属性的默认值
Season season() default Season.SPRING;// 使用 default 关键字来指定属性的默认值
MyAnnotation myAnnotation();// 使用 default 关键字来指定属性的默认值
/*
* 可以是一维数组形式
*
* @return
*/
String[] names();
/*
注解的属性的数据类型必须是以上的几种类型或者这几种类型的一维数组,不能是其他类型
*/
//Object obj();
}
---------------------------
package com.java.dingyi;
/**
* 自定义的注解(以下这是注解的定义过程!!!!!)
*/
public @interface MyAnnotation {
}
-------------------------------
package com.java.dingyi;
/**
* 季节的枚举类型
*/
public enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
15.3.2、注解的使用
-
如果属性只有一个并且属性名是value时,使用注解时value可以省略不写
-
如果属性是一个数组
使用注解时数组值只有一个则数组的大括号是可以省略的
-
注解在使用时必须给属性赋值,除非你使用了default关键字为属性指定了默认值
-
注解默认可以使用在类上、方法上、属性上、构造方法上、方法参数上等…
示例1
规则1:如果属性只有一个并且属性名是value时,使用注解时value可以省略不写
package com.java.dingyi;
public @interface Table1 {
/*
* 有一个属性并且这个属性的名字是value
*/
String value();
}
----------
package com.java.dingyi;
// 正常使用注解
// @Table1(value="t_user")
// 如果属性名是value的话在使用注解的时候该属性名可以省略
@Table1("t_user")
public class TableTest01 {
@SuppressWarnings("all")
public static void main(String[] args) {
}
}
示例2
如果属性是一个数组
使用注解时数组值只有一个则数组的大括号是可以省略的
package com.java.dingyi;
public @interface Table2 {
String[] value();
}
-------------------------
package com.java.dingyi;
// 正常使用注解
// @Table2(value = {"t_user1", "t_user2"})
// 如果属性名是value的话, 在使用注解的时候,该属性名可以省略
@Table2({"t_user1", "t_user2"})
public class TableTest02 {
@SuppressWarnings("all")
public static void main(String[] args) {
}
}
示例3
如果这个注解中有属性,那么使用的时候,必须给属性赋值,没有赋值则报错
除非定义注解的时候给属性指定了默认值
语法
@注解名(属性名=值,属性名=值,属性名=值,属性名=值,属性名=值)
@注解名({属性名=值,属性名=值,属性名=值,属性名=值,属性名=值})
package com.java.dingyi;
public @interface Table3 {
/*
* 注解也可以定义属性,但是属性定义时有要求,属性名后面必须添加:()
* 语法:
* 属性的类型 属性的名字();
*/
String driver() default "com.mysql.cj.jdbc.Driver"; // 使用 default 关键字来指定属性的默认值。
String url();
String user();
String password();
byte b() default 0;
short s() default 0;
int i() default 0;
long l() default 0L;
float f() default 0.0F;
double d() default 0.0;
boolean flag() default false;
char c() default '0';
Class clazz() default String.class;
Season season() default Season.SPRING;
MyAnnotation myAnnotation();
/*
* 可以是一维数组形式
*
* @return
*/
String[] names();
}
----------------
package com.java.dingyi;
// 注解在使用时必须给属性赋值,除非你使用了default关键字为属性指定了默认值
public class TableTest03 {
// 语法规则:如果这个注解中有属性,那么使用的时候,必须给属性赋值。没有赋值则报错。
// 除非你定义注解的时候给属性指定了默认值。
// 怎么给属性赋值?语法:@Table3(属性名=值,属性名=值,属性名=值,属性名=值,属性名=值)
@Table3(
//driver="oracle.jdbc.driver.OracleDriver",
url = "jdbc:mysql://localhost:3306/test",
user = "root",
password = "123456",
myAnnotation = @MyAnnotation,
names = {"zhangsan", "lisi", "wangwu"},
flag = true,
i = 100,
clazz = Integer.class,
season = Season.WINTER)
public void connDB() {
}
}
-----------------------------
package com.java.dingyi;
/**
* 季节的枚举类型
*/
public enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
--------------------------------
package com.java.dingyi;
/**
* 自定义的注解(以下这是注解的定义过程!!!!!)
*/
public @interface MyAnnotation {
}
示例4
注解默认可以使用在类上、方法上、属性上、构造方法上、方法参数上等…
package com.java.dingyi;
/**
* 自定义的注解(以下这是注解的定义过程!!!!!)
*/
public @interface Table4 {
}
--------------------------------
package com.java.dingyi;
/*
* 测试注解的使用位置
* 类上、方法上、属性上、构造方法上、方法参数上等......
*/
@Table4
public class TableTest04 {
@Table4
private String name;
@Table4
public void doSome() {
}
public void doOther(@Table4 String name, @Table4 String password) {
}
public void toDo(
@Table4
String name,
@Table4
String password) {
}
}
4、元注解
用来标注注解的注解叫做元注解(也是JDK内置的注解)
常用的元注解
@Retention:设置注解的保持性
@Target:设置注解可以出现的位置
@Documented:设置注解是否可以生成到帮助文档中
@Inherited:设置注解是否支持继承
@Repeatable:设置注解在某一个元素上是否可以重复使用(Java8的新特性)
15.4.1、@Retention
Retention英文意思有保留、保持的意思,它表示注解存在阶段是保留在源代码(编译期)、字节码(类加载)或者运行时(JVM中运行)
在@Retention注解中使用枚举RetentionPolicy来表示注解保留时期
@Retention(RetentionPolicy.SOURCE):注解仅存在于源代码中,在字节码文件中不包含
@Retention(RetentionPolicy.CLASS):注解在字节码文件中存在,但运行时无法获得(默认)
@Retention(RetentionPolicy.RUNTIME):注解在字节码文件中存在,且运行时可通过反射获取
package com.java.meta1;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
//@Retention(value= RetentionPolicy.SOURCE) // @MyAnnotation 注解保留在源码中
//@Retention(value= RetentionPolicy.CLASS) // @MyAnnotation 注解保留在字节码中,这是默认的行为,但不能被反射
//@Retention(value= RetentionPolicy.RUNTIME) // @MyAnnotation 注解保留在字节码中,并且在运行时可以被反射
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
}
------------------------
package com.java.meta1;
import java.lang.annotation.Annotation;
@MyAnnotation // 这个注解会被保留到字节码中,并且在运行时可以被反射
public class Test {
public static void main(String[] args) {
// 获取这个类
Class<Test> testClass = Test.class;
// 获取这个类上的注解
//MyAnnotation annotation = testClass.getAnnotation(MyAnnotation.class);
// java.lang.annotation.Annotation是所有注解的老祖宗
Annotation annotation = testClass.getAnnotation(MyAnnotation.class);
System.out.println(annotation);
}
}
15.4.2、@Target
用于描述注解可以使用的位置,该注解使用ElementType枚举类型用于描述注解可以出现的位置
ElementType有如下枚举值
- @Target(ElementType.TYPE):作用于接口、类、枚举、注解
- @Target(ElementType.FIELD):作用于属性、枚举的常量
- @Target(ElementType.METHOD):作用于方法
- @Target(ElementType.PARAMETER):作用于方法参数
- @Target(ElementType.CONSTRUCTOR):作用于构造方法
- @Target(ElementType.LOCAL_VARIABLE):作用于局部变量
- @Target(ElementType.ANNOTATION_TYPE):作用于注解
- @Target(ElementType.PACKAGE):作用于包
- @Target(ElementType.TYPE_PARAMETER):作用于泛型,即泛型方法、泛型类和泛型接口
- @Target(ElementType.TYPE_USE):作用于任意类型
【示例】
package com.java.meta2;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
//@Target(value={ElementType.METHOD})
//@Target(ElementType.METHOD) // 限定注解只能出现在方法上
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
public @interface MyAnnotation {
}
-----------------------------------
package com.java.meta2;
@MyAnnotation
public class Test {
@MyAnnotation
int num = 100;
@MyAnnotation
public static void main(String[] args) {
}
}
15.4.3、@Documented
Documented的英文意思是文档
使用javadoc.exe工具可以从程序源代码中抽取类、方法、属性等注释形成一个源代码配套的API帮助文档,而该工具抽取时默认不包括注释内容。
如果使用的注解被@Documented标注,那么该注解就能被javadoc.exe工具提取到API文档。
IDEA设置注释:
/**
* className:${NAME}
* dateTime:${DATE} ${TIME}
* author: 龙弟弟
* description:
*/
IDEA生成文档
提取设置方法
15.4.4、@Inherited
Inherited的英文意思是继承,但是这个继承和我们平时理解的继承大同小异,一个被@Inherited注解了的注解修饰了一个父类,则它的子类也继承了父类的注解。
【示例】
package com.java.meta4;
/**
* ClassName: Animal
* Description:
* Datetime: 2025/2/04 11:35
* Version: 1.0
*/
@MyAnnotation
public class Animal {
}
--------------------------------
package com.java.meta4;
/**
* ClassName: Cat
* Description:
* Datetime: 2025/2/04 11:35
* Version: 1.0
*/
public class Cat extends Animal {
}
---------------------------------
package com.java.meta4;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* ClassName: MyAnnotation
* Description:
* Datetime: 2025/2/04 11:35
* Version: 1.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Inherited // 表示 @MyAnnotation注解能够被继承
public @interface MyAnnotation {
}
-------------------------
package com.java.meta4;
/**
* ClassName: Test
* Description:
* Datetime: 2025/2/04 11:35
* Version: 1.0
*/
public class Test {
public static void main(String[] args) {
Class<Cat> catClass = Cat.class;
MyAnnotation annotation = catClass.getAnnotation(MyAnnotation.class);
System.out.println(annotation);
}
}
-----------------------------
15.4.5、@Repeatable
Repeatable表示可重复的含义,意思是在一个位置该注解可以使用多次
该注解属于JDK1.8版本的新特性
定义方式
-
定义一个注解
定义一个注解,该注解使用"@Repeatable"注解标注,并在该注解的属性中引用该注解的复数注解
package com.java.meta6; import java.lang.annotation.Repeatable; /** * ClassName: Author * Description: * <p> * Datetime: 2025/2/04 12:00 * Author: 龙弟弟 * Version: 1.0 */ @Repeatable(Authors.class) public @interface Author { /** * 作者的名字 * * @return 作者的名字 */ String name(); }
-
定义一个复数注解
定义注解的复数形式,在该复数注解中定义注解的一维数组
-
使用注解
package com.java.meta6; /** * ClassName: Test * Description: * <p> * Datetime: 2025/2/04 12:00 * Author: 龙弟弟 * Version: 1.0 */ public class Test { @Author(name = "张三") @Author(name = "李四") public void doSome() { } }
5、反射获取注解
获取类上的所有注解
Annotation[] annotations = clazz.getAnnotations();
获取类上指定的某个注解
clazz.isAnnotationPresent(AnnotationTest01.class)
AnnotationTest01 an = clazz.getAnnotation(AnnotationTest01.class);
获取属性上的所有注解
Annotation[] annotations = field.getAnnotations();
获取属性上指定的某个注解
field.isAnnotationPresent(AnnotationTest02.class)
AnnotationTest02 an = field.getAnnotation(AnnotationTest02.class);
获取方法上的所有注解
Annotation[] annotations = method.getAnnotations();
获取方法上指定的某个注解
method.isAnnotationPresent(AnnotationTest02.class)
AnnotationTest02 an = method.getAnnotation(AnnotationTest02.class);
6、框架原理
编写程序扫描一个包下所有的类,凡是被 @Table 注解标注的类都要生成一条建表语句,表名在 @Table 注解中指定。
被@Table 标注的类中的属性被 @Column 注解标注,在 @Column注解中描述字段的名称和字段的数据类型
建表语句如下:
create table t_user(
id int,
name varchar,
age int,
email varchar
);
【示例】
package com.java.reflect;
import java.lang.annotation.*;
/**
* ClassName: Annotation1
* Description:
* <p>
* Datetime: 2025/2/05 14:04
* Author: 龙弟弟
* Version: 1.0
*/
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Annotation1 {
String name() default "";
int age() default 0;
}
------------------------------------------
package com.java.reflect;
import java.lang.annotation.*;
/**
* ClassName: Annotation2
* Description:
* <p>
* Datetime: 2025/2/05 14:04
* Author: 龙弟弟
* Version: 1.0
*/
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Annotation2 {
String email() default "";
double price() default 0.0;
}
----------------------------------------------
package com.java.reflect;
/**
* ClassName: MyClass
* Description:
* <p>
* Datetime: 2025/2/05 14:04
* Author: 龙弟弟
* Version: 1.0
*/
@Annotation1(name="zhangsan22222", age=3333)
@Annotation2(email="zhangsan@123.com", price = 3000.5)
public class MyClass {
@Annotation1
@Annotation2
String s;
@Annotation1
@Annotation2
public void doSome(){
}
}
-------------------------------
package com.java.reflect;
/**
* ClassName: Test
* Description:
* <p>
* Datetime: 2025/2/05 14:04
* Author: 龙弟弟
* Version: 1.0
*/
public class Test {
public static void main(String[] args) {
// 获取类
Class<MyClass> mcClass = MyClass.class;
// 获取类上的所有注解
/*Annotation[] annotations = mcClass.getAnnotations();
for(Annotation a : annotations){
System.out.println(a);
}*/
// 判断该类上是否存在这个注解
if (mcClass.isAnnotationPresent(Annotation1.class)) {
// 获取指定的某个注解
Annotation1 a1 = mcClass.getAnnotation(Annotation1.class);
// 访问注解对象中的属性
System.out.println(a1.name());
System.out.println(a1.age());
}
if (mcClass.isAnnotationPresent(Annotation2.class)) {
Annotation2 a2 = mcClass.getAnnotation(Annotation2.class);
System.out.println(a2.email());
System.out.println(a2.price());
}
}
}
package com.java.reflect;
/**
-
ClassName: Test
-
Description:
-
-
Datetime: 2025/2/05 14:04
-
Author: 龙弟弟
-
Version: 1.0
*/
public class Test {
public static void main(String[] args) {
// 获取类
Class mcClass = MyClass.class;// 获取类上的所有注解 /*Annotation[] annotations = mcClass.getAnnotations(); for(Annotation a : annotations){ System.out.println(a); }*/ // 判断该类上是否存在这个注解 if (mcClass.isAnnotationPresent(Annotation1.class)) { // 获取指定的某个注解 Annotation1 a1 = mcClass.getAnnotation(Annotation1.class); // 访问注解对象中的属性 System.out.println(a1.name()); System.out.println(a1.age()); } if (mcClass.isAnnotationPresent(Annotation2.class)) { Annotation2 a2 = mcClass.getAnnotation(Annotation2.class); System.out.println(a2.email()); System.out.println(a2.price()); }
}
}