注解
什么是注解
注解就是对程序做出解释,与我们在方法、类上的注释没什么区别,但是注解可以被其他程序所读取,进行信息处理,否则与注释没有太大区别.
内置注解
内置注解就是JDK所带的一些注解.常用的三个注解:
@Override
修辞方法,表示打算重写超类中的方法声明.
@Deprecated
表示废弃,这个注解可以修饰方法、属性、类表示不鼓励程序员使用这样的元素,通常是因为他们危险或有更好的选择.
@SuperWarnings
抑制警告信息,我们写程序的时候,可能会报很多黄线的警告,但是不影响允许,我们就可以用这个注解来抑制隐藏他们.
与前两个注解不同的是,我们必须给注解参数才可以使用它.
参数 | 说明 |
---|---|
deprecation | 使用了过时的类或方法的警告 |
unchecked | 执行了未检查的转换时的警告 如:使用集合时未指定泛型 |
fallthrough | 当在switch语句使用时发生case穿透 |
path | 在类路径、源文件路径中有不存在路径的警告 |
serial | 当在序列化的类上缺少serialVersionUID定义时的警告 |
finally | 任何finally子句不能完成时的警告 |
all | 关于以上所有的警告 |
自定义注解
public @interface 注解名 { 定义体 }
@interface MyAnnotation{
//注解的参数名称: 参数类型 + 参数名 ();
String name() default "";
int age() default 0;
int id() default -1;//如果为-1,代表不存在
String[] schools();
}
需要注意:
- 使用
@interface
自定义注解时,自动继承了java.lang.annotation.Annotation
接口 - 每个方法实际上是声明了一个配置参数
- 方法的名称就是参数的名称
- 返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)
- 可以通过
default
来声明参数的默认值 - 如果只有一个参数成员,一般参数名为
value
- 使用注解元素时必须要有值,可以定义默认值,空字符串,0或者-1
元注解
在自定义注解时,需要使用Java提供的元注解,就是复制注解的其他注解.Java定义了四个标准的meta-annotation类型,它们被用来提供对其他注解类型的声明.
@Target
用来描述注解的使用范围,就是注解可以被应用在哪个地方.
所修饰范围 | 取值ElementType |
---|---|
package包 | PACKAGE |
类、接口、枚举、Annotation类型 | TYPE |
类型成员(方法、构造方法、成员变量、枚举值) | CONSTRUCTOR:用于描述构造器。FIELD:用于描述域。METHOD:用于描述方法 |
方法参数和本地变量 | LOCAL_VARIABLE:用于描述局部变量。PARAMETER:用于描述参数 |
Retention
告诉编译器需要在什么级别保存该注解信息,用于描述注解的生命周期
取值RetentionPolicy | 作用 |
---|---|
SOURCE | 在源文件中有效(即源文件保留) |
CLASS | 在Class文件中有效(即Class保留) |
RUNTIME | 在运行时有效(即运行时保留)注:为RUNTIME时可以被反射机制所获取 |
一般情况下,我们使用RUNTIME即可.这样程序运行时我们也可以通过反射机制来读取到该注解.
- @Document
- @Inherited
反射
什么是反射
反射是Java迈向动态语言的一步,让Java成为了一个伪动态语言,反射机制允许程序在执行期间借助ReflecctionAPI取得类的任何信息,并能直接操作对象的内部属性及方法.
加载完类之后,方法区会存放类的信息,包含类的完整结构信息,我们可以通过它看到类的结构,所以我们称之为反射.
Class类
使用反射,离不开java.lang.Class
这个类.
如何获取Class
通过
Class.forName()
获取(最常用)public class TestReflect { public static void main(String[] args) { try { //获取User的Class对象,参数为需要获取类对象的全类名 Class aClass = Class.forName("sml.reflect.User"); //因为是动态编译,所有我们需要抛出类未找到的异常 } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
通过
getClass()
获取public class TestReflect { public static void main(String[] args) { //new一个user对象 User user = new User(); //通过user对象来获取User类对象 Class aClass = user.getClass(); } }
通过
.class
获取public class TestReflect { public static void main(String[] args) { //通过导包获取类名点class来获取类对象 Class aClass = User.class; } }
反射的基本操作
获取到Class
对象之后,可以获取类的信息,这里列举常用的:
获取类名
Class aClass = Class.forName("sml.reflect.User"); //获取全类名 String name = aClass.getName(); //获取简单的类名 String simpleName = aClass.getSimpleName();
获取类的字段、某些变量
Class aClass = Class.forName("sml.reflect.User"); //获取该类的所有public字段,包括父类的 Field[] fields = aClass.getFields(); //根据字段名获取该类的public字段 Field field = aClass.getField("age"); //获取该类的所有字段,不包括父类(仅自定义) Field[] fields1 = aClass.getDeclaredFields(); //根据字段名获取该类的字段 Field field1 = aClass.getDeclaredField("name");
注意:我们仔细看注释,不带Declared的方法职能获取到public字段,且包括父类的,带Declared的方法可以获取到所有的自定义的字段!
获取类的方法
Class aClass = Class.forName("sml.reflect.User"); //获取该类的所有public方法,包括父类的 Method[] methods = aClass.getMethods(); //根据方法名获取该类的public方法 Method method = aClass.getMethod("getName"); //如果该类为重写方法,可以在第二个参数加上重写方法的参数类型,不写为无参数的方法 Method paramMethod = aClass.getMethod("getName",String.class); //获取该类的所有方法,不包括父类(仅自定义) Method[] declaredMethods = aClass.getDeclaredMethods(); //根据方法名获取该类的方法 Method declaredMethod = aClass.getDeclaredMethod("getName");
注:获取方法的方式与获取字段的方法一样,在这里我们需要注意的是重写的方法,一个类中存在俩个或多个方法名是一样的,因此在根据方法名获取方法时,提供第二个参数,为可变参数。参数类型为我们获取方法的参数类型的类,如果不写默认为无参方法。
获取类的构造器
Class aClass = Class.forName("sml.reflect.User"); //获取该类的所有构造器,包括父类 Constructor[] constructors = aClass.getConstructors(); //根据构造器的参数类型来获取指定构造器,不写为无参构造器 Constructor constructor = aClass.getConstructor(); Constructor constructor1 = aClass.getConstructor(String.class,int.class); //获取该类的所有构造器,不包括父类 Constructor[] declaredConstructors = aClass.getDeclaredConstructors(); //根据构造器的参数类型来获取指定的自定义构造器,不写为无参构造器 Constructor declaredConstructor = aClass.getDeclaredConstructor(); Constructor declaredConstructor1 = aClass.getDeclaredConstructor(String.class, int.class);
注:在我们获取类构造器和类方法时涉及到可变参数的知识,大家可以自行百度一下,或者查阅官方文档,也不难,就是在我们不确定参数有几个时,就可以写成可变参数,我们在使用时可以传多个参数。注意我们写的参数都为类对象!
类的实例化
Class aClass = Class.forName("sml.reflect.User"); //通过class类直接实例化,使用的是User类的无参构造器 User user = (User) aClass.newInstance(); //获取构造器来进行实例化,这里获取有参构造器 Constructor declaredConstructor = aClass.getDeclaredConstructor(String.class, int.class); //根据构造器进行实例化 User user1 = (User) declaredConstructor.newInstance("sml",18);
注:我们在使用类对象直接实例化时,一定要确保需实例化的类中存在无参构造器,否则会报错。默认获取的是Object类型,因此最后需要进行下类型转化。
方法的调用
Class aClass = Class.forName("sml.reflect.User"); User user = (User) aClass.newInstance(); //获取setName方法 Method setName = aClass.getDeclaredMethod("setName", String.class); //通过获取的方法来调用(invoke),invoke方法有俩个参数 //第一个是调用底层方法的对象,也就是通过哪个对象来调用方法 //第二个为可变参数,是用于方法调用的参数 setName.invoke(user,"sml");
注:如果我们调用的方法为私有方法,虽然编译器通过,在运行时会报错的(
java.lang.IllegalAccessException
),这是因为java的安全检查。我们可以使用setAccessible(true)
这个方法来跳过安全检查。Class aClass = Class.forName("sml.reflect.User"); User user = (User) aClass.newInstance(); Method setName = aClass.getDeclaredMethod("setName", String.class); //若setName为私有方法,跳过安全检查 setName.setAccessible(true); setName.invoke(user,"sml");
字段的操作
Class aClass = Class.forName("sml.reflect.User"); User user = (User) aClass.newInstance(); //获取name字段 Field name = aClass.getDeclaredField("name"); //通过该字段的set方法来改变该字段的值,该字段有俩个参数 //第一个为应该修改其字段的参数 //第二个为被修改字段的新值 //如果是私有字段,同样需要添加setAccessible(true) //setName.setAccessible(true); name.set(user,"sml");
泛型的操作(Generic)
Java采用泛型擦除机制来引入泛型,也就是说java的泛型仅仅只是给编译器javac使用的,确保数据安全和免去强制类型转换的麻烦,一旦完成编译,这泛型等于没有.
为了通过反射操作这些类型以迎合实际开发需求,Java新增了
ParameterizedType
、GenericArrayType
、TypeVariable
和WildcardType
几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型,这四种类型实现了Type接口.类型 含义 ParameterizedType 参数化类型,带有类型参数的类型,即常说的泛型,如:List TypeVariable 类型变量,如参数化类型Map<E,Y>中的Y,K等类型变量,表示泛指任何类 GenericArrayType (泛型)数组类型,比如List [],T[]这种,注意,这不是一般数组,而是表示一种{元素类型是参数化类型或者类型变量的}数组类型 WildcardType 代表通配符表达式,或泛型表达式,比如[?],[? super T],[? extends T],虽然WildcardType是Type的一个子接口,但并不是Java类型中的一种 public class TestReflect { //测试方法,返回类型与参数都为泛型 public static Map<String,User> GenericityTest(List<User> list,User user){ return null; } public static void main(String[] args) { try { //先获取到该类 Class aClass = Class.forName("sml.reflect.TestReflect"); //获取到测试方法 Method genericityTest = aClass.getDeclaredMethod("GenericityTest", List.class,User.class); //获取到类型参数数组,就是获取方法所有的参数类型 Type[] genericParameterTypes = genericityTest.getGenericParameterTypes(); for (Type genericParameterType : genericParameterTypes) { //输出一下类型参数 System.out.println(genericParameterType); //我们在循环时判断该参数类型,若该参数属于参数化类型 if(genericParameterType instanceof ParameterizedType){ //若属于参数化类型,则获取类型对象的数组,表示此类型的实际类型参数 Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { //打印下实际类型参数 System.out.println(actualTypeArgument); } } } } catch (Exception e) { e.printStackTrace(); } } }
上面演示的是获取方法参数,下面为获取返回类型
//获取方法所有的参数类型 Type[] genericParameterTypes = genericityTest.getGenericParameterTypes(); //获取返回值的参数类型,返回值只有一个,所有不是数组 Type genericReturnType = genericityTest.getGenericReturnType();
注解的操作
public class AnnotationTest { @TestAnnotation("sml") public static void main(String[] args) { try { Class aClass = Class.forName("sml.annotation.AnnotationTest"); Method main = aClass.getDeclaredMethod("main",String[].class); //根据我们的main方法获取main方法上的注解 TestAnnotation declaredAnnotation = main.getDeclaredAnnotation(TestAnnotation.class); //获取到他的值 String value = declaredAnnotation.value(); System.out.println(value); } catch (Exception e) { e.printStackTrace(); } } }
如果我们需要获取类上的注解,只需要获取到类对象,然后.getDeclaredAnnotation()即可,其实不管是获取类上的注解还是字段上的注解都是一样的方法.
参考
文章1:https://blog.csdn.net/weixin_45056780/article/details/105127722
文章2:https://blog.csdn.net/zhangzhiyuan88/article/details/113404626