一直听说反射,但在实际开发过程中却没怎么接触过,最近在学习代理模式时,发现动态代理就是用的反射,于是学习一下。
一、反射定义
Java反射是指在程序运行状态中,获取一个类所有属性和方法,调用一个对象的任意一个属性和方法;这种动态的获取信息和动态调用对象的功能就是Java的反射机制。也就是在程序运行时,通过class文件对象来使用该文件的变量、构造方法以及成员方法。
JavaAPI提供了以下几个类,一些常用的方法通可以通过这几个类获取:
java.lang.Class;相当于.class文件对象
java.lang.reflect.Constructor;相当于类中构造方法对象
java.lang.reflect.Field;相当于类中成员变量对象
java.lang.reflect.Method;相当于类中成员方法对象
二、利用反射获取信息
使用反射,必须先得到类的class文件对象,Java中获取class文件对象有三种方式:
(1)Student s = new Student();Class c = s.getClass();
(2)Class c = Student.class;
(3)Class c = Class.forName("cn.reflect.demo.Student");//这里必须使用全类名
常用的是2、3两种方式;下面是一个简单的Student类,供测试使用。
package proxy.reflect.demo;
public class Student {
private String name ;
Integer age ;
public Double money;
public Student() {}
Student(String name){
this.name = name;
}
private Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public static void learn(String name,String address){
System.out.println(name+"在"+address+"学习");
}
private void learn(){
System.out.println("学生正在学习");
}
public String learn(String name){
return name+"正在学习";
}
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
创建对象实例
Object obj = c.newInstance();//调用的是Student的public修饰的无参构造方法,相当于Object obj = new Student();
有了对象之后就可以获取想要的信息了。
2.1获取构造方法
/**
Constructor[] getConstructors();只能获取public构造方法
Constructor[] getDeclaredConstructors();获取所有构造方法(包括private修饰的)
Constructor getConstructor(Class<?>... params);根据传递参数获取public修饰的构造方法对象
Constructor getDeclaredConstructor(Class<?>... params);根据传递参数获取构造方法对象(包括private修饰的)
*/
public static void main(String[] args) throws Exception{
//获取字节码文件对象
Class c = Class.forName("proxy.reflect.demo.Student");
//获取所有构造方法
Constructor[] constructors = c.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
//带参数的构造方法,传递的参数类型对应构造方法的参数
Constructor constructor = c.getDeclaredConstructor(String.class,Integer.class);
//取消Java访问检查,只有设置为true后才可以调用private修饰的构造方法
constructor.setAccessible(true);
//相当于Object obj = new Student("小明",24);
Object obj = constructor.newInstance("小明",24);
//使用System的打印流时自动调用obj对象的toString()方法
System.out.println(obj);
}
打印结果
private proxy.reflect.demo.Student(java.lang.String,java.lang.Integer)
proxy.reflect.demo.Student(java.lang.String)
public proxy.reflect.demo.Student()
Student [name=小明, age=24]
可以看到Student的所有构造方法都可以取到,还可以调用private修饰的构造方法;
2.2获取成员变量
获取成员变量和获取构造函数一样。首先获取到Constructor对象,然后使用Constructor创建新对象实例,然后调用get(),set()方法获取和修改与该Field对象关联的字段。
/**
Field[] getFields();得到所有带public修饰的成员方法对象数组
Field[] getDeclaredFields();得到所有带修饰的成员方法对象数组
Field getField(String name);获取对应name的Field对象,必须是public修饰的
Field getDeclaredField(String name);获取对应name的Field对象
field.get(Object obj)返回指定对象obj在此Field对象表示的值
field.set(Object obj, Object value)将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
*/
public static void main(String[] args) throws Exception{
//获取字节码文件对象
Class c = Class.forName("proxy.reflect.demo.Student");
//创建对象实例,默认调用的是无参构造,相当于Object ojb = new Student();
Object obj = c.newInstance();
//获取属性为name成员变量的Field对象
Field field = c.getDeclaredField("name");
//取消Java访问检查
field.setAccessible(true);
//为obj对象的field属性赋值"小明",相当于ojb.name="小明";
field.set(obj, "小明");
//打印结果:小明
System.out.println(field.get(obj));
}
2.3获取成员方法
使用Constructor创建新对象,然后用invoke()方法调用与Method对象关联的而方法。成员方法的获取和其他两个属性获取还是有一点区别的,看代码。
/**
Method[] c.getMethods();得到本类和父类所有public修饰的方法Method对象数组
Method[] c.getDeclaredMethods();得到所有方法Method对象数组
Method getMethod(String name, Class<?>... params) 得到某一方法method对象(只能是public修饰的),name是方法名,params是参数.class
Method getDeclaredMethod(String name, Class<?>... params) 得到某一方法method对象,name是方法名,params是参数.class
Object invoke(Object obj, Object... args)调用obj对象的method方法传递参数是args
*/
public static void main(String[] args) throws Exception {
Class c = Class.forName("proxy.reflect.demo.Student");
Object object = c.newInstance();
//可以获取本类和父类的public方法
Method[] methods = c.getMethods();
//只获取自己的所有的方法
//Method[] methods = c.getDeclaredMethods();
for (Method m : methods) {
System.out.println(m);
}
//获取方法名为learn并且没有形参的方法对象
Method m = c.getDeclaredMethod("learn");
m.setAccessible(true);
//调用object对象的与m对象关联方法
m.invoke(object);
//拿到方法名为learn参数是String类型的方法对象
Method m2 = c.getMethod("learn",String.class);
//调用object对象的方法并给参数赋值为"小明",该方法有返回值
Object result = m2.invoke(object, "小明");
Method m3 = c.getMethod("learn",String.class,String.class);
//调用静态方法,因为静态方法是随着类的加载而加载的,无需使用对象调用,所有这里传null,当然传对象也没有问题
m3.invoke(null, "-----\n小明","教室");
}
2.4一些其他的常用方法
以下几种方法也是比较常用的,不再一一描述,可以通过JavaAPI详细了解,它才是我们最好的老师!
方法名 | 含义 |
---|---|
Class方法 | |
getResourceAsStream(String name) | 查找具有给定名称的资源 |
getSimpleName() | 返回源代码中给出的底层类的简称 |
getClassLoader() | 返回该类的类加载器 |
getSuperclass() | 获取一个类的父类 |
Field方法 | |
getType() | 获取对象表示字段的声明类型 |
getName() | 获取对象表示字段的名称 |
Method方法 | |
getName() | 获取对象表示方法的名称 |
三、一些应用
反射的应用是非常广泛的,合理的利用反射可以很大程度上节省我们的开发时间。反射是在运行时期获取类信息的,我们利用这一特性,可以做很多事情。
3.1加载配置文件
在软件开发过程中,使用配置文件是必不可少的,通常把一些多变的数据写在配置文件,在修改时候只需需修改配置文件,增加了代码的可维护性。在学习jdbc的时候,将访问数据库的一些参数放到配置文件中,比如数据库链接、用户名、密码等,在更换数据库或者修改密码之后,只需修改配置文件即可。来看一下反射如何加载config.properties文件的。
dbName=jdbc:mysql://localhost:3306/reflect
username=jieke
password=rose
使用反射获取数据
public static void main(String[] args) throws Exception {
//加载文件中的键值对数据
Properties p = new Properties();
/**
JavaAPI:查找与给定类相关的资源的规则是通过定义类的 class loader 实现的,此方法委托此对象的类加载器,
如果此对象通过引导类加载器加载,则此方法将委托给 ClassLoader.getSystemResourceAsStream(String)。
通过以上说明此处可以填写任意类,因为这些类都会有由加载器加载,最后都是调用类加载器的方法;
所以此处写Object.class、String.class、ReflectProperty.class等等都时可以的。
不可以使用this(本类对象)、super(父类对象),在jvm加载一个类时,首先为static属性分配内存空间,而对象是在new时候才会被加载的
*/
InputStream is = ReflectProperty.class.getResourceAsStream("/config.properties");
p.load(is);
is.close();
System.out.println("数据库链接:"+p.getProperty("dbName")+"\n用户名:"+p.getProperty("username")+"\n密码:"+p.getProperty("password"));
}
打印结果
数据库链接:jdbc:mysql://localhost:3306/reflect
用户名:jieke
密码:rose
3.2泛型”擦除”
泛型”擦除”就是越过泛型检查,泛型是在程序编译期检查程序的,通过反射获取到程序运行期的class文件对象进行操作,即可越过泛型检查。不过该知识点在开发过程中基本上不可能用到,只是在一些面试题上会出现,简单了解即可。
public static void main(String[] args) {
//泛型简写,Java7之后出现的
List<Integer> list = new ArrayList<>();
Class c = list.getClass();
try {
//通过class对象得到方法名为add参数类型是Object的方法对象
//通过查询源码发现List的add()方法传递的就是Object
Method method = c.getMethod("add",Object.class);
method.invoke(list, "小明");
System.out.println(list);
} catch (Exception e) {
e.printStackTrace();
}
}
四、总结
Java反射的灵活性已经体验到了,很多框架都有用到反射,Spring的AOP,代码自动生成工具、mybatis等等。不过在我们平时开发过程中,是很少使用反射的,它会使我们的程序性能变低,因为使用反射调用类的构造方法、变量、方法的效率远低于直接代码。总之,有利有弊,开发过程中应尽量避开反射,假如想自己写框架,那么反射是必不可少的。