注解和反射
注解
1.0 什么是注解
注解顾名思义为注释、讲解,可以理解为一种标签或标记,可以用在类、方法或者域上。向类、方法等添加注解,相当于给他们贴上了一层标签。注解可以被其他程序读取。
注解格式:
以“@注释名”,可添加一些参数值。
1.2 内置注解
内置注解就是我们的jdk所带的一些注解。常用的三个注解:
-
@Override
这个应该都不陌生,修辞方法,表示打算重写超类中的方法声明。 -
@Deprecated
这个注解我们应该也不会陌生,我们可能看不到这个注解,但是我们肯定在使用一些方法时会出现横线。表示废弃,这个注释可以修辞方法,属性,类,表示不鼓励程序员使用这样的元素,通常是因为他很危险或有更好的选择。
-
@SuperWarnings
这个注解主要是用来抑制警告信息的,我们在写程序时,可能会报很多黄线的警告,但是不影响运行,我们就可以用这个注解来抑制隐藏它。与前俩个注解不同的是我们必须给注解参数才能正确使用他。
参数 | 说明 |
---|---|
deprecation | 使用了过时的类或方法的警告 |
unchecked | 执行了未检查的转换时的警告 如:使用集合时未指定泛型 |
fallthrough | 当在switch语句使用时发生case穿透 |
path | 在类路径、源文件路径中有不存在路径的警告 |
serial | 当在序列化的类上缺少serialVersionUID定义时的警告 |
finally | 任何finally子句不能完成时的警告 |
all | 关于以上所有的警告 |
1.3 自定义注解
格式:
public @interface 注解名 { 定义体 }
- 使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
- 其中的每一个方法实际上是声明了一个配置参数
- 方法的名称就是参数的名称
- 返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)
- 可以通过default来声明参数的默认值
- 如果只有一个参数成员,一般参数名为value
- 我们在使用注解元素时必须要有值,可以定义默认值,空字符串,0或者-1
public @interface TestAnnotation {
//参数默认为空
String value() default "";
}
1.4 元注解
我们在自定义注解时,需要使用java提供的元注解,就是负责注解的其他注解。java定义了四个标准的meta-annotation类型,他们被用来提供对其他注解类型声明。
- @Target
这个注解的作用主要是用来描述注解的使用范围,说白了就是我们自己定义的注解可以使用在哪个地方。
所修饰范围 | 取值ElementType |
---|---|
package 包 | PACKAGE |
类、接口、枚举、Annotation类型 | TYPE |
类型成员(方法,构造方法,成员变量,枚举值) | CONSTRUCTOR:用于描述构造器。FIELD:用于描述域。METHOD:用于描述方法 |
方法参数和本地变量 | LOCAL_VARIABLE:用于描述局部变量。PARAMETER:用于描述参数 |
//自定义一个注解
//Target 表示我们的注解可以用在哪些地方,传入的参数参考以上表格
@Target(value ={ElementType.TYPE})
public @interface tYPE {
}
- @Retention
这个注解的作用就是我们需要告诉编译器我们需要在什么级别保存该注释信息,用于描述注解的生命周期。(表示我们的注解在什么地方还有效)
取值RetentionPolicy | 作用 |
---|---|
SOURCE | 在源文件中有效(即源文件保留) |
CLASS | 在class文件中有效(即class保留) |
RUNTIME | 在运行时有效(即运行时保留)注:为RUNTIME时可以被反射机制所读取 |
@Target(value ={ ElementType.TYPE})
//Retention表示我们的注解在哪些地方有效(具体参数值的作用参考上面的表格)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
/*定义体*/
}
下面这两个注解不常用
- @Document
表示是否将我们的注解生成在JAVAdoc中。 - @Inherited
子类可以继承父类的注解
实例代码:
public class Test {
//注解没有默认值,必须给参数
@MyAnnotation(name = "Hello")
public void test1() {
}
//注解有默认值,可以不用给参数
@MyAnnotation1
public void test2() {
}
}
//自定义注解
//表示我们的注解可以用在哪些地方
@Target(value = {ElementType.TYPE, ElementType.METHOD})
//表示我们的注解在哪些地方有效
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
//注解的参数:参数类型 + 参数名();
String name();
}
@Target(value = {ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation1 {
//注解的参数:参数类型 + 参数名();
//默认值为空
int age() default 0;
int id() default -1; //如果默认值为-1,代表不存在
String[] eum() default {"jack", "Tom"}; //数组类型的参数
}
反射
1.1 什么是反射?
反射指的是我们可以在运行期间加载、探知、使用编译期间完全未知的类。是一个动态的机制,允许我们通过字符串来指挥程序实例化,操作属性、调用方法。使得代码提高了灵活性,但是同时也带来了更多的资源开销。
加载完类之后,在堆内存中,就产生了一个 Class 类型的对象(一个 类只有一个 Class 对象),这个对象就包含了完整的类的结构信息。 我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过 这个镜子看到类的结构,所以,我们形象的称之为:反射。
优点:
- 可以实现动态创建对象和编译,体现出很大的灵活性。
缺点:
- 对性能有影响,使用反射基本是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于 直接执行相同的操作。
1.2 Class类
我们在使用反射时,需要先获得我们需要的类,而java.lang.Class这个类必不可少,他十分特殊,用来表示java中类型 (class/interface/enum/annotation/primitive type/void)本身。
- 一个类被加载后,类的整个结构都会被封装在Class对象中。一个类对应一个 Class对象。
- 当一个class被加载,或当加载器(class loader)的defineClass()被 JVM调用,JVM 便自动产生一个Class 对象。
哪些类型可以有Class类?
- class:外部类、成员(成员内部类,静态内部类)、局部内部内,匿名内部类。
- inteface:接口
- [ ] : 数组
- enum:枚举
- annotation:注解 @interface
- primitive type :基本数据类型
- void
public class Test {
public static void main(String[] args) {
Class c1 = Object.class; //类
Class c2 = Comparable.class; //接口
Class c3 = String[].class; //一维数组
Class c4 = int[][].class; //二维数组
Class c5 = Override.class; //注解
Class c6 = ElementType.class; //枚举
Class c7 = Integer.class; ///基本数据类型
Class c8 = void.class; //void
Class c9 = Class.class; //Class
}
}
一维数组和二维数组:只要元素类型维度是一样的,就是同一个class。(即c3 = c4)
1.3 我们应该如何获取Class类的对象?
有三种获取方式,Class.forname、getClass()、类名.class
其中forName方式最常用。
先创建一个普通的实体类(用于以下所有实例的实体类测试):
package com.sdablog.demo;
public class User {
//这里name用的是私有类型
private String name;
//这里age用的是公有类型
public int age;
//无参构造器
public User(){}
//有参构造器
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 通过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) 获取
若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能高。
public class TestReflect {
public static void main(String[] args) {
//通过导包获取类名点class来获取类对象
Class aClass = User.class;
}
}
1.4 反射的基本操作
1. 获取类名
* getName():获取一个包括包名的类名
* getSimpleName():获取一个不包括包名的类名
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
//获取User的Class对象
Class aClass = Class.forName("com.sdablog.demo.User");
//获取类名:包名 + 类名 输出:com.sdablog.demo.User
String name = aClass.getName();
//获取单纯的一个类名 输出:User
String name1 = aClass.getSimpleName();
}
}
2. 获取类的字段、某些变量
函数 | 功能 | 是否可获取父类 | 备注 |
---|---|---|---|
getFields() | 获取该类的所有public字段 | 是 | 返回一个字典! |
getField(“name”) | 根据字段(name)获取类的public字段 | 是 | 返回一个字段 |
getDeclaredFields() | 获取该类的所有字段(仅自定义) | 否 | 返回字典 |
getDeclaredField(“name”) | 根据字段名获取该类的字段 | 否 | 返回一个字典 |
Tip:带Decalared方法的不能获取到public的字段,并且不能获取到父类的字段。
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class<?> aClass = Class.forName("com.sdablog.lesson01.User");
//获取该类的所有public字段,包括父类的
Field[] fields = aClass.getFields();
//根据字段名获取该类的public字段(包括父类)
Field name = aClass.getField("school");
System.out.println(name);
//获取该类的所有字段,不包括父类(仅自定义)
Field[] fields1 = aClass.getDeclaredFields();
System.out.println(name);
//根据字段获取该类的字段,不包括父类
Field fields2 = aClass.getDeclaredField("name");
for (Field field:fields){
System.out.println(field);
}
}
}
3. 获取类的方法
- getMethods():获取该类的所有public方法,包括父类的
- getMethod(“method”,*String.class):根据方法名获取该类的public方法,包括父类的。如果有第二个参数,就是获取重写方法,可以在第二个参数加上重写方法的参数类型,不写为无参数的方法
- getDeclaredMethods():
- 获取该类的所有方法,不包括父类
- 根据方法名获取该类的方法,不包括父类
注:获取方法的方式与获取字段的方法一样,在这里我们需要注意的是重写的方法,一个类中存在俩个或多个方法名是一样的,因此在根据方法名获取方法时,提供第二个参数,为可变参数。参数类型为我们获取方法的参数类型的类,如果不写默认为无参方法。
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class<?> aClass = Class.forName("com.sdablog.lesson01.User");
//获取该类的所有public方法,包括父类的
Method[] methods = aClass.getMethods();
//根据方法名获取该类的public方法,包括父类的
Method methods1 = aClass.getMethod("getAge");
System.out.println(methods1);
//如果该类为重写方法,可以在第二个参数加上重写方法的参数类型,不写为无参数的方法
Method paramMethod = aClass.getMethod("setName", String.class);
System.out.println(paramMethod);
//获取该类的所有方法,不包括父类
Method[] declaredMethods = aClass.getDeclaredMethods();
//根据方法名获取该类的方法,不包括父类
Method declaredMethod = aClass.getDeclaredMethod("getAge");
System.out.println(declaredMethod);
for (Method method : declaredMethods) {
System.out.println(method);
}
}
}
4.获取类的构造器
- getConstructors():获取该类的所有构造器,包括父类
- getConstructor():根据构造器的参数类型来获取指定构造器,不写为无参构造器
- getDeclaredConstructors():获取该类的所有构造器,不包括父类
- getDeclaredConstructor():根据构造器的参数类型来获取指定的自定义构造器,不写为无参构造器
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class<?> aClass = Class.forName("com.sdablog.lesson01.User");
//获取该类的所有构造器,包括父类
Constructor[] constructors = aClass.getConstructors();
//获取无参构造
Constructor constructor1 = aClass.getConstructor();
//获取有参构造
Constructor constructor2 = aClass.getConstructor(String.class,int.class);
//获取该类的所有构造器,不包括父类
Constructor[] declaredConstructors = aClass.getDeclaredConstructors();
//根据构造器的参数类型来获取指定的自定义构造器,不写为无参构造器
Constructor declaredConstructor = aClass.getDeclaredConstructor();
Constructor declaredConstructor1 = aClass.getDeclaredConstructor(String.class, int.class);
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
}
}
注:获取构造器和方法时,可能不确定参数有几个,可以写成可变参数,在使用时可传多个参数。
5.反射创建对象(类的实例化)
使用 getDeclaredConstructor() 方法会根据他的参数对该类的构造函数进行搜索并返回对应的构造函数,没有参数就返回该类的无参构造函数,然后再通过newInstance()进行实例化。
- getDeclaredConstructor()方法不传参
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//获取Class对象
Class<?> aClass = Class.forName("com.sdablog.lesson01.User");
//构造一个对象(类的实例化)
User user = (User) aClass.getDeclaredConstructor().newInstance();
System.out.println(user);
}
}
输出结果:
User{name=‘null’, age=0}
- getDeclaredConstructor()方法传参
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//获取Class对象
Class<?> aClass = Class.forName("com.sdablog.lesson01.User");
//构造一个对象,传入参数)
User user = (User) aClass.getDeclaredConstructor(String.class,int.class).newInstance("小明",17);
System.out.println(user);
}
}
输出结果:
User{name=‘小明’, age=17}
使用getDeclaredConstructor()方法传参时,可以没有无参构造,但是必须写入参数值。
总结:
- 默认获取的是Object类型,因此类型需要转型
- getDeclaredConstructor() 没有参数时,默认调用的是无参构造,所以需要在类中(User类)定义无参构造,否则报错
- getDeclaredConstructor()有参数时,调用的是有参构造,可以不需要无参的构造,但必须使用newInstance()方法传入参数值
6反射调用方法和属性
- 通过放射调用方法
- 先实例化对象
- 再获取方法
- 用invoke激活方法
- 调用方法
Tip:invoke(对象,“方法值”) 激活的意思
//通过发射操作方法
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//获取Class对象
Class<?> aClass = Class.forName("com.sdablog.lesson01.User");
//创建一个对象(实例化对象)
User user = (User) aClass.getDeclaredConstructor().newInstance();
// 先通过反射获取一个方法
Method setName = aClass.getDeclaredMethod("setName", String.class);
//invoke:激活,invoke(对象,“方法值”)
setName.invoke(user,"小明");
//调用getName方法
System.out.println(user.getName()); //输出“小明”
}
}
注:如果我们调用的方法为私有方法,虽然编译器通过,在运行时会报错(java.lang.IllegalAccessException),这是因为java的安全检查。我们可以使用setAccessible(true)这个方法来跳过安全检查。
- 通过反射操作属性
- 先实例化对象
- 再获取字段
- 通过该字段的set方法来改变该字段的值。set(对象,“值”)
这是操作非私有属性:
//通过发射操作属性
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
//获取Class对象
Class<?> aClass = Class.forName("com.sdablog.lesson01.User");
//创建一个对象(实例化对象)
User user = (User) aClass.getDeclaredConstructor().newInstance();
//通过放射获取属性
Field name = aClass.getDeclaredField("age");
//通过该字段的set方法来改变该字段的值,该方法有俩个参数
name.set(user,10);
System.out.println(user.getAge());
}
}
这是操作私有属性:
//通过发射操作属性
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
//获取Class对象
Class<?> aClass = Class.forName("com.sdablog.lesson01.User");
//创建一个对象(实例化对象)
User user = (User) aClass.getDeclaredConstructor().newInstance();
//通过放射获取属性
Field name = aClass.getDeclaredField("name");
//使用setAccessible(true)这个方法来跳过安全检查
name.setAccessible(true);
//通过该字段的set方法来改变该字段的值,该方法有俩个参数
name.set(user,"小明");
System.out.println(user.getName());
}
}
总结:调用方法或者属性,都必须先用反射获取这个对象(相当于普遍的类实例化),然后在获得该属性或者方法,最后用这个实例化的对象去调用方法或者属性。无论是调用私有方法还是私有属性,都必须要用SetAccessible(true)方法来跳过安全检查。
反射操作注解
- getAnnotation
- getAnnotations
//通过发射操作属性
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("com.sdablog.lesson01.Student");
//通过反射获得注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获得注解value的值
Tabe annotation = (Tabe) c1.getAnnotation(Tabe.class);
System.out.println(annotation.value());
//获得类指定的注解
Field f = c1.getDeclaredField("name");
Fields a =(Fields) f.getAnnotation(Fields.class);
System.out.println(a.columenName());
System.out.println(a.type());
System.out.println(a.length());
}
}
注:如果我们需要获取类上的注解,只需要获取到类对象,然后.getDeclaredAnnotation()即可,其实不管是获取类上的注解还是字段上的注解都是一样的方法,关于有无Declared的方法,看到这里大家应该也有了解了。
————————————————
参考链接:https://blog.csdn.net/weixin_45056780/article/details/105127722