今天来简单进阶一下注解,太深奥的没有场景咱也没法进阶
注解也是1.5以后出的,感觉1.5是个动荡的时期,出的东西还不少。
什么是注解
从JDK5开始,Java增加对元数据的支持,也就是注解,注解与注释是有一定区别的,可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息(此段话来自百度百科)。我个人而言,对于这段话还是很满意的,因为在我的意识里,用一个专业名词解释别的专业名词就是垃圾,相对于其他的解释,这个注解的解释还行,总结下来就是,在不同的时期对代码进行扩展,补充。首先看下日常遇到的一些注解
1.@Override,在重写方法的时候我们经常遇到
public class Father extends People {
@Override
public void eat() {
System.out.println("父亲爱吃红薯");
}
@Override
public void listen() {
System.out.println("父亲爱听舞女泪");
}
}
2.@Test,测试的时候遇到
@Test
public void method01()
{
Father father = new Father();
father.eat();
father.listen();
}
}
以上两个大概就是日常见到的最多的注解了
注解的分类
1.元注解
元注解就是给普通注解进行注解的注解,我们上一张图,有五个元注解,分别标注了它们的作用,最后一个等会细说,而我们自定义注解就需要用到这些元注解,基本都会用到的就是@Retention和@Target,其它三个视情况而言
简单的使用一下Repeatable这个元注解,这个理解起来有一点难度,可重复使用,就是说被这个元注解注释的注解可以重复在一个对象上使用,类,方法,字段可以用多次,我下面这张图就描述如何定义和使用
@inherited这个注解,我看了别人的帖子,都描述的是如果子类没有被其他注解注释过那么它就继承了父类的注解,那如果子类被其他注解注释过了,就不会继承了吗?我没有找到具体的答案,所以测试一下
环境准备,自定义两个注解
package test_2021_1_5.注解.Inherited;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Inherited
public @interface myAnnoation05 {
}
package test_2021_1_5.注解.Inherited;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface myAnnoation06 {
}
父类
package test_2021_1_5.注解.Inherited;
import java.lang.annotation.Annotation;
@myAnnoation05
public class A {
public static void main(String[] args) {
Class<A> aClass = A.class;
Annotation[] annotations = aClass.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
}
}
第一次测试,子类没有注解
package test_2021_1_5.注解.Inherited;
import java.lang.annotation.Annotation;
public class B extends A{
public static void main(String[] args) {
{
Class<B> aClass = B.class;
Annotation[] annotations = aClass.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
}
}
}
/**
结果
@test_2021_1_5.注解.Inherited.myAnnoation05()
**/
第二次测试,B本身有个注解,但是结果还是继承了A的,同样可以获取到(如有错误请指出,万分感谢),当然了,这个注解只对类这个对象有用,方法,接口什么的都没用
package test_2021_1_5.注解.Inherited;
import java.lang.annotation.Annotation;
@myAnnoation06
public class B extends A{
public static void main(String[] args) {
{
Class<B> aClass = B.class;
Annotation[] annotations = aClass.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
}
}
}
/**
* 结果
* @test_2021_1_5.注解.Inherited.myAnnoation05()
@test_2021_1_5.注解.Inherited.myAnnoation06()
**/
2.java内置注解
1.@Deprecated:这个元素是用来标记过时的元素,想必大家在日常开发中经常碰到。编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。
2.@Override: 提示子类要复写父类中被 @Override 修饰的方法
3.@SuppressWarnings: 阻止警告
4.@SafeVarargs:参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。它是在 Java 1.7 的版本中加入的。
5.@FunctionalInterface:函数式接口注解,这个是 Java 1.8 版本引入的新特性。函数式编程很火,所以 Java 8 也及时添加了这个特性。函数式接口 (Functional Interface) 就是一个具有一个方法的普通接口。
3.注解的组成
在自定义一个注解之前我们首先得分析一下一个注解都有几部分,我个人把它分为4部分,引入的注解,引入注解的参数,自身注解的声明,自身注解自定义的属性
第一部分:注解的名称没什么可说的对吧,你想引入什么就是@+“名字"
第二部分:注解的参数,我们看看图中的两个参数
第一个RetentionPolicy.RUNTIME,进入源代码发现是这样的一个枚举类,RUNTIME就是一个枚举变量,在介绍元注解的时候,我们介绍了@Retention这个元注解,而这个枚举类中的三个枚举类型的变量就是它的可选参数
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
第二个ElementType.METHOD,ElementType同样是个枚举类,METHOD是里面的一个枚举类型的变量,里面同样还有其参数,但都是此枚举类型的,当然我们得明白,这两个注解是元注解,它的参数类型,和可选择的参数,是java规定好的,而不是说参数只能是这两个枚举类中的枚举变量,如果我们自定义一个注解,那么参数的名字和值就是由我们自己控制,但是参数的类型也必须符合注解的要求
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
第三部分:注解的声明:也就是“@interface”,这部分具体说的是什么那,这句代码的声明代表什么,使用@interface声明注解时,就自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。我们测试下,这是自己定义的一个注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface myAnnoation {
}
反编译后是这样,它显示继承了Annotation这个接口,这个这看起来与枚举十分类似,也就是第三部分实质上就是Annotation这个接口,如果你在大括号中定义一些操作,我们也可以说他实现了Annotation这个接口,那么是不是可以说我们每使用了@interface这个声明就是实现了一次Annotation这个接口,至于这个接口被编译器如何实现在代码中我们暂时不用管
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: myAnnoation.java
package test_2021_1_5.6CE889E3;
import java.lang.annotation.Annotation;
public interface myAnnoation
extends Annotation
{
}
第四部分:大括号中定义的 东西,解决两个问题: 1.什么东西可以被定义 2.定义的格式是什么
首先在大括号中可以出现的东西有:8种基本数据类型,Class类型(类),String类型,枚举类型,注解类型,以及上述类型的一维数组
其次如何定义那,注解的属性应该叫做成员变量。注解只有成员变量,没有方法。但是在定义这个成员变量的时候又是像在定义一个无参的方法,也就是说虽然是在定义变量,但形式上像一个无参的方法,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。用@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值,我们还用着之前的例子来说明
4.自定义注解
我们该如何自定义注解那,我们首先看下常用的@Test定义方式
自己定义一个注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface myAnnoation {
String name() default "";
int value() default 0;
}
在使用注解之前还得明白,如何传递变量的值
5.参数如何传递
这在里我们把参数只分为两类,一类是普通的,一类是数组类型的,在定义注解属性的时候,可以设置默认值,但是默认值不能为null,必须是确定的值,如果定义了默认值就可传可不传,看你心情,其次这里有个重要的东西,就是如果注解中定义了名为value的变量,那么它的传参就可以分很多种情况,如果注解中只有一个value普通属性,那这个value就可以省略,如果这个属性是数组类型的value,那么数组中只有一个数据,value也可以省略
@MyAnnoation02
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MyAnnoation02 {
int value() ;
String[] s();
}
@myAnnoation03
public class Demo01 {
//
@MyAnnoation02(value = 5,s={"asd","sadasd"})
//@myAnnoation03()
//@myAnnoation03(value=5)
@myAnnoation03(5)
public void method01()
{
}
}
@myAnnoation04
@Retention(RetentionPolicy.RUNTIME)
@Target( ElementType.METHOD)
public @interface myAnnoation04 {
int[] value();
}
进行测试
public class Demo01 {
/**
* 当注解中有多个属性,赋值的时候以逗号隔开,数组的赋值得用大括号括起来
*/
@MyAnnoation02(value = 5,s={"asd","sadasd"})
/**
* 当注解中只有一个属性名字是value,或者说其它的属性有默认的值,就可以像下面这样赋值
*/
//@myAnnoation03(value=5)
@myAnnoation03(5)
/**
* 当变量的名字是value,并且value()是数组类型的
*/
//数组有多个数据
//@myAnnoation04(value = {1,2,3,4,8})
//数组只有一个数据,value可以省略
@myAnnoation04(1)
public void method01()
{
}
}
,参数讲完了,就该用了,然后使用一下,发现屁用没有!!!!!!!!!!!!!,,这就这么写上啥用没有,那这个注解到底该怎么用或者说它的 用处究竟是什么????
public class Father extends People {
@myAnnoation(20)
private int age;
private String name;
@Override
public void eat() {
System.out.println("父亲爱吃红薯");
}
@Override
public void listen() {
System.out.println("父亲爱听舞女泪");
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
}
学着使用注解
@Deprecated:这个元素是用来标记过时的元素,想必大家在日常开发中经常碰到。编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。
上面的这张图我表达了我对注解的看法,“ 不是注解有什么用,而时我们想用注解做什么”,我觉得这才是核心,我可以继续看下其他几个内置注解的定义,和写法
Runnable这个接口应该很熟悉,它是个函数式接口,@FunctionalInterface就标志着它是一个函数式的接口,这个接口里只有一个方法
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
我们看下这个@FunctionalInterface注解
由上面的两个内置注解,我们得到一个结论,那就是我们之前的那个自定义注解可以说是个半成品,我们只定义了它,但是没有说明它是做什么的,所以接下来我们要去说明它是做什么的
注解与反射
如果一个注解被注释在代码上,那么我们该如何获取那那个注解,并且进行操作
经过编译器编译之后,所有的注解都继承了继承了Annotation接口,Java在java.lang.reflect 反射包下新增了AnnotatedElement接口,Class、Method、Field、Constructor等实现了AnnotatedElement这个接口,也就是说我们可以通过反射获取来获取注解,以下是会用到的一些方法,Class、Method、Field、Constructor等都是实现了以下方法
测试下上面的方法
首先做准备
People类
public class People {
@MyAnnoation02
public void eat(){
System.out.println("我爱吃饭");
}
public void listen()
{
System.out.println("我爱听歌");
}
}
然后Father类
public class Father extends People {
@Override
public void eat() {
System.out.println("父亲爱吃红薯");
}
@Override
@Deprecated
@MyAnnoation02
@SuppressWarnings("all")
public void listen() {
System.out.println("父亲爱听舞女泪");
}
}
我们获取的时候也要分对象获取,比如说获取包上的,类上的,接口上的,方法上的,变量上的,首先要选择获取哪个对象上的 注解,method02是获取Father这个类上的注解,但是类上没有定义,于是获取到的注解的数组长度为0,
@Test
public void method02() throws ClassNotFoundException {
Class<?> aClass = Class.forName("test_2021_1_5.注解.Father");
Annotation[] annotations = aClass.getAnnotations();
if (annotations.length>0)
{
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
}
else {
System.out.println("类上没有注解");
}
}
method03获取Father类里的listen方法中的注解,并且判断它是否存在MyAnnoation02这个注解,按照我们想的这里应该获取到4个注解,并且MyAnnoation02是存在的,但是实际上只获取到了两个注解,@Override和@SuppressWarnings没有获取到,我不是很明白,我也没有找到答案
@Test
public void method03() throws ClassNotFoundException, NoSuchMethodException {
Class<?> aClass = Class.forName("test_2021_1_5.注解.Father");
Method listen = aClass.getDeclaredMethod("listen");
Annotation[] annotations = listen.getAnnotations();
if (annotations.length>0)
{
System.out.println(annotations.length);
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
}
boolean annotationPresent = listen.isAnnotationPresent(MyAnnoation02.class);
if (annotationPresent)
{
System.out.println("存在MyAnnoation02.class注解");
}
else
System.out.println("不存在");
}
获取注解这一步是不是就做完了,下来就到最终目的,我们用注解做些什么
模仿单元测试
最简单的,我们来模拟一下单元测试
定义自己的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
定义一个类
package test_2021_1_5.注解;
public class life {
@myTest
public void eat()
{
System.out.println("吃");
}
@myTest
public void drink()
{
System.out.println("喝");
}
public void playame()
{
System.out.println("玩");
}
public void happy()
{
System.out.println("乐");
}
}
进行测试,在这里我给吃喝两个方法加了注解,执行的结果也是这两个被注解的方法运行了,被这个myTest注解了的方法进行的操作就是启动它们,也就是说我们用自定义的这个注解去运行一些方法,这就是这个注解的作用
package test_2021_1_5.注解;
import java.lang.reflect.Method;
public class myA {
public static void main(String[] args) throws Exception {
Class aClass = Class.forName("test_2021_1_5.注解.life");
Method[] methods = aClass.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(myTest.class))
{
method.invoke(aClass.newInstance());
}
}
}
}
所以结论是,不是说注解有什么用,而是我们打算用注解做什么