反射机制使在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法
在Java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息
反射是Java语言中的一种机制,通过这种机制可以动态地实例化对象、读写属性、调用方法
反射也是很多框架开发的基础,比如hibernate,struts等框架
早在我们使用jdbc连接数据库的时候就已经使用到了Java的反射机制
Class.forName("com.mysql.jdbc.Driver.class");//加载MySql的驱动类
优点:
- 能够在运行时动态获取类的实例,提高灵活性
- 与动态编译结合
缺点:
- 使用反射性能较低,需要解析字节码(.class文件),将内存中的对象进行解析
- 相对不安全,因为它破坏了封装性(通过反射可以获得私有方法和属性)
解决方案:
- 通过setAccessible(true)关闭JDK的安全检查来提升反射速度
- 多次创建一个类的实例时,有缓存会快很多
- ReflectASM工具类,通过字节码生成的方式加快反射速度
拿Student举例(如下图),当Student被实例化时,实际是通过JVM(Java虚拟机)编译成.class文件,而反射机制就是在类被加载出来时,会生成一个Class对象,而这个被编译完成的.class文件生成的Student模型相当于是Class对象的子类(每一个class文件都有一个对应的Class模型,Class模型用于表示这个类的类型信息),并且Student类对于Class模型来说就是全透明的,包括private(私有化)属性和方法都是可以直接修改和调用的。
所以反射机制实际上是在被编译后的文件上动手脚
组成部分 | 访问方法 | 返回值类型 | 说明 |
---|---|---|---|
包路径 | getPackage() | Package对象 | 获得该类的存放路径 |
类名称 | getName() | String对象 | 获得该类的名称 |
继承类 | getSuperclass() | Class对象 | 获得该类继承的类 |
实现接口 | getInterfaces() | Class型数组 | 获得该类实现的所有接口 |
构造方法 | getConstructors() | Constructor型数组 | 获得所有权限为public的构造方法 |
getConstructor(Class>…parameterTypes) | Constructor对象 | 获得权限为public的指定构造方法 | |
getDealaredConstructors() | Constructor型数组 | 获得所有构造方法,按声明顺序返回 | |
getDealaredConstructor(Class>…parameterTypes) | Constructor对象 | 获得指定的构造方法 | |
方法 | getMethods() | Method型数组 | 获取所有权限为public的方法 |
getMethod(String name,Class>…parametersTypes) | Method对象 | 获得权限为public的指定方法 | |
getDeclareMethods() | Method型数组 | 获得所有方法,按声明顺序返回 | |
getDeclareMethod(String name,Class>…parameterTypes) | Method对象 | 获得指定方法 | |
成员变量 | getFields() | Field型数组 | 获得所有权限为public的成员变量 |
getField(Sgtring name) | Field对象 | 获得权限为public的指定成员变量 | |
getDeclaredFields() | Field型数组 | 获得所有成员变量,按声明顺序返回 | |
getDeclaredField(String name) | Field对象 | 获得指定的成员变量 | |
内部类 | getClasses() | Class型数组 | 获得所有权限为public的内部类 |
getDeclaredClasses() | Class型数组 | 获得所有内部类 | |
内部类的声明类 | getDeclaringClass() | Class对象 | 如果该类为内部类,则返回它的成员类,否则返回null |
**注意:**通过getFileds()和getMethods()方法依次获得权限为public的成员变量和方法时,将包含从超类(父类)中继承到的成员变量和方法;而通过getDeclaredFields()和getDeclaredMethods()只能获得在本类中定义的所有成员变量和方法
先创建一个测试用的Student类
package com.demo;
public class Student {
private String sid;
private String sname;
public Integer age;
static {
System.out.println("加载进jvm中!");
}
public Student() {
super();
System.out.println("调用无参构造方法创建了一个学生对象");
}
public Student(String sid) {
super();
this.sid = sid;
System.out.println("调用带一个参数的构造方法创建了一个学生对象");
}
public Student(String sid, String sname) {
super();
this.sid = sid;
this.sname = sname;
System.out.println("调用带二个参数的构造方法创建了一个学生对象");
}
@SuppressWarnings("unused")
private Student(Integer age) {
System.out.println("调用Student类私有的构造方法创建一个学生对象");
this.age = age;
}
public String getSid() {
return sid;
}
public void setSid(String sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public void hello() {
System.out.println("你好!我是" + this.sname);
}
public void hello(String name) {
System.out.println(name + "你好!我是" + this.sname);
}
@SuppressWarnings("unused")
private Integer add(Integer a, Integer b) {
return new Integer(a.intValue() + b.intValue());
}
@Override
public String toString() {
return "Student [sid=" + sid + ", sname=" + sname + ", age=" + age + "]";
}
}
获取Class对象的三种常用方法
//1.Class.forName("类的权限命名") 获取Class,需要抛出异常或者套用try catch
Class<Student> clazz =(Class<Student>)Class.forName("com.demo.Student");
//2. 类.class
Class clazz02 = Student.class;
//3. 对象.getClass()
Student stu = new Student();
Class clazz03 = stu.getClass();
package com.demo;
import java.lang.reflect.Constructor;
public class Demo {
public static void main(String[] args) throws Exception{
Class<Student> clazz =(Class<Student>)Class.forName("com.demo.Student");
//获取无参构造,Constructor:构造函数
Constructor<Student> c1 = clazz.getConstructor();
//实例学生对象
Student stu01 = c1.newInstance();
stu01.setSname("小明");
System.out.println(stu01);
//获取有参构造
Constructor<Student> c2 = clazz.getConstructor(String.class);
Student stu02 = c2.newInstance("1");
stu02.setSname("小黑");
System.out.println(stu02);
//获取有参构造
Constructor<Student> c3 = clazz.getConstructor(String.class,String.class);
Student stu03 = c3.newInstance("2", "小哈");
System.out.println(stu03);
//获取私有化的有参构造:Declared
Constructor<Student> c4 = clazz.getDeclaredConstructor(Integer.class);
//允许访问,Accessible访问控制符
c4.setAccessible(true);
Student stu04 = c4.newInstance(22);
System.out.println(stu04);
}
}
输出结果:
在反射获取构造函数中判断参数个数的方法:使用可变参数(如果前面还有其他参数,就必须放在最后),查看getConstructor源码即可发现:
//导包
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
//获取私有化的有参构造:Declared
Constructor<Student> c4 = clazz.getDeclaredConstructor(Integer.class);
//允许访问,Accessible访问控制符
c4.setAccessible(true);
Student stu04 = c4.newInstance(22);
System.out.println(stu04);
Method method = clazz.getMethod("hello");
stu04.setSname("hello");
//调用方法
method.invoke(stu04);
Method method02 = clazz.getMethod("hello",String.class);
method02.invoke(stu04, "world");
//获取私有方法
Method method03 = clazz.getDeclaredMethod("add",Integer.class, Integer.class);
method03.setAccessible(true);
int rv = (int)method03.invoke(stu04, 1,1);
System.out.println(rv);
运行结果:
//导包
import java.lang.reflect.Field;
//获取私有化的有参构造:Declared
Constructor<Student> c4 = clazz.getDeclaredConstructor(Integer.class);
//允许访问,Accessible访问控制符
c4.setAccessible(true);
Student stu04 = c4.newInstance(22);
System.out.println(stu04);
//设置私有属性
Field f = clazz.getField("age");
f.set(stu04, 78);
System.out.println(stu04);
System.out.println(f.get(stu04));
//获取私有属性
Field f01 = clazz.getDeclaredField("sname");
f01.setAccessible(true);
//调用属性
f01.set(stu04, "hello world");
System.out.println(stu04);
输出结果:
除了上述获取属性的方法之外,还可以通过BeanUtil.getproperty(对象名,“属性”)的方法获取属性,其原理是先调用类中的公开get属性方法获取再通过反射机制进行获取
上面和JavaBean的封装和设计有关:一个对象类中私有属性,公开get(得到值)、set(注入值)方法的意义是提供统一控制获取和处理类中私有属性的方法入口