🌈hello,你好鸭,我是Ethan,西安电子科技大学大三在读,很高兴你能来阅读。
✔️目前博客主要更新Java系列、项目案例、计算机必学四件套等。
🏃人生之义,在于追求,不在成败,勤通大道。加油呀!
🔥个人主页:Ethan Yankang
🔥推荐:史上最强八股文||一分钟看完我的几百篇博客
🔥温馨提示:划到文末发现专栏彩蛋 点击这里直接传送
🔥本篇概览:详细讲解了Java注解的定义与使用!并结合真实案例落实详解。🌈⭕🔥
【计算机领域一切迷惑的源头都是基本概念的模糊,算法除外】
🌈序言:
JAVA基础必序扎实的一批,此关不过,啥都没有。今日得《JAVA核心技术·卷I》之良品辅助,应按本书学之习之,时时复习,长此以往必能穿魂入脉,习得大功。
感谢Cay S. Horstmann给世界留下如此优美的作品。
对于一个强烈想完全掌握JAVA的技术宅来说,JAVA的XXX万万不能放过,这些基础的概念例程都值得细细体味的,千万别觉得都是文字,浪费时间,记住——别违背科学发展的客观规律。别一味地赶进度以满足自己学的都么快的虚荣心,自欺欺人,要老老实实的走好每一步。
上一篇文章详细讲解了XX,建议先将这部分知识掌握之后再来学习本篇内容,点击查看。
🔥
🌈引出:
注解是什么,有什么用,怎么用?今天这篇文章我们就来详细讲解讲解!
【申明·引自原文Spring系列第16篇:深入理解java注解----预备知识-CSDN博客】
灵魂拷问:
- 注解是干什么的?
- 一个注解可以使用多次么?如何使用?
- @Inherited是做什么的?
- @Target中的`TYPE_PARAMETER和TYPE_USER`用在什么地方?
- 泛型中如何使用注解?
- 注解定义可以实现继承么?
- spring中对注解有哪些增强?@Aliasfor注解是干什么的?
一、本文内容
带你玩转java注解,解决上面的所有问题
什么是注解?
代码中注释大家都熟悉吧,注释是给开发者看的,可以提升代码的可读性和可维护性,但是对于java编译器和虚拟机来说是没有意义的,编译之后的字节码文件中是没有注释信息的;
而注解和注释有点类似,唯一的区别就是注释是给人看的,而注释是给编译器和虚拟机看的,编译器和虚拟机在运行的过程中可以获取注解信息,然后可以根据这些注解的信息做各种想做的事情。比如:大家对@Override应该比较熟悉,就是一个注解,加在方法上,标注当前方法重写了父类的方法,当编译器编译代码的时候,会对@Override标注的方法进行验证,验证其父类中是否也有同样签名的方法,否则报错,通过这个注解是不是增强了代码的安全性。
总结
注解是对代码的一种增强,可以在代码编译或者程序运行期间获取注解的信息,然后根据这些信息做各种牛逼的事情。
二、注解如何使用?
3个步骤:
-
定义注解
-
使用注解
-
获取注解信息做各种牛逼的事情
定义注解
关于注解的定义,先来几个问题:
- 如何为注解定义参数?
- 注解可以用在哪里?
- 注解会被保留到什么时候?
定义注解语法
jdk中注解相关的类和接口都定义在java.lang.annotation包中。
注解的定义和我们常见的类、接口类似,只是注解使用@interface来定义,如下定义一个名称为MyAnnotation的注解:
public @interface MyAnnotation {
}
注解中定义参数
注解有没有参数都可以,定义参数如下:
public @interface 注解名称{
[public] 参数类型 参数名称1() [default 参数默认值];
[public] 参数类型 参数名称2() [default 参数默认值];
[public] 参数类型 参数名称n() [default 参数默认值];
}
注解中可以定义多个参数,参数的定义有以下特点:
- 访问修饰符必须为public,不写默认为public
- 该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组
- 该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(后面使用会带来便利操作)
- 参数名称后面的()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法
- default代表默认值,值必须和第2点定义的类型一致
- 如果没有默认值,代表后续使用注解时必须给该类型元素赋值
指定注解的使用范围:@Target
使用@Target注解定义注解的使用范围,如下:
@Target(value = {ElementType.TYPE,ElementType.METHOD})
public @interface MyAnnotation {
}
上面指定了MyAnnotation
注解可以用在类、接口、注解类型、枚举类型以及方法上面,自定义注解上也可以不使用@Target注解,如果不使用,表示自定义注解可以用在任何地方。
看一下@Target
源码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
有一个参数value,是ElementType类型的一个数组,再来看一下ElementType
,是个枚举,源码如下,表示的是注解的适用范围:
package java.lang.annotation;
/*注解的使用范围*/
public enum ElementType {
/*类、接口、枚举、注解上面*/
TYPE,
/*字段上*/
FIELD,
/*方法上*/
METHOD,
/*方法的参数上*/
PARAMETER,
/*构造函数上*/
CONSTRUCTOR,
/*本地变量上*/
LOCAL_VARIABLE,
/*注解上*/
ANNOTATION_TYPE,
/*包上*/
PACKAGE,
/*类型参数上*/
TYPE_PARAMETER,
/*类型名称上*/
TYPE_USE
}
指定注解的保留策略:@Retention
我们先来看一下java程序的3个过程
-
源码阶段
-
源码被编译为字节码之后变成class文件
-
字节码被虚拟机加载然后运行
那么自定义注解会保留在上面哪个阶段呢?可以通过@Retention
注解来指定,如:
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
}
上面指定了MyAnnotation
只存在于源码阶段,后面的2个阶段都会丢失。
来看一下@Retention源码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
有一个value参数,类型为RetentionPolicy枚举,如下:
public enum RetentionPolicy {
/*注解只保留在源码中,编译为字节码之后就丢失了,也就是class文件中就不存在了*/
SOURCE,
/*注解只保留在源码和字节码中,运行阶段会丢失*/
CLASS,
/*源码、字节码、运行期间都存在*/
RUNTIME
}
使用注解
语法
将注解加载使用的目标上面,如下:
@注解名称(参数1=值1,参数2=值2,参数n=值n)
目标对象
直接来案例说明。
无参注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Ann1 { //@1
}
@Ann1 //@2
public class UseAnnotation1 {
}
@1:Ann1为无参注解
@2:类上使用@Ann1注解,没有参数
一个参数的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Ann2 { //@1
String name();
}
@Ann2(name = "我是路人甲java") //@2
public class UseAnnotation2 {
}
一个参数为value的注解,可以省略参数名称只有一个参数
名称为value的时候【前文提到的好处】,使用时参数名称可以省略
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Ann3 {
String value();//@1
}
@Ann3("我是路人甲java") //@2
public class UseAnnotation3 {
}
@1:注解之后一个参数,名称为value
@2:使用注解,参数名称value省略了
数组类型参数
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Ann4 {
String[] name();//@1
}
@Ann4(name = {"我是路人甲java", "欢迎和我一起学spring"}) //@2
public class UseAnnotation4 {
@Ann4(name = "如果只有一个值,{}可以省略") //@3
public class T1 {
}
}
@1:name的类型是一个String类型的数组
@2:name有多个值的时候,需要使用{}包含起来
@3:如果name只有一个值,{}可以省略
为参数指定默认值
通过default为参数指定默认值,用的时候如果没有设置值,则取默认值,没有指定默认值的参数,使用的时候必须为参数设置值,如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Ann5 {
String[] name() default {"路人甲java", "spring系列"};//@1
int[] score() default 1; //@2
int age() default 30; //@3
String address(); //@4
}
@Ann5(age = 32,address = "上海") //@5
public class UseAnnotation5 {
}
@1:数组类型通过{}指定默认值
@2:数组类型参数,默认值只有一个省略了{}符号
@3:默认值为30
@4:未指定默认值
@5:age=32对默认值进行了覆盖,并且为address指定了值
综合案例
@Target(value = {
ElementType.TYPE,
ElementType.METHOD,
ElementType.FIELD,
ElementType.PARAMETER,
ElementType.CONSTRUCTOR,
ElementType.LOCAL_VARIABLE
})
@Retention(RetentionPolicy.RUNTIME)
@interface Ann6 {
String value();
ElementType elementType();
}
@Ann6(value = "我用在类上", elementType = ElementType.TYPE)
public class UseAnnotation6 {
@Ann6(value = "我用在字段上", elementType = ElementType.FIELD)
private String a;
@Ann6(value = "我用在构造方法上", elementType = ElementType.CONSTRUCTOR)
public UseAnnotation6(@Ann6(value = "我用在方法参数上", elementType = ElementType.PARAMETER) String a) {
this.a = a;
}
@Ann6(value = "我用在了普通方法上面", elementType = ElementType.METHOD)
public void m1() {
@Ann6(value = "我用在了本地变量上", elementType = ElementType.LOCAL_VARIABLE) String a;
}
}
上面演示了自定义注解在在类、字段、构造器、方法参数、方法、本地变量上的使用,@Ann6注解有个elementType
参数,我想通过这个参数的值来告诉大家对应@Target中的那个值来限制使用目标的,大家注意一下上面每个elementType
的值。
@Target(ElementType.TYPE_PARAMETER)
这个是1.8加上的,用来标注类型参数,类型参数一般在类后面声明或者方法上声明,这块需要先了解一下泛型泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!不然理解起来比较吃力,来个案例感受一下:
@Target(value = {
ElementType.TYPE_PARAMETER
})
@Retention(RetentionPolicy.RUNTIME)
@interface Ann7 {
String value();
}
public class UseAnnotation7<@Ann7("T0是在类上声明的一个泛型类型变量") T0, @Ann7("T1是在类上声明的一个泛型类型变量") T1> {
public <@Ann7("T2是在方法上声明的泛型类型变量") T2> void m1() {
}
public static void main(String[] args) throws NoSuchMethodException {
for (TypeVariable typeVariable : UseAnnotation7.class.getTypeParameters()) {
print(typeVariable);
}
for (TypeVariable typeVariable : UseAnnotation7.class.getDeclaredMethod("m1").getTypeParameters()) {
print(typeVariable);
}
}
private static void print(TypeVariable typeVariable) {
System.out.println("类型变量名称:" + typeVariable.getName());
Arrays.stream(typeVariable.getAnnotations()).forEach(System.out::println);
}
}
类和方法上面可以声明泛型类型的变量,上面有3个泛型类型变量,我们运行一下看看效果:
类型变量名称:T0
@com.javacode2018.lesson001.demo18.Ann7(value=T0是在类上声明的一个泛型类型变量)
类型变量名称:T1
@com.javacode2018.lesson001.demo18.Ann7(value=T1是在类上声明的一个泛型类型变量)
类型变量名称:T2
@com.javacode2018.lesson001.demo18.Ann7(value=T2是在方法上声明的泛型类型变量)
@Target(ElementType.TYPE_USE)
这个是1.8加上的,能用在任何类型名称上,来个案例感受一下:
@Target({ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@interface Ann10 {
String value();
}
@Ann10("用在了类上")
public class UserAnnotation10<@Ann10("用在了类变量类型V1上") V1, @Ann10("用在了类变量类型V2上") V2> {
private Map<@Ann10("用在了泛型类型上") String, Integer> map;
public <@Ann10("用在了参数上") T> String m1(String name) {
return null;
}
}
类后面的V1、V2都是类型名称,Map后面的尖括号也是类型名称,m1方法前面也定义了一个类型变量,名称为T
💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖
热门专栏推荐
🌈🌈计算机科学入门系列 关注走一波💕💕
🌈🌈CSAPP深入理解计算机原理 关注走一波💕💕
🌈🌈微服务项目之黑马头条 关注走一波💕💕
🌈🌈redis深度项目之黑马点评 关注走一波💕💕
🌈🌈JAVA面试八股文系列专栏 关注走一波💕💕
🌈🌈JAVA基础试题集精讲 关注走一波💕💕
🌈🌈代码随想录精讲200题 关注走一波💕💕
总栏
🌈🌈JAVA基础要夯牢 关注走一波💕💕
🌈🌈JAVA后端技术栈 关注走一波💕💕
🌈🌈JAVA面试八股文 关注走一波💕💕
🌈🌈JAVA项目(含源码深度剖析) 关注走一波💕💕
🌈🌈计算机四件套 关注走一波💕💕
🌈🌈数据结构与算法 关注走一波💕💕
🌈🌈必知必会工具集 关注走一波💕💕
🌈🌈书籍网课笔记汇总 关注走一波💕💕
📣非常感谢你阅读到这里,如果这篇文章对你有帮助,希望能留下你的点赞👍 关注❤收藏✅ 评论💬,大佬三连必回哦!thanks!!!
📚愿大家都能学有所得,功不唐捐!