什么是反射?
Java 属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有加载到 JVM。通过反射可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁
Java 反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性或方法,本质是 JVM 得到 class 对象之后,从而获取对象的各种信息
为什么需要反射?
在编译阶段不知道哪个类名,要在运行期从配置文件读取类名,这时候就没有硬编码
Java 程序中的对象在运行时可以表现为两种类型,即编译时类型和运行时类型。
例如 Person p = new Student();
,这行代码将会生成一个 p 变量,该变量的编译时类型为 Person,运行时类型为 Student。有时程序在运行时接收到外部传入的一个对象,该对象的编译时类型是 Object,但程序又需要调用该对象的运行时类型的方法,这就要求程序需要在运行时发现对象和类的真实信息
假设在编译时和运行时都完全知道类型的具体信息,在这种情况下,可以先使用 instanceof 运算符进行判断,再利用强制类型转换将其转换成其运行时类型的变量即可。
编译时根本无法预知该对象和类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射。
使用场景
不能明确接口调用哪个函数,需要根据传入的参数在运行时决定
不能明确传入函数的参数类型,需要在运行时处理任意对象
用 JDBC 连接数据库时使用 Class.forName()通过反射加载数据库的驱动程序
项目底层有时用 MySQL,有时用 Oracle,需要动态地根据实际情况加载驱动类,假设 com.java.dbtest.mysqlConnection
,com.java.dbtest.oracleConnection
要用这两个类,这时候程序就写得比较动态化,通过 Class tc = Class.forName("com.java.dbtest.TestConnection");
类的全类名让 JVM 在服务器中找到并加载这个类,而如果是 Oracle,则传入的参数就变成另一个了
Spring 框架也用到反射机制
Spring 通过 XML 配置装载 Bean 的过程:
将程序内所有 XML 或 Properties 配置文件加载入内存中
Java 类里面解析 XML 或 Properties 里面的内容,得到对应实体类的字节码字符串以及相关的属性信息
使用反射机制,根据这个字符串获得某个类的 Class 实例
动态配置实例的属性
类加载器
ClassLoader:负责加载类的对象
Java 运行时具有以下内置类加载器:
Bootstrap class loader:虚拟机的内置类加载器,通常表示为 null,并且没有父加载器
Platform class loader:平台类加载器可以看到所有平台类,平台类包括由平台类加载器或其祖先定义的 Java SE 平台 API,其实现类和 JDK 特定的运行时类
System class loader:应用程序类加载器,系统类加载器通常用于定义应用程序类路径,模块路径和 JDK 特定工具上的类
类加载器的继承关系:System 的父加载器为 Platform,而 Platform 的父加载器为 Bootstrap
1 2 3 4 5 6 7 8 9 10 11 12 public static void main (String[] args) { ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader); ClassLoader parent = systemClassLoader.getParent(); System.out.println(parent); ClassLoader parent1 = parent.getParent(); System.out.println(parent1); }
使用案例
获取 Class 对象的方式
Class.forName(“全类名”):将字节码文件加载进内存,返回 class 对象
类型.class:通过类名的属性 class 获取
对象.getClass():getClass()方法在 Object 类中定义
类型.TYPE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Data class Person { public String name; } class Student extends Person { public Student () { this .name="学生" ; } } class Teacher extends Person { public Teacher () { this .name="老师" ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.reflect;public class Test02 { public static void main (String[] args) throws ClassNotFoundException { Person person = new Student (); Class aClass = person.getClass(); System.out.println(aClass); Class aClass1 = Class.forName("com.reflect.Student" ); System.out.println(aClass1); Class aClass2 = Student.class; System.out.println(aClass2); System.out.println(aClass == aClass1); System.out.println(aClass == aClass2); Class type = Integer.TYPE; System.out.println(type); Class integerClass = Integer.class; System.out.println(integerClass); } }
结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的 Class 对象都是同一个
1 2 3 4 5 int [] a = new int [10 ];int [] b = new int [100 ];System.out.println(a.getClass().hashCode()); System.out.println(b.getClass().hashCode());
获取成员变量
Field:成员变量
Field [] getFields():获取所有 public 修饰的成员变量
Field getField(String name):获取指定名称的 public 修饰的成员变量
Field [] getDeclaredFields():获取所有的成员变量,不考虑修饰符
Field getDeclaredField(String name):获取指定名称的成员变量,不考虑修饰符
void set(Object obj, Object value):设置值
get(Object obj):获取值
setAccessible(true):暴力反射(忽略访问权限修饰符的安全检查)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Data public class Person { private String name; private int age; public int a; public String b; public Person () { } public Person (String name, int age) { this .name = name; this .age = age; } public void eat () { System.out.println("吃" ); } public void eat (String s) { System.out.println("吃" +s); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public static void main (String[] args) throws NoSuchFieldException, IllegalAccessException { Class personClass = Person.class; Field[] fields = personClass.getFields(); for (Field field:fields){ System.out.println(field); } System.out.println("-------------" ); Field a = personClass.getField("a" ); System.out.println(a); Person person = new Person (); Object o = a.get(person); System.out.println(o); System.out.println("-------------" ); a.set(person,12 ); System.out.println(person); System.out.println("-------------" ); Field[] declaredFields = personClass.getDeclaredFields(); for (Field field:declaredFields){ System.out.println(field); } System.out.println("-------------" ); Field a1 = personClass.getDeclaredField("a" ); a1.setAccessible(true ); Object o1 = a1.get(person); System.out.println(o1); }
获取构造方法
Constructor:构造方法
Constructor <?> [] getConstructors()
Constructor < T > getConstructor(类 <?>… parameterTypes)
Constructor < T > getDeclaredConstructor(类 <?>… parameterTypes)
Constructor <?> [] getDeclaredConstructors()
创建对象:T newInstance(Object… initargs)
如果使用空参数构造方法创建对象,操作可以简化:Class 对象.newInstance()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static void main (String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Class personClass = Person.class; Constructor constructor = personClass.getConstructor(String.class, int .class); System.out.println(constructor); Object dad = constructor.newInstance("dad" , 12 ); System.out.println(dad); System.out.println("---------------" ); Constructor constructor1 = personClass.getConstructor(); System.out.println(constructor1); Object dad1 = constructor1.newInstance(); System.out.println(dad1); System.out.println("----------------" ); Object o = personClass.newInstance(); System.out.println(o); }
获取成员方法
Method:方法对象
Method [] getMethods():获取所有 public 修饰的方法
Method getMethod(String name, 类 <?>… parameterTypes)
Method [] getDeclaredMethods()
Method getDeclaredMethod(String name, 类 <?>… parameterTypes)
执行方法:Object invoke(Object obj,Object… args)
获取方法名称:String getName()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public static void main (String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class personClass = Person.class; Method method = personClass.getMethod("eat" ); System.out.println(method); Person person=new Person (); method.invoke(person); Method eat = personClass.getMethod("eat" , String.class); eat.invoke(person,"饭" ); System.out.println("------------------" ); Method[] methods = personClass.getMethods(); for (Method method1:methods){ System.out.println(method1); String name = method1.getName(); System.out.println(name); } System.out.println("------------------" ); String name = personClass.getName(); System.out.println(name); }
获取类名
String getName():获取包名+类名
String getSimpleName():获取类名
1 2 3 4 5 6 7 8 9 10 11 package com.reflect;public class Test06 { public static void main (String[] args) throws ClassNotFoundException { Class aClass = Class.forName("com.reflect.Person" ); System.out.println(aClass.getName()); System.out.println(aClass.getSimpleName()); } }
反射效率
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class Test07 { public static void test01 () { User user=new User (); long start = System.currentTimeMillis(); for (int i = 0 ; i < 100000000 ; i++) { user.getName(); } long end=System.currentTimeMillis(); System.out.println(end-start); } public static void test02 () throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { User user=new User (); Class aClass = user.getClass(); Method getName = aClass.getDeclaredMethod("getName" , null ); long start = System.currentTimeMillis(); for (int i = 0 ; i < 100000000 ; i++) { getName.invoke(user,null ); } long end=System.currentTimeMillis(); System.out.println(end-start); } public static void test03 () throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { User user=new User (); Class aClass = user.getClass(); Method getName = aClass.getDeclaredMethod("getName" , null ); getName.setAccessible(true ); long start = System.currentTimeMillis(); for (int i = 0 ; i < 100000000 ; i++) { getName.invoke(user,null ); } long end=System.currentTimeMillis(); System.out.println(end-start); } public static void main (String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { test01(); test02(); test03(); } }
注意:设置 setAccessible()能使加快速率
获取父类类型
1 2 3 4 5 Class aClass1 = Class.forName("com.reflect.Student" );System.out.println(aClass1); Class superclass = aClass1.getSuperclass();System.out.println(superclass);
注意:
.getClass().getResource(fileName):表示只会在当前调用类所在的同一路径下查找该 fileName 文件
.getClass().getClassLoader().getResource(fileName):表示只会在根目录下(/)查找该文件
fileName 如果前面加“/”,如“/fileName”,则表示绝对路径,取/目录下的该文件;
fileName 如果前面没有加“/”,如“fileName”,则表示相对路径,取与调用类同一路径下的该文件
如果路径中包含包名,getClass().getResource(“com/xxx/1.xml”); 包名的层级使用“/”隔开,而非“.”