前言:
现在看官们对“java注释的作用和特点”大致比较关切,看官们都需要知道一些“java注释的作用和特点”的相关知识。那么小编同时在网上收集了一些关于“java注释的作用和特点””的相关知识,希望大家能喜欢,大家一起来了解一下吧!【前言】
日常中,各位看到的优秀代码,有着哪些特点呢?充分利用的工具类(lang3,lombok,Validation等等),完善的注解,统一的代码规范等等。还有的,就是Java语言的诸多高级特性(lambda,stream,io等)。Java语言中,有三个特性,是高级工程师不可或缺的:注解、反射、泛型如果代码中,存在这些东西,那么即使应用得还不够合理,也能够从侧面证明这位程序员的技术追求
什么是注解
注解(Annotations)是 Java5 开始提供的功能特性,注解的定义和接口有些相似,最直观感觉就是比接口时多一个 @ 符号。
//注解public @interface MyAnnotation { String value();}
通过 "@ + 注解名" 来使用注解
@MyAnnotation(value = "hahaha")public class AnnotationUse { //那么注解有什么用呢? 注解是用来为类、接口、方法和字段等提供元数据信息, //这些信息可以被编译器,开发工具和其他程序等识别,能够在编译和运行时访问元数据信息, //然后程序根据这些信息来做一些事情。//例如,编译器根据注解来提示错误和警告信息,文档工具可以通过读取注解生成文档,//程序运行时读取元数据(配置信息)来执行某些操作等。 //换个方式说,注解就是贴在类上的标签。//打个比方,比如我们去超市买面包,如果把自己看做是运行的程序或编译器,//把面包看做 Java 类,面包上的标签就相当于注解,标签上有生产日期,保质期,//通过这些标签直接从面包上获得了一些信息,通过读取这些信息我们可以做一些选择或行为,//买或者不买,或买几个。注解可以这样理解,就是标签。 //商品上有多个标签,不同用途。Java 注解也一样,也可以标注多个注解。}
注解功能:注解是一种“增强型”的注释。只不过相对于只能给人看的注释,注解可以给电脑(JVM,程序等)看。
注解原理:注解的底层是Annotation接口的继承者。只不过相对于日常使用的接口,注解需要使用@interface,但是编译的结果依旧是接口继承(如TestAnnotation extend Annotation)。
Annotation接口 底层是接口
//@Deprecated -- @Deprecated 所标注内容,不再被建议使用。//@Override -- @Override 只能标注方法,表示该方法覆盖父类中的方法。//@Documented -- @Documented 所标注内容,可以出现在javadoc中。//@Inherited -- @Inherited只能被用来标注“Annotation类型”,它所标注的Annotation具有继承性。//@Retention -- @Retention只能被用来标注“Annotation类型”,而且它被用来指定Annotation的RetentionPolicy属性。//@Target -- @Target只能被用来标注“Annotation类型”,而且它被用来指定Annotation的ElementType属性。//@SuppressWarnings -- @SuppressWarnings 所标注内容产生的警告,编译器会对这些警告保持静默。
【注】仔细看这个接口的注释。注释中明确提出,虽然注解的本质是接口。但是直接引用Annotation接口,是无法实现注解功能的。
定义注解
//通过 @interface 来定义注解,注解里包含方法声明,方法可称为注解元素或属性。public @interface MyAnnotation { int id(); String value(); // ... }
标记注解
//标记注解就是没有任何元素的注解类型,称之为标记注解。public @interface MyAnnotation {}
单元素注解
/*定义参数的格式为:格式:类型 参数名() [default 默认值]注:default可选,用于为当前参数定义默认值。如果不指定,则使用注解时必须为此参数赋值。使用注解传参时格式:@注解名(参数名1=参数值1[,参数名2=参数值2,....])如果注解@AutoRunMethod只有一个参数,且参数名为num时,那么使用时格式如下: @AutoRunMethod(num=1)=============重点=============如果注解中只有一个参数,参数名建议选取value,这样的好处是,使用时可以不指定参数名,如: @AutoRunMethod(1)如果指定了默认值,则可以不指定参数,例如:@AutoRunMethod() 此时注解中参数的使用default的默认值 */ //为注解定义一个int型的参数// int num() default 1;//一个参数时,参数名不建议选取value以外的名字。 int value() default 1;
注解的元素类型
注解中的元素的声明返回类型是有限制的,必须是下列类型之一:
八种基本类型StringClassenumannotation以上类型的一维数组
public @interface MyAnnotation { Class<?> clazz(); Class<? extends List> clazz2(); }// clazz2 元素值受泛型有界通配符约束,只能是 List 的子类@MyAnnotation(clazz = Object.class, clazz2 = ArrayList.class)public class AnnotationUse { // ...}
不能使用的元素
每个注解都默认继承 java.lang.annotation.Annotation 接口,自定义的注解不能覆盖Annotation 接口中的方法,否则会编译错误。
public @interface MyAnnotation { int hashCode(); // 错误的,不能覆盖 Annotation 接口中的方法 String toString();// 错误的}
注解循环引用
注解类型元素不能直接或间接地包含本身类型(不能循环引用),否则编译错误。
public @interface MyAnnotation2 { MyAnnotation myannotation();}public @interface MyAnnotation { MyAnnotation myannotation(); // 不能使用自身作为元素类型 MyAnnotation2 myannotation2(); // 也不能间接使用自身作为元素类型}
作用在代码的注解(功能注解 或 预置注解)是
@Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。@Deprecated - 标记过时方法。如果使用该方法,会报编译警告。@SuppressWarnings - 指示编译器去忽略注解中声明的警告。@SafeVarargs 1.7 版本 用于方法或构造函数,抑制关于不可具体化变量(vararg)类型的未检查警告。@FunctionalInterface 1.8 版本 用于指定接口为函数接口,函数接口只能有一个抽象方法,如果不符合函数式接口定义编译器将报错。函数接口可以使用 lambda 表达式。
作用在其他注解的注解(元注解)是
@Rentention:表示目标注解的保持策略。其value为RetentionPolicy。如果目标注解没有使用该注解,则默认使用RetentionPolicy.CLASS@Target:表示目标注解的应用目标类型。其value为ElementType。如果目标注解没有使用该注解,则目标注解可以用于除了TYPE_PARAMETER和TYPE_USE以外的任何地方(这两个类型都是Java8新添加的)。@Documented:表示目标注解可以出现在JavaDoc中。@Repeatable:1.8版本 表示目标注解可以在同一位置,重复使用。@Inherited:表示目标注解可以随着所修饰的类的继承关系,被子类继承。
@Target (标注在哪些地方使用)
该元注解用来约束注解使用范围。@Target 注解只有一个 ElementType[] 类型的 value 属性。
@Retention (申明注解的生命周期)
用来指定注解的生命周期。使注解保留到源码、字节码还是运行时。@Retention 注解有一个 RetentionPolicy 类型的 value 属性。
@Documented (生成Java文档)
被 @Documented 修饰的注解会被 javadoc 工具记录到文档中,默认情况下 javadoc 是不会将注解生成到类的文档上。
@Inherited (允许继承)
通过 @Inherited 修饰的注解用在某个类上后,这个注解能被这个类的子类继承。
但接口的实现类不能继承接口上 @Inherited 修饰的注解,以及类的方法并不从它所重载的方法继承注解(如果是继承父类中的方法,方法上的注解不管是否用 @Inherited 修饰的,注解随着方法一起被继承下来的)。
如何自定义注解?
步骤:
[必选] 使用@interface来构建自定义注解。一般在创建自定义注解的同时,就达成了该要求。[可选] 使用@Target元注解。通过该注解,确定自定义注解的作用目标类型。注意:如果目标注解没有使用该注解,则目标注解可以用于除了TYPE_PARAMETER和TYPE_USE以外的任何地方(这两个类型都是Java8新添加的)。[可选] 使用@Retention元注解。通过该注解,明确自定义注解的生命周期,或者说自定义注解作用域。如果目标注解没有使用该注解,则默认使用RetentionPolicy.CLASS[可选] 添加成员变量。格式为“long value() default 1000L;”,与Java8的接口成员变量非常类似。注意:注解的成员变量只能采用无参方法表示。并且注解的成员变量,只能采用基本数据类型(char,boolean,byte、short、int、long、float、double)和String、Enum、Class、annotations数据类型,以及这一些类型的数组。[可选] 使用自定义注解。自定义注解的使用领域很多,主要分为两个方向:利用已有框架,不需要自己实现相关逻辑,自定义注解多作为标记注解。如配合SpringBoot的注解,形成自己的注解(相关的逻辑由SpringBoot自己处理)利用已有框架,需要自己实现部分逻辑(不涉及反射),但需要关联已有框架,并实现对应接口。如Validation框架的自定义校验注解,感兴趣的小伙伴,可以查看我之前写的Validation框架的应用。
eg:自动调用与Test4在同一个包中那些被@AutoRunClass标注的类中所有被@AutoRunMethod标注的方法(Demo)
public static void main(String[] args) throws Exception { File dir = new File( Test4.class.getResource(".").toURI() ); //通过当前类Test3的类对象获取所在的包名 String packageName = Test4.class.getPackage().getName(); //获取Test3.class文件所在的目录中所有.class文件 File[] subs = dir.listFiles(f->f.getName().endsWith(".class")); for(File sub : subs) { //获取字节码文件的文件名 String fileName = sub.getName(); String className = fileName.substring(0, fileName.indexOf(".")); //加载该类的类对象 Class cls = Class.forName(packageName + "." + className); if(cls.isAnnotationPresent(AutoRunClass.class)){ Object o = cls.newInstance(); //获取该类定义的所有方法 Method[] methods = cls.getDeclaredMethods(); for(Method method : methods){ if(method.isAnnotationPresent(AutoRunMethod.class)){ AutoRunMethod arm = method.getAnnotation(AutoRunMethod.class); int value = arm.value(); System.out.println("自动调用"+className+"类的方法:"+method.getName()+"()"+value+"次"); for(int i=0;i<value;i++) { method.invoke(o); } } } } } }
处理请求:解析路径(Demo)
/* 1扫描controller包下所有的类,利用反射机制加载,并判断是否被@Controller标注了 2如果被@Controller标注了,则获取该类中所有本类中定义的方法,并判断是否被 @RequestMapping标注了 3如果被@RequestMapping标注了,则获取该方法上这个注解中指定的参数value,这个参数 表示的就是该方法处理的是哪个请求。因此得到该参数后判断是否为path的值,如果是,则 说明该方法就是处理本次请求的业务方法了,从而调用该方法即可 如果扫描了所有的Controller以及里面所有的业务方法都没有与path匹配的,则说明本次 请求不是处理业务,则执行下面原有的响应文件或404的操作 */try { File dir = new File( DispatcherServlet.class.getClassLoader().getResource( "./com/webserver/controller" ).toURI() ); File[] subs = dir.listFiles(f -> f.getName().endsWith(".class")); for (File sub : subs) { String fileName = sub.getName(); String className = fileName.substring(0, fileName.indexOf(".")); Class cls = Class.forName("com.webserver.controller." + className); //判断该类是否被@Controller标注了 if (cls.isAnnotationPresent(Controller.class)) { Method[] methods = cls.getDeclaredMethods(); for (Method method : methods) { //判断该方法是否被@RequestMapping标注了 if (method.isAnnotationPresent(RequestMapping.class)) { //获取该注解 RequestMapping rm = method.getAnnotation(RequestMapping.class); //获取该注解的参数(该方法处理的请求路径) String value = rm.value(); if (path.equals(value)) {//判断当前请求是否为该方法处理的请求 //实例化该Controller Object o = cls.newInstance(); //执行该方法 method.invoke(o, request, response); return; } } } } }
【注】--重点强调
注解就是增强型的注释(可被计算机识别的注释),本质是接口。把握住这两点,就非常好理解注解与它的各种规则,行为。注解本身并没有任何功能(因为它只是注释,本质也只是接口),需要其他代码支撑,它才能体现价值。
标签: #java注释的作用和特点