什么是反射
- Reflection (反射)是被视为动态语言的关键,反射机制允许程序在执行期
借助于ReflectionAPI取得任何类的内部信息,并能直接操作任意对象的内
部属性及方法。- 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个
类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可
以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看
到类的结构,所以,我们形象的称之为:反射。
Java反射机制提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意-一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意-一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
反射的基础----字节码文件
Java源文件是不能直接运行的,.java源文件在编译之后形成字.class的字节码文件,然后使用双亲委派模式被类加载器加载之后形成字节码文件对象,才可以在JVM中运行
在Java中,一切皆对象,当字节码文件加载到JVM中后,会形成一个Class类对象,即:该类在JVM中变成了一个对象(注意与new T()创建的对象不同)。
字节码文件对象中包含了三部分内容:
- 构造方法—>Constructor对象
- 成员方法—>Method对象
- 成员变量—>Filed对象
注意:
不是所有的对象都在堆里,Class对象(字节码文件对象)存放在方法区,
不在堆里面
反射就是通过配置文件触发类加载拿到字节码文件对象,然后通过字节码文件对象操作类中的成员
双亲委派模式是什么鬼?
双亲委派模式是在Java 1.2后引入的,其工作原理的是,如果一个类加载器收到了
类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,
如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将
到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘
若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委
派模式
双亲委派模式要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,请注意双亲委派模式中的父子关系并非通常所说的类继承关系,而是采用组合关系来复用父类加载器的相关代码,
优势
- 避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
- 防止核心API库被随意篡改。
反射的实现
- 使用Class.forName(“类的全路径名”),该方法是Class中的静态方法,前提时必须知道类的全路径名
Class<?> c3=Class.forName("commenbit.refliction.Student");
- 使用.class方法,仅适合在编译器就已经明确要操作的Class
Class<?> c2=Student.class;
System.out.println(c2);
- 使用对象的getClass()方法
Student s1=new Student("luming",20,"男");
//通过getClass获取class对象
Class<?> c1=s1.getClass();
public class Student {
private String name;
private int age;
private String sex;
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 getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
public Student(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public static void main(String[] args) throws ClassNotFoundException {
Class<?> s0=int.class;
System.out.println(s0);
Student s1=new Student("luming",20,"男");
System.out.println(s1);
//通过getClass获取class对象
Class<?> c1=s1.getClass();
System.out.println(c1.getName());
System.out.println(c1);
//直接通过类名.class获取,该方法更为安全可靠,程序性能更高,实际上每个类都有一个隐含的静态实例class
Class<?> c2=Student.class;
System.out.println(c2);
//通过Class的静态方法forName进行获取,路径为全路径,有包名包含包名
Class<?> c3=Class.forName("commenbit.refliction.Student");
System.out.println(c3);
System.out.println(c1.equals(c2));
System.out.println(c2.equals(c3));
System.out.println(c3.equals(c1));
}
}
//运行结果
//class commenbit.refliction.Student
//class commenbit.refliction.Student
//true
//true
//true
反射的使用
- 先获取要反射类的Class对象,然后获取该对象的构造器来创建实例
Class.forName()
private static void test1() {
try {
Class<?> c=Class.forName("commenbit.refliction.Student");
// 返回该类所有公有的构造器类对象
// 如果将Student类中所有的构造函数全部设置成非public类型的,该数组的长度为0
Constructor[] constructors1=c.getConstructors();
System.out.println(constructors1.length);
// 获取类中所有的构造器对象:public和非public
Constructor[] constructors2=c.getDeclaredConstructors();
System.out.println(constructors2.length);
// 获取无参的公有的构造器
Constructor<?> stuConstruct1 = c.getConstructor();
System.out.println(stuConstruct1);
// 获取参数为:String,int的public构造器
Constructor<?> stuConstruct2 = c.getConstructor(String.class, int.class);
System.out.println(stuConstruct2);
// 获取参数为:String,String,int的构造器对象--不管是public还是非public都可以获取到
Constructor<?> stuConstruct3 = c.getDeclaredConstructor(String.class,
String.class, int.class);
System.out.println(stuConstruct3);
} catch (Exception e) {
e.printStackTrace();
}
}
- 创建对象实例
- 如果构造方法是公有的,可以直接调用Class类提供的newInstrance方法来构造对象
private static void test2() {
try {
Class<?> c=Class.forName("commenbit.refliction.Student");
// 获取到Student无参的构造器对象-调用newInstance实例化对象
Constructor<?> constructor=c.getConstructor();
Student student= (Student) constructor.newInstance();
System.out.println(student);
// 获取到Student的带有参数的构造器对象-调用newInstance实例化对象
Constructor<?> constructor1=c.getConstructor(String.class,int.class);
Student student1= (Student) constructor1.newInstance("lm",18);
System.out.println(student1);
} catch (Exception e) {
e.printStackTrace();
}
}
//Student{name='null', age=0, sex='null'}
//Student{name='lm', age=18, sex='null'}
2.如果构造方法是私有的,必须使用Constructor类的setAccessible方法改变构造器权限才可以实例化对象
private static void test3() {
Class<?> c=null;
try {
c=Class.forName("commenbit.refliction.Student");
Constructor<?> constructor=c.getDeclaredConstructor(String.class,String.class,int.class);
constructor.setAccessible(true);// 将构造器的权限改成可在外部访问的,即public
Student student= (Student) constructor.newInstance("lm","男",18);
System.out.println(student);
} catch (Exception e) {
e.printStackTrace();
}
}
//Student{name='lm', age=18, sex='男'}
- 反射属性
注意:该系列函数的返回值必须使用Filed或者Filed[]接收。
public static void test4() {
Class<?> classStudent = null;
try {
// 获取Student的类对象
classStudent = Class.forName("commenbit.refliction.Student");
// 获取所有的属性
Field[] fieles1 = classStudent.getDeclaredFields();
System.out.println(fieles1.length);
// 获取所有公有属性
Field[] fieles2 = classStudent.getFields();
System.out.println(fieles2.length);
// 获取Student的构造器对象-调用newInstance实例化对象
Constructor<?> stuConstruct = classStudent.getDeclaredConstructor(String.class,
String.class, int.class);
stuConstruct.setAccessible(true);
Student s = (Student)stuConstruct.newInstance("Peter", "男", 18);
// 反射私有属性name
Field name = classStudent.getDeclaredField("name");
name.setAccessible(true);
name.set(s, "Peter Wang");
System.out.println(name.get(s));
// 反射私有属性gender
Field gender = classStudent.getDeclaredField("sex");
gender.setAccessible(true);
gender.set(s, "女");
System.out.println(gender.get(s));
// 反射私有属性age
Field age = classStudent.getDeclaredField("age");
age.setAccessible(true);
age.set(s, 22);
System.out.println(age.get(s));
System.out.println(s);
} catch (Exception e) {
e.printStackTrace();
}
}
//3
//0
//Peter Wang
//女
//22
Student{name='Peter Wang', age=22, sex='女'}
- 反射方法
注意:上述方法的返回值必须使用Method或者Method[]进行接收,使用invoke对方法进行调用
public static void test5(){
try {
Class<?> classStudent = Class.forName("commenbit.refliction.Student");
// 获取所有的方法---只获取本类声明的方法
Method[] methods1 = classStudent.getDeclaredMethods();
System.out.println(methods1.length);
System.out.println(methods1);
// 获取所有方法---处理本类声明的公有方法外,基类中继承的public方法也获取到了
Method[] methods2 = classStudent.getMethods();
System.out.println(methods2.length);
System.out.println(methods2);
// 获取Student的构造器对象-调用newInstance实例化对象
Constructor<?> stuConstruct = classStudent.getDeclaredConstructor(String.class,
String.class, int.class);
stuConstruct.setAccessible(true);
Student s = (Student)stuConstruct.newInstance("Peter", "男", 18);
// 反射具体的方法-setName
Method method1 = classStudent.getDeclaredMethod("setName", String.class);
method1.setAccessible(true);
method1.invoke(s, "Lily");
// 反射具体的方法-getName
Method method2 = classStudent.getDeclaredMethod("getName");
method2.setAccessible(true);
System.out.println(method2.invoke(s));
} catch (Exception e) {
e.printStackTrace();
}
}
//14
//[Ljava.lang.reflect.Method;@6d6f6e28
//19
//[Ljava.lang.reflect.Method;@45ee12a7
//Lily
反射的优缺点
- 优点
- 对于任意一个类,可以知道该类的所有属性和方法;对于任意一个对象,都能调用它的任意一个方法
- 增加程序的灵活性和扩展性,降低耦合性,提高自适应能力
- 反射已经运用在了很多流行框架如:Struts、Hibernate、Spring 等等。
- 缺点
- Java 反射效率低主要原因是:
Method#invoke 方法会对参数做封装和解封操作
需要检查方法可见性
需要校验参数
反射方法难以内联
JIT 无法优化
- 反射技术绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂