Java的反射机制
1、 什么是反射
- Reflection是Java语言的一个很重要的特征,它使得Java具有了“动态性”。
- JVM加载完成类以后,在堆内存的方法区中就产生了一个Class类型的对象(一个类中只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子我们可以看到类的结构,所以我们形象的称之为:反射
- 正常方式: 引入需要包的名称 ----- 通过new实体化 ----- 获得实例化对象
- 反射方式:实例化对象 --------------- getClass()方法 ----- 得到完整的的"包类"名称信息
2、 反射机制可以做什么
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法。
Reflection是Java被视为动态(或准动态)语言的一个关键性质,这个机制允许在程序运行时通过Reflection API取得任何一个已知名或未知名的Class的内部信息,包括fields和methods的所有信息,并可运行时改变fields或调用methods。
一般而言,在我们说到动态语言,大致认同的一个定义:“程序运行时,允许改变程序结构或变量类型,这种程序称之为动态语言”
尽管在这样的定义与分类下Java不是动态语言,但它却有着一个非常突出的动态相关的机制:Reflection(反射)。
利用这样的机制我们可以在程序运行期间,加载,探知,使用编译器完全未知的classes。换句话说,Java可以加载一个程序运行期间才得名class,获悉其完整结构,并生成其对象实体,或对其fields设值,或唤起methods,这种看透class的能力被称之为introspection。Reflection和introspection是常被提起的术语
3、Class对象
对象照镜子后可以得到的信息:某个类的属性,方法,和构造器,某个类到底实现了哪些接口,对于某个类而言,JRE都为其保留一个不变Class类型的对象。一个Class对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
- Class 本身也是一个类
- Class 对象只能有系统建立
- 一个加载的类在JVM中只能有一个Class实例
- 一个Class对象对应的是一个加载到JVM的一个.class文件
- 每个类的实例都会记得自己是由哪个Class实例所生成
- 通过Class可以完整的得到一个类中的所有被加载的结构
- Class类是Reflection的根源,针对任何你想动态加载,运行的类,唯有先获取相应的Class对象
获取Class的实例,有三种情况
- 若已知具体的类,通过类的class属性来获取,该方法最为安全可靠,程序性能最高.
//第一种方法:通过类获取
Class c1 = Student.class;
System.out.println(c1);
//class day01.Student
- 已知某个类的实例,调用该实例的getClass()方法获取Class对象
//第二种方法 :通过对象获取
Person student = new Student();
Class c2 = student.getClass();
System.out.println(c2);
//class day01.Student
- 已知一个类的全类名,且该类在类路径下,可以通过Class类的静态方法forName()获取,要抛出一个ClassNotFoundExcetion异常
//第三种方法 :通过完整包名+类名获取
Class c3 = Class.forName("day01.Student");
System.out.println(c3);
//class day01.Student
- 无论有几个对象,只要是一个类new出来的对象它的Class对象只有一个
//我们通过这三个Class对象的hashCode值来判断是否是一个
System.out.println(c1.hashCode());
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
356573597
356573597
356573597
//他们的结果是相同,可见他们的Class对象是一个
//如果大家对于这个有疑问或者感兴趣的话可以查看我的另一条博客《Java类加载机制》
那我们知道了怎么获取Class对象的方法,那么哪些数据类型才可以有Class对象呢
- class:外部类,成员(内部成员类,静态成员类),局部成员类,匿名内部类
Class c1 = Object.class;
//class java.lang.Object
- interface:接口
Class c2 = Comparable.class;
//interface java.lang.Comparable
- []:数组
//在这里分为一维数组和二维数组
//一维数组
Class c3 = int[].class;
//class [I
//二维数组
Class c4 = int[][].class;
//class [[I
- enum:枚举
Class c5 = ElementType.class;
//class java.lang.annotaion.ElementType
- annotation:注解@interface
Class c6 = Override.class;
//interface java.lang.Override
- primitive type:基本数据类型
Class c7 = Integer.class;
//class java.lang.Integer
- void
//这个数据类型就比较特殊了
Class c8 = void.class;
//void
那就有人问了那Class有没有呢
Class c9 = Class.class;
//class java.lang.Class
//也是有的因为Class对象本身也是个类
4、获取类运行时结构
//通过反射获取要操作类的Class对象
Class aClass = Class.forName("pojo.User");
我们来看User类结构
package pojo;
public class User {
private Integer id;
private String name;
public User() {
}
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//为了方便测试我们创建了一个公开的字段一个私有的方法和私有的构造器
public String age;
private void test(){}
private User(int i){};
}
- 获取字段
- 获取类的公开字段
//返回一个Field类型的数组 Field[] fields = aClass.getFields(); //public java.lang.String pojo.User.age 因为我们类只有一个公开的字段所以只获取了一个
- 获取类的全部字段
//返回一个Field类型的数组 Field[] declaredFields = aClass.getDeclaredFields(); //private java.lang.Integer pojo.User.id //private java.lang.String pojo.User.name //public java.lang.String pojo.User.age 可以看到获取到类的三个字段全部都能获取到包括私有字段
- 获取类指定的公开字段
//因为只有age字段是公开的所以我们获取这个字段 Field age = aClass.getField("age"); //public java.lang.String pojo.User.age 如果用这个方法获取私有字段会报错
- 获取类指定的字段
//因为不限制访问修饰符所以我们用私有字段做测试 Field id = aClass.getDeclaredField("id"); //private java.lang.Integer pojo.User.id
- 获取方法
- 获取类的公开方法及父类的公开方法
//返回一个Method数据类型的数组 Method[] methods = aClass.getMethods(); //public java.lang.String pojo.User.getName() //public java.lang.Integer pojo.User.getId() //public void pojo.User.setName(java.lang.String) //public void pojo.User.setId(java.lang.Integer) //public final void java.lang.Object.wait() throws java.lang.InterruptedException //public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException //public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException //public boolean java.lang.Object.equals(java.lang.Object) //public java.lang.String java.lang.Object.toString() //public native int java.lang.Object.hashCode() //public final native java.lang.Class java.lang.Object.getClass() //public final native void java.lang.Object.notify() //public final native void java.lang.Object.notifyAll() 可以看到获取的东西有很多,不光光有本类的公开方法父类的公开方法也会拿到
- 获取类的所有方法
//包括私有方法 Method[] declaredMethods = aClass.getDeclaredMethods(); //public java.lang.String pojo.User.getName() //public java.lang.Integer pojo.User.getId() //public void pojo.User.setName(java.lang.String) //private void pojo.User.test() //public void pojo.User.setId(java.lang.Integer) 可以看到我们的get/set方法以及我们测试用的test方法也已拿到,但有没有发现我们的构造器怎么没有呢,因为我们的构造器是一个特殊的方法,下面会有专门获取构造器的方法
- 获取类指定的公开方法
//该方法需要两个参数,参数的作用就是为了区分重载的方法 //1、要获取的方法名称 //2、要获取方法的参数,如果没有不用填 //这里个注意的点,第二个参数区分包装类,如果你的方法参数是包装类的话这里也是要包装类.class否则要报错 Method getName = aClass.getMethod("setName", String.class); //public void pojo.User.setName(java.lang.String)
- 获取类指定的方法
Method test = aClass.getDeclaredMethod("test"); //private void pojo.User.test()
- 获取构造器
- 获取类的公开构造器
//返回一个Constructor类型的数组 Constructor[] constructors = aClass.getConstructors(); //public pojo.User(java.lang.Integer,java.lang.String) //public pojo.User() 除了私有构造器都已经获取到了
- 获取类的所有构造器
Constructor[] declaredConstructors = aClass.getDeclaredConstructors(); //private pojo.User(int) //public pojo.User(java.lang.Integer,java.lang.String) //public pojo.User() 可以看到全部的构造器已经获取到包括私有的构造器
- 获取类指定的公开构造器
//这里也要注意一下也是区分包装类和基本数据类型的 Constructor constructor = aClass.getConstructor(Integer.class,String.class); //public pojo.User(java.lang.Integer,java.lang.String) 如果要获取的方法第一个参数类型不是Integer而是int的话这要更改成int.class
- 获取类指定的构造器
//同样也是要区分包装类型的 Constructor declaredConstructor = aClass.getDeclaredConstructor(int.class); //private pojo.User(int)
- 通过上面的方法我们可以判断只要方法中带有Declared无论是私有的字段还是公开的字段我们都可以获取到
5、通过反射来动态创建对象
还使用User类的Class对象来创建User类对象
- 构造无参对象
//1、通过Class对象的newInstance方法,本质上就调用了类的无参构造 User user = (User)aClass.newInstance(); System.out.println(user); //User{id=null, name='null'}
- 构造有参对象
//2、通过类构造器来创建对象,本质上调用了类的有参构造 Constructor constructor = aClass.getDeclaredConstructor(Integer.class, String.class); User user2 = (User) constructor.newInstance(1, "李垚"); System.out.println(user2); //User{id=1, name='李垚'}
6、通过反射来调用方法或给属性赋值
- 方法
//通过反射来调用普通方法 User user3 = (User) aClass.newInstance(); //1、反射获取要调用的方法 Method setName = aClass.getDeclaredMethod("setName", String.class); //2、通过invoke方法来激活执行方法 //第一个参数要执行的方法属于哪个对象,就把该对象传递进去,第二个参数就是要执行方法的参数 setName.invoke(user3, "李垚"); System.out.println(user3); //User{id=null, name='李垚', age='null'}
- 属性
//通过反射操作属性 User user4 = (User) aClass.newInstance(); Field name = aClass.getDeclaredField("name"); //在设值之前对于私有字段必须关闭掉权限检测,私有方法也可以用 //如果对一个方法或者字段频繁进行操作,建议到关闭权限检测,提升运行效率 name.setAccessible(true); //默认开启False;关闭为true name.set(user4, "李垚"); System.out.println(user4); //User{id=null, name='李垚', age='null'}
7、通过反射来获取类的注解
在这里我们自定义三个注解一个pojo类
- 类注解
//类名的注解 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyClassAnnotation { String value(); }
- 字段注解
//字段的注解 @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface MyFieldAnnotation { String columnName(); String type(); int length(); }
- 方法注解
//方法的注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyMethodAnnotation { String value(); }
- pojo类
@MyClassAnnotation("db_Student") public class Student { @MyFieldAnnotation(columnName = "db_id", type = "int", length = 10) private Integer id = 12; @SuppressWarnings("all") @MyFieldAnnotation(columnName = "db_name", type = "varchar", length = 50) private String name; @MyFieldAnnotation(columnName = "db_age", type = "int", length = 100) private Integer age; @MyMethodAnnotation(value = "123") public void test(){}; public Student() { } public Student(Integer id, String name, Integer age) { this.id = id; this.name = name; this.age = age; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
准备工作已经做好进入我们的测试类里
拿到pojo类的Class对象,后面全部用对象
Class aClass = Class.forName(“pojo.Student”);
- 获取类注解和指注解的参数
//1、获取类的所有注解 Annotation[] annotations = aClass.getAnnotations(); for (Annotation annotation : annotations) { System.out.println(annotation); //@annotation.MyClassAnnotation(value=db_Student) } //2、通过注解名称拿到注解参数,注意强制转换 MyClassAnnotation myClassAnnotation = (MyClassAnnotation) aClass.getAnnotation(MyClassAnnotation.class); System.out.println(myClassAnnotation.value()); //db_Student
- 获取指定字段的注解和参数
//1、获取指定字段 Field name = aClass.getDeclaredField("name"); //2、获取字段的注解 Annotation[] declaredAnnotations = name.getDeclaredAnnotations(); for (Annotation declaredAnnotation : declaredAnnotations) { System.out.println(declaredAnnotation); //@annotation.MyFieldAnnotation(columnName=db_name, type=varchar, length=50) } //3、通过直接名称获取注解参数,注意强制转换 MyFieldAnnotation myFieldAnnotation = (MyFieldAnnotation)name.getAnnotation(MyFieldAnnotation.class); System.out.println(myFieldAnnotation.columnName()); //name System.out.println(myFieldAnnotation.type()); //varchar System.out.println(myFieldAnnotation.length()); //50
- 获取指定方法的注解和参数
//1、获取指定方法的全部注解 Method toString = aClass.getDeclaredMethod("test"); Annotation[] declaredAnnotations1 = toString.getDeclaredAnnotations(); for (Annotation annotation : declaredAnnotations1) { System.out.println(annotation); //@annotation.MyMethodAnnotation(value=123) } //2、根据直接名称获取参数值 MyMethodAnnotation myMethodAnnotation = (MyMethodAnnotation) toString.getAnnotation(MyMethodAnnotation.class); System.out.println(myMethodAnnotation.value()); //123