反射
1. 反射的概述(熟悉)
-
Java给我们提供了一套API,使用这套API我们可以在运行时动态的获取指定对象所属的类,创建运行时类的对象,调用指定的结构(属性、方法)等。
-
API:
java.lang.Class
:代表一个类- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造器
- ……
-
反射的优点和缺点
- 优点:
-
提高了Java程序的灵活性和扩展性,降低了耦合性,提高自适应能力
-
允许程序创建和控制任何类的对象,无需提前硬编码目标类
-
- 缺点:
- 反射的性能较低。
- 反射机制主要应用在对灵活性和扩展性要求很高的系统框架上
- 反射会模糊程序内部逻辑,可读性较差。
- 反射的性能较低。
- 优点:
-
创建对象并调用与反射创建对象并调用使用场景
-
从程序员开发者角度看:开发中主要完成业务代码,对相关的对象、方法的调用都是确定的,所以使用非反射的方式多一些
-
反射体现了动态性(可以在运行时动态的获取对象所属的类,多态的调用相关的方法),所以在设计框架的时候会大量使用反射。
意味着,如果需要学习框架源码,那么就需要学习反射
框架 = 注解 + 反射 + 设计模式
-
2. Class类:反射的源头
-
Class类的理解
-
针对于编写好的.java源文件进行编译(javac.exe),会生成一个或多个.class字节码文件。
然后使用java.exe命运对指定的.class文件进行解释运行(这个解释运行的过程中,我们需要将.class字节码文件加载(类的加载器)到内存中(存放在方法区))。
加载到内存中的.class文件对应的结构即为Class的一个实例
如:加载到内存中的Person类或String类,都作为Class的一个实例- Class classPerson = Person.class; //运行时类(类.class,运行时加载到内存)
- Class classString = String.class;
- Class classComparable = Comparable.class;
-
说明:运行时类在内存中(方法区)会缓存起来,在整个执行期间,只会加载一次。
-
同一个加载器可以加载一次,不同的加载器可以加载多次
-
-
获取Class实例的几种方式
-
1.调用运行时类的静态属性:class
-
2.调用运行时类的对象的getClass()
-
3.调用Class的静态方法forName(String className) 此种更好的体现动态性。
-
4.使用类的加载器的方式
-
代码演示:
//1.调用运行时类的静态属性:class Class classU1 = User.class; System.out.println("classU1 = " + classU1); //2.调用运行时类的对象的getClass() User u1 = new User(); Class classU2 = u1.getClass(); System.out.println("classU2 = " + classU2); System.out.println(classU1 == classU2); //true //3.调用Class的静态方法forName(String className) 此种更好的**体现动态性** String className = "com.atguigu02._class.User"; //全类名 Class classU3 = Class.forName(className); System.out.println("classU3 = " + classU3); System.out.println(classU3 == classU2); //true //4.使用类的加载器的方式 Class classU4 = ClassLoader.getSystemClassLoader().loadClass("com.atguigu02._class.User"); System.out.println("classU4 = " + classU4); System.out.println(classU1 == classU4); //true
- 运行结果
classU1 = class com.atguigu02._class.User classU2 = class com.atguigu02._class.User true classU3 = class com.atguigu02._class.User true classU4 = class com.atguigu02._class.User true
-
3. 反射的应用(理解)
通过反射调用指定的结构:指定的属性、方法、构造器
getXxx():获取到运行时类本身及其所有的父类中声明为public权限的属性/方法/构造器
getDeclaredXxx():获取当前运行时类中声明的所有属性/方法/构造器(还要允许访问setAccessible(true))
-
调用属性:
步骤:
①通过Class实例调用getDeclaredField(String fieldName),获取运行时类指定名的属性
②setAccessible(true):确保此属性名可以被访问
③通过Field类的实例调用get(Object obj)(获取属性) / set(Object obj,Object name)(设置属性) -
调用方法:
步骤:Class<Person> personClass = Person.class; //获取运行时类 Person person = personClass.newInstance(); //创建对象 // ①class实例调用getDeclaredMethod(String methodName,Class...args),获取该实例指定的方法 Method showNationMethod = personClass.getDeclaredMethod("showNation", String.class,int.class); // ②允许此方法访问 showNationMethod.setAccessible(true); // ③通过Method实例调用invoke(Object obj,Object...objs),即调用Method对应的方法。 // invoke()的返回值即为Method对应的方法的返回值,若为void,则返回null String china = (String) showNationMethod.invoke(person, "CHINA", 28); System.out.println("showNation = " + china);
-
调用构造器
步骤://①获取运行时类 Class personClass = Class.forName("com.atguigu03.reflectapply.data.Person"); //②创建运行时类的构造器:通过Class的实例调用getDeclaredConstructor(Class...agrs),获取指定参数类型的构造器。 Constructor personConstructor = personClass.getDeclaredConstructor(String.class, int.class); //③允许访问 personConstructor.setAccessible(true); //④使用构造器创建实例对象:通过Constructor实例调用newInstance(Object...objs),返回一个运行时类的实例 Person person = (Person) personConstructor.newInstance("ljl", 18); System.out.println("person = " + person);
4. 反射的动态性
public class ReflectTest {
//体会:静态性
public Person getInstance(){
return new Person();
}
//体会:反射的动态性
//举例1:根据传入的全类名去创建不同的类的实例
public <T> T getInstance(String className) throws Exception {
//获取大的Class实例
Class aClass = Class.forName(className);
//获取指定构造器
Constructor constructor = aClass.getDeclaredConstructor();
constructor.setAccessible(true);
//返回构造的实例对象
return (T) constructor.newInstance();
}
@Test
public void test1() throws Exception {
Person p1 = getInstance(); //不能改变
System.out.println("p1 = " + p1);
String className = "com.atguigu04.other.dynamic.Person";
Person p2 = getInstance(className); //随着className动态变化去加载实例
System.out.println("p2 = " + p2);
}
//体会:反射的动态性
//类似:todo:前后端分离:如login、register等,前端传入数据之后再创建实例,体现为动态性(调用的时候再创建对象),否则如果先创建实例,会造成大量内存泄露
//举例2:
public Object invoke(String className,String methodName) throws Exception {
//1.创建全类名对应的运行时类的对象
Class aClass = Class.forName(className);
Constructor constructor = aClass.getDeclaredConstructor();
constructor.setAccessible(true);
Object obj = constructor.newInstance();
//2.获取运行时类中指定的方法,并调用
Method method = aClass.getDeclaredMethod(methodName);
method.setAccessible(true);
return method.invoke(obj);
}
@Test
public void test2() throws Exception {
String className = "com.atguigu04.other.dynamic.Person";
String methodName = "show";
Person invoke = (Person) invoke(className, methodName);
System.out.println("invoke = " + invoke);
}
}