什么是注解?
注解,根据字面意思,就是注释和解释,那么为什么说注解是一个注释和解释呢?
首先,注解根据它的生命周期可以分为,源码注解、编译时注解、运行时注解
源码注解:只存在于源码中,当源码进行编译以后,注解就不存在了
编译时注解:存在于源码和字节码文件中,当程序开始运行注解就不存在了
运行时注解:不仅存在于源码和字节码文件中,在程序运行时依然存在,并且可以影响程序的运行
通俗地说,注解就像是一个二维码,上面含有这个注解所带有的信息,当这个二维码在被扫描(注解在被读取)的时候,二维码(注解)上所携带的信息会被读取出来,这样我们可以在不改变原有代码和逻辑的情况下在源代码中补充信息。极大地增强了程序的可扩展性。
那么我们能够使用那些注解呢?
1、JDK提供的注解
JDK为我们提供了一些能够直接使用的注解
- @Override:用于标识方法,标识该方法属于重写父类的方法
- @Deprecated:用于标识方法或类,标识该类或方法已过时,建议不要使用
- @SuppressWarnings(“”):用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告
使用三个注解:
class A extends Time {
public A(int hour, int minute, int second) {
super(hour, minute, second);
}
@Override
@Deprecated
@SuppressWarnings("")
public int getDate() {
return super.getDate();
}
@Override
public String toString() {
return super.toString();
}
}
可以明显的看到在getDate()方法和toString()方法上面我们加了注解,那么我们再看一看在字节码文件中他们是如何存在的。
A.class:
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) fieldsfirst ansi space
// Source File Name: test.java
package zhuJie;
import java.sql.Time;
class A extends Time
{
public A(int hour, int minute, int second)
{
super(hour, minute, second);
}
/**
* @deprecated Method getDate is deprecated
*/
public int getDate()
{
return super.getDate();
}
public String toString()
{
return super.toString();
}
}
在字节码文件中我们可以明显看到@Override
和@SuppressWarnings("")
两个注解完全消失了,而@Deprecated
注解变成了一个注释,我们可以得到一个结论,@Override
、@Deprecated
、@SuppressWarnings("")
三个注解是源码注解,只存在于源码中。
2、自定义的注解
当我们发现JDK为我们提供的注解并不能满足我们的某些需求的时候,比如,想要让程序在运行的时候获得信息,这时候,我们就需要使用一个工具来制作符合我们需求的注解,这个工具叫做元注解。
元注解
元,在道家的经典中代表起始,也就是道,在这里也是,它代表的是注解的起源,注解的道。
元注解共有四个,他们的地位就像金木水火土一样,是作为根基存在的,使用这四个注解,我们可以创造任何我们想要的注解。
这四个注解分别是:
注解 | 作用 |
---|---|
@Target | 声明注解的作用域,该注解可以用在哪些内容上,可能的ElementType属性有: CONSTRUCTOR:用于构造器 FIELD:用于属性(包括enum实例) LOCAL_VARIABLE:用于局部变量 METHOD:用于方法 PACKAGE:用于包 PARAMETER:用于参数 TYPE:用于类、接口(包括注解类型)或enum |
@Retention | 声明注解的生命周期。可选的RetentionPolicy参数包括: SOURCE:(源码注释)注解将被编译器丢弃 CLASS:(编译时注解)注解在class文件中可用,但会被VM丢弃 RUNTIME:(运行时注解)VM将在运行期间保留注解,因此可以通过反射机制读取注解的信息。 |
@Document | 将注解包含在Javadoc中 |
@Inherited | 允许子类继承父类中的注解 |
使用元注解组装注解(自定义注解)
定义一个注解的基本方法:
class A{
@zhujieTest
int age;
@zhujieTest
public void see(){
System.out.println("hello world!");
}
}
----------------------------------------------------------
package zhuJie;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.FIELD,ElementType.METHOD}) // 注解可用于属性和方法
public @interface zhujieTest {
}
如何为注解添加想要让它携带的信息?当然是在声明注解的时候告诉它他都需要携带什么信息啦。但是注解能携带的信息种类比较少,它只能携带原始类型及String
,Class
,Annotation
,Enumeration
这些类型。
举个栗子:
@Target({ElementType.FIELD,ElementType.METHOD}) // 注解可用于属性和方法
@Retention(RetentionPolicy.RUNTIME)
public @interface zhujieTest {
int age();
String name();
// 可以为属性提供默认值
String school() default "河南南阳理工学院";
}
在声明注解的时候还应当注意这些问题:
- 注解的成员类型是受限制的,它只支持原始类型及
String
,Class
,Annotation
,Enumeration
这些类型。 - 如果注解只有一个成员,那么在声明的时候只能将成员声明为“value()”,但这时候给“value()”赋值的时候可以省略 “=”。
- 注解可以没有成员,此时的注解成为标识注解
- 注解成员的声明类似于声明接口的方法,后面需要加括号,而不是说加了括号我们声明的就是一个方法。
再举个栗子:
package zhuJie;
public class test {
public static void main(String[] args) {
}
}
class A{
@zhujieTest(age=12,name="xm")
int age;
@zhujieTest(age=13,name = "xm99")
public void see(){
System.out.println("hello world!");
}
}
---------------------------------------------------------------------
// 声明注解
package zhuJie;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD,ElementType.METHOD}) // 注解可用于属性和方法
@Retention(RetentionPolicy.RUNTIME)
public @interface zhujieTest {
int age();
String name();
// 可以为属性提供默认值
String school() default "河南南阳理工学院";
}
3、第三方注解
第三方注解就是已经有大佬帮我们封装好了,我们只需要按照他们指定的方式传入参数进行使用,就能够达到特定的效果了。
解析注解
我们现在已经可以将参数放在注解中,使用注解来携带参数了,那么我们有该如何将注解的属性的属性值,从注解当中取出来呢?
我们是如何从类中获取类的信息呢?通过一个名字,可以知道它的类型,属性,方法。对了,是反射!
那么我们可以参考一下反射来解决一下解析注解的问题。
- 首先,获得想要解析注解的那个类的对应的Class对象
Class.forName()
- 再获得类上面携带的注解信息
isAnnotationPresent(Class<? extends Annotation> annotationClass)
:如果指定类型的注释存在于此元素上,则返回 true,否则返回 false。
使用isAnnotationPresent()方法判断它是否拥有指定类型的注解,如果存在指定类型的注解,那么可以使用getAnnotation()方法获得对应的注解对象
- 获得注解对应的注解对象
getAnnotation(Class<A> annotationClass)
:如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。
解析类方法上面的注解思路与获得类上面的注解的思路相似。
解析所用的方法:
方法 | 作用 |
---|---|
getAnnotation(Class annotationClass) | 如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。 |
getAnnotations() | 返回此元素上存在的所有注释。返回值是 Annotation[] |
getDeclaredAnnotations() | 返回直接存在于此元素上的所有注释。 |
isAnnotation() | 如果此 Class 对象表示一个注释类型则返回 true。 |
isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果指定类型的注释存在于此元素上,则返回 true,否则返回 false。 |
举个栗子:
现有一个注解类,zhujieTest
----------------------------------------------------------------------
package zhuJie;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.TYPE}) // 注解可用于属性和方法
@Retention(RetentionPolicy.RUNTIME)
public @interface zhujieTest {
int age();
String name();
// 可以为属性提供默认值
String school() default "河南南阳理工学院";
}
有一个使用了这个注解的类 A
------------------------------------------------------------------------------
package zhuJie;
@zhujieTest(age=15,name = "hello")
public class A{
@zhujieTest(age=12,name="xm")
int age;
@zhujieTest(age=13,name = "xm99")
public void see(){
System.out.println("hello world!");
}
}
解析A类上面的注解
-----------------------------------------------------------------------------
package zhuJie;
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) {
// 1、使用类加载器加载类的信息
try {
Class cla = Class.forName("zhuJie.A");
// 2、查看类上面是否有这个注解
boolean b = cla.isAnnotationPresent(zhujieTest.class);
if(b){
//3、有这个注解的时候获得这个注解的对象,使用对象获得注解中的数据
zhujieTest z = (zhujieTest) cla.getAnnotation(zhujieTest.class);
System.out.println("class:"+z.school());
}
// 获得类方法上的注解
Method[] md = cla.getMethods();
for (Method me: md ) {
b = me.isAnnotationPresent(zhujieTest.class);
if(b){
zhujieTest zj = me.getAnnotation(zhujieTest.class);
System.out.println("Method:"+zj.school());
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}