反射-设计框架的灵魂
被反射的类必须显示创建无参构造函数才能被反射出该构造器;基本数据类型与包装类型不一样!
- 框架:半成平软件,在框架的基础上进行软件开发,简化代码
-
反射:将类的各个组成部分封装成其他对象,这就是反射机制
- 反射的好处:
- 可以在程序员运行过程中,提高程序的可扩展性
- 可以解耦,提高程序的可扩展性
- 反射的好处:

反射.png
类加载器,一个加载类的描述方式,将被加载的类当做对象,那么加载类的描述方式就是对应的类。和python中的元类是一个概念。
使用反射,就是站在JVM的角度去操控类!
一、获取Class对象的方式
-
Class.forName("全类名<包名+类名>"):【.class源代码阶段】 将字节码文件加载进内存,返回Class对象- 多用于配置文件,将类名定义在配置文件中,读取文件,加载类。
-
类名.class:【方法区阶段】例如Student.class,通过类名的属性class获取- 多用于参数的传递
-
对象.getClass(): 【堆内存阶段】getCalss()方法在Object类中定义。- 多用于对象的获取字节码的方式。
package reflexLearn;
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
// .class : Class.forName("全类名")
Class s1 = Class.forName("reflexLearn.Student");
// 方法区 : 类名.class
Class s2 = Student.class;
// 堆内存 : 对象名.getClass()
Student student = new Student();
Class s3 = student.getClass();
// 类加载器【类加载机制】只允许加一次相同的.class文件
boolean con = s1 == s2 && s1 == s3;
// true
}
}
/*学生类*/
class Student{
private String name;
private int age;
public Student(){}
private Student(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; }
public String getS(String s){ return s; }
private int getI(int n){ return n; }
@Override
public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; }
}
同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪种方式获取的Class对象都是同一个。
二、Class对象功能。
以上面的代码为例进行操作
1.获取成员变量们【Field】成员
- 获取功能:
-
Field[] getFields():获取所有public修饰的成员变量. -
Field getField(String name): 获取指定名称的public修饰的成员变量 -
Field[] getDeclaredFields(): 获取所有的成员变量,不考虑修饰符。 -
Field getDeclaredFields(String name)指定名称的成员变量,不考虑修饰符
-
- 获取Field成员变量的操作:
-
void set(Object obj, Object value):设置对应对象的值 -
get(Object obj): 获得对应对象的值 -
setAccessible(true):暴力反射,忽略访问权限修饰符的安全检查
-
/**@获取 Student的class对象*/
Class studentClass = Student.class;
//-----------------------//
// 1. 获取所有public修饰的成员变量
Field[] fields = studentClass.getFields();
// 2. 获取指定的被public修饰的成员变量
Field name = studentClass.getField("name");
// 3.获取成员变量 name 的值
Student stu1 = new Student("小红",22);
System.out.println(name.get(stu1));
// 4.设置成员变量的值
name.set(stu1,"唐小红");
System.out.println(stu1);
//----------------------//
// 5. 获取所有的成员变量,不考虑修饰符
Field[] declaredFields = studentClass.getDeclaredFields();
// 6. 获取指定成员变量,不考虑修饰符
Field age = studentClass.getDeclaredField("age");
// 忽略访问权限修饰符的安全检查,暴力反射,否则可能会抛错
age.setAccessible(true);
// 设置成员
age.set(stu1, 18);
// 获取成员
System.out.println(age.get(stu1));
2.获取构造方法们【Constructor】
- 获取功能
-
Constructor<?>[] getConstructors(): 获取public修饰的所有构造方法对象数组。 -
Constructor<T> getConstructor(类<?>...parameterTypes)获取public修饰的指定方法签名的构造方法对象 -
Constructor<?>[] getDeclaredConstructors(): 获取所有构造方法,忽略权限修饰符 -
Constructor<T> getDeclaredConstructor(类<?>...parameterTypes): 获取指定方法签名的构造方法对象,忽略权限修饰符
-
- Constructor构造方法对象的创建对象
-
T newInstance(Object...initargs)创建此对象所表示一个类的新的实例。 - 如果使用空参构造方法创建对象,可以简化操作:Class对象的newInstance方法
-
setAccessible(true):暴力反射
-
/**获取Student的Class对象*/
Class studentClass = Student.class;
// 1. 获取**public**修饰的所有构造方法对象数组
Constructor[] con1 = studentClass.getConstructors();
// 2. **获取**public**修饰的指定方法签名的构造方法对象
Constructor con2 = studentClass.getConstructor(String.class, int.class);
// 3. 获取所有构造方法,忽略权限修饰符
Constructor[] con3 = studentClass.getDeclaredConstructors();
// 4. 获取指定方法签名的构造方法对象,忽略权限修饰符
Constructor con4 = studentClass.getDeclaredConstructor();
// 5. 创建构造对象所表示一个类的新的实例
Object obj1 = con2.newInstance("小红", 22);
System.out.println(obj1);
// 6. 暴力反射,然后创建对象
con4.setAccessible(true);
Object obj2 = con4.newInstance();
System.out.println(obj2);
3.获取成员方法们
- 获取功能
-
Method[] getMethods(): 获取public修饰的某些Method对象的数组,(该类或接口声明的以及从 超类和超接口继承的那些的类或接口)的公共member方法。 -
Method getMethod(String name, Class<?>...parameterTypes):返回一个publicMethod对象,它反映此Class对象所表示的类或接口的指定公共成员方法。 -
Method[] getDeclaredMethods():返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法(公共、保护、默认(包)访问和私有方法),但不包括继承的方法。 -
Method getDeclaredMethod(String name, Class<?>...parameterTypes):返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
-
- Method方法对象的使用<需要对应的实例>:
-
Object invoke(Object obj, Object...args):指定实例obj,传参并执行方法。 -
String getName(): 获取方法名。 -
setAccessible(true):暴力反射
-
/**获取类对象*/
Class studentClass = Student.class;
// 1. 获取public修饰的所有方法对象列表【public 包含继承的】
Method[] meLis1 = studentClass.getMethods();
// 2. 获取public修饰的指定方法签名的方法对象
Method me1 = studentClass.getMethod("getS", String.class);
Student stu1 = (Student) studentClass.getConstructor(String.class, int.class)
.newInstance("杰克", 18);
System.out.println(me1.invoke(stu1, "这是一个公共方法!"));
// 3. 获取该对象表示的类的所有类型的方法对象,但是不包括继承的!
Method[] meList2 = studentClass.getDeclaredMethods();
// 4. 获取指定方法签名的方法对象
Method me2 = studentClass.getDeclaredMethod("getI",int.class);
// 5.暴力反射
me2.setAccessible(true);
// 6.执行方法
me2.invoke(stu1, 666);
// 7.获得方法名
me2.getName();
4.获取类名
-
String getName():获取全类名
studentClass.getName()
三、综合案例
需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法【简单的,无参的】
实现:配置文件、反射
配置文件
className=reflexLearn.Student
methodName=getName
被执行类为上方的Student类
package reflexLearn;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
public class ReflexTest {
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
// 1. 加载配置文件
Properties pro = new Properties();
// 2. 获取class目录下的配置文件
// [下面这种方法的保证pro.properties在当前模块的src下(与包根同级)]
ClassLoader classLoader = ReflexTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("pro.properties");
pro.load(is);
// 3. 获取配置问价中定义的数据
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
// 4. 加载该内进入内存
Class cls = Class.forName(className);
// 5. 创建对象
Constructor con = cls.getDeclaredConstructor();
con.setAccessible(true);
Object obj = con.newInstance();
// 6. 获取方法对象
Method method = cls.getMethod(methodName);
// 7. 执行方法
method.invoke(obj);
}
}