Java的反射
1、反射机制的概述和字节码对象的获取方式
1、反射概要
反射,一种计算机处理方式。是程序可以访问、检测和修改它本身状态或行为的一种能力。JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
优点:
1、反射提高了程序的灵活性和扩展性。
2、降低耦合性,提高自适应能力。
3、它允许程序创建和控制任何类的对象,无需提前硬编码目标类。
缺点:
1、性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。
2、使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。
2、字节码文件获取的三种方式
1、对象名.getCalss(); // 次方法来自于Object 对象已经存在的情况下, 可以使用这种方式
2、类名.class // 类名.class这是一个静态的属性, 只要知道类名, 就可以获取
3、Class.forName("com.itheima_01.Student"); // 通过Class类中的静态方法, 指定字符串, 该字符串是类的全类名(包名+类名)
注意:此处将会抛出异常都系 ClassNotFoundException 防止传入错误的类名
3、案例代码
package com.itcast_01;
/*
* 反射:
* 在运行时,我们可以获取任意一个类的所有方法和属性
* 在运行时,让我们调用任意一个对象的所有方法和属性
*
* 反射的前提:
* 要获取类的对象(Class对象)
*/
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
// 通过Object的getClass()方法获取,必须要有对象
Student s = new Student();
Class clazz = s.getClass();
// 通过类名获取字节码对象
Class clazz2 = Student.class;
// static Class<?> forName(String className)
Class clazz3 = Class.forName(\"com.itheima_01.Student\");
System.out.println(clazz == clazz2);
System.out.println(clazz == clazz3);
System.out.println(clazz);
}
}
4、问题: 字节码对象是用来描述什么的?
用来描述.class文件的
面向对象阶段的时候讲过java中描述事物都是通过类的形式
而字节码文件也可以看做为一种事物, 如何描述这种事物? 那就看看这个事物是由什么组成的了
1、成员变量 == Filed
2、成员方法 == Method
3、构造方法 == Constructor
2、反射操作构造方法(Constructor)
1、通过获取的构造创建对象
步骤:
1、获得Class对象
2、获得构造
3、通过构造对象获得实例化对象
package com.itcast_01;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/*
* 通过反射获取构造方法并使用
* Constructor<?>[] getConstructors()
* Constructor<T> getConstructor(Class<?>... parameterTypes)
* newInstance()
*Constructor:
* newInstance(Object... initargs)
*/
public class ReflectDemo2 {
public static void main(String[] args) throws ReflectiveOperationException {
Class clazz = Class.forName("com.itheima_01.Student");
//method(clazz);
//Constructor<T> getConstructor(Class<?>... parameterTypes)
//method2(clazz);
//method3(clazz);
Object obj = clazz.newInstance();
System.out.println(obj);
}
private static void method3(Class clazz)throws Exception {
Constructor c = clazz.getConstructor(String.class,int.class);//获取有参构造,参数1类型为String,参数2类型为int
System.out.println(c);
Object obj = c.newInstance("lisi",30);
System.out.println(obj);
}
private static void method2(Class clazz)throws Exception {
Constructor c = clazz.getConstructor();//获取无参构造
System.out.println(c);
Object obj = c.newInstance();
System.out.println(obj);
}
private static void method(Class clazz) {
//Constructor<?>[] getConstructors() :获取所有public修饰的构造方法
Constructor[] cs = clazz.getConstructors();
for (int i = 0; i < cs.length; i++) {
System.out.println(cs[i]);
}
}
}
2、问题: 直接通过Class类中的newInstance()和获取getConstructor()有什么区别?
newInstance()方法, 只能通过空参的构造方法创建对象
getConstructor(Class<T>... parameterTypes)方法, 方法接受一个可变参数, 可以根据传入的类型来匹配对应的构造方法
总结(只能获取public修饰的构造方法 )
Constructor<?>[] getConstructors() // 方法1: 获取该类中所有的构造方法, 返回的是一个数组
Constructor<T> getConstructor(Class<?>... parameterTypes) // 方法2: 方法接受一个可变参数, 可以根据传入的类型, 来匹配对应的构造方法
3、获取任何权限的构造方法(推荐是使用这两种)
Constructor<?>[] getDeclaredConstructors() // 方法1: 获取该类或接口中所有的构造方法, 返回的是一个数组
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) // 方法2: 方法接受一个可变参数, 可以根据传入的类型, 来匹配对应的构造方法
3、反射操作成员变量(Field)
-
通过反射获取该类的字节码对象
Class clazz = Class.forName(“com.itcast.Person”);
-
创建该类对象
Object obj = clazz.newInstance(); // 调用了类中的无参构造方法
-
获取该类中需要操作的字段(成员变量)
getField(String name) --> 方法传入字段的名称.
Field f = clazz.getField(“age”); // 注意: 此方法只能获取公共的字段
-
通过字段对象中的方法修改属性值
void set(Object obj, Object value)
参数1): 要修改那个对象中的字段;
参数2): 将字段修改为什么值.f.set(p, 23);
1、案例代码
package com.itcast_01;
import java.lang.reflect.Field;
/*
* 通过反射获取成员变量并使用
* Field[] getFields() 获取所有public修饰的成员变量,返回一个数组,
* Field getField(String name) 获取指定的public修饰的成员变量 返回单个的成员变量名
*
* Field[] getDeclaredFields() 获取所有的成员变量名
* Field getDeclaredField(String name) 获取指定的成员变量名
*
* Field的取值和存值的方法
* Object get(Object obj)
* void set(Object obj, Object value)
*/
public class ReflectDemo3 {
public static void main(String[] args) throws ReflectiveOperationException {
// 获取学生类的字节码对象
Class clazz = Class.forName("com.itcast_01.Student");
// 获取学生类的对象
Object stu = clazz.newInstance();
// Field getField(String name) :根据字段名称获取公共的字段对象
Field f = clazz.getField("age"); // 获取成员变量对象
// System.out.println(f);
// void set(Object obj, Object value)
f.set(stu,28); // 通过成员变量对象,修改指定对为指定的值
// Object get(Object obj)
Object age = f.get(stu);// 通过对象获取成员变量的值
System.out.println(age);
System.out.println(stu);
}
private static void method(Class clazz){
// Field[] getFields() :获取公共的成员变量
Field[] fs = clazz.getFields();
for (int i = 0; i < fs.length; i++) {
System.out.println(fs[i]);
}
System.out.println("----------");
//getDeclaredFields() :获取所有的成员变量
Field[] fs2 = clazz.getDeclaredFields();
for (int i = 0; i < fs2.length; i++) {
System.out.println(fs2[i]);
}
}
}
2、方法总结
通过反射获取成员变量并使用
Field[] getFields() --> 返回该类所有(公共)的字段
Field getField(String name) --> 返回指定名称字段
Field[] getDeclaredFields() --> 暴力反射获取所有字段(包括私有)
Field getDeclaredField(String name) --> 暴力反射获取指定名称字段
Field:
Object get(Object obj) --> Field对象调用, 返回传入对象的具体字段
void set(Object obj, Object value) --> Field对象调用
参数1: 要修改的对象
参数2: 将此对象的字段修改为什么值.
3、反射private成员变量(练习)
反射private属性执行流程
- 获取学生类字节码对象
- 获取学生对象
- 通过getDeclaredField方法获取私有字段
- 通过setAccessible让jvm不检查权限
- 通过set方法设置对象为具体的值
案例代码
package com.itcast_01;
import java.lang.reflect.Field;
/*
* 通过反射获取私有成员变量并使用
* Field[] getDeclaredFields()
* Field getDeclaredField(String name)
*/
public class ReflectDemo4 {
public static void main(String[] args) throws ReflectiveOperationException {
// 获取学生类的字节码对象
Class clazz = Class.forName("com.itheima_01.Student");
// 获取学生对象
Object stu = clazz.newInstance();
// 获取私有的字段对象
Field f = clazz.getDeclaredField("name");
f.setAccessible(true); // 设置反射时取消Java的访问检查,暴力访问
// 给获取的成员变量赋值
f.set(stu, "lisi");
Object name = f.get(stu);
System.out.println(name);
}
}
方法总结
Field[] getDeclaredFields() --> 暴力反射获取所有字段(包括私有)
Field getDeclaredField(String name) --> 暴力反射获取指定名称字段
void setAccessible(boolean flag) --> 让jvm不检查权限
4、反射获取成员方法(Method)
1、反射获取普通成员方法
反射public方法执行流程
- 获取学生类字节码对象
- 反射手段创建学生对象
- 调用getMethod方法获取Method对象, 方法形参接受方法的名字
- 调用Method方法中的invoke()将方法运行
案例代码
package com.itcast_01;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/*
* 通过反射获取成员方法并使用
* Method getMethod(String name, Class<?>... parameterTypes)
* Method:
* Object invoke(Object obj, Object... args)
*/
public class ReflectDemo5 {
public static void main(String[] args) throws ReflectiveOperationException {
//获取学生类的字节码对象
Class clazz = Class.forName("com.itheima_01.Student");
//获取学生类的对象
Object stu = clazz.newInstance();
//获取无参有返回值的方法
Method m = clazz.getMethod("getName");
Object obj = m.invoke(stu); // 执行
System.out.println(obj);
}
private static void method2(Class clazz, Object stu) throws Exception {
//获取有参无返回值的方法
Method m = clazz.getMethod("setName", String.class);
m.invoke(stu, "lisi");
System.out.println(stu);
}
private static void method(Class clazz, Object stu) throws Exception {
//获取无参无返回值的方法
Method m = clazz.getMethod("method");
m.invoke(stu);
}
}
方法总结
Class:
Method getMethod(String name, Class<?>... parameterTypes)
// 此方法由字节码对象调用
// 参数1: 要反射的方法名称
// 参数2: 此方法需要接受的参数类型(注意,传入的都是字节码)
Method:
Object invoke(Object obj, Object... args)
// 方法由Method对象调用
// 参数1: 要由那个对象调用方法
// 参数2: 方法需要的具体实参(实际参数)
2、私有的成员方法怎么玩?
// 获取字节码对象
Class clazz = Class.forName("com.heima.Student");
// 创建学生对象
Object stu = clazz.newInstance();
// 暴力反射获取方法
Method method = clazz.getDeclaredMethod("method");
// 让jvm不检查权限
method.setAccessible(true);
// 执行方法
method.invoke(stu);
5、反射的综合案例
学生类
package com.reflect;
public class Student {
private String name;
private String sex;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
给学生类的属性赋值
package com.reflect;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.junit.Test;
/*反射案例 通过两种方法给Student类中的成员赋值
* 1、使用java中的反射技术将类中的属性与map中的key相同名称的,使用反射技术将key对应的value值赋值给属性.
*
* 2、通过get/setXxx的方法
*/
public class Demo1 {
@Test
public void test01() throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
map.put("name", "张三");
map.put("sex", "男");
map.put("age", 21);
map.put("address", "上海");
// 获取Student的字节码
Class clazz = Class.forName("com.reflect.Student");
// 获取字节码的无参对象
Object obj = clazz.newInstance();
// 获取类中的成员变量名Field
Field[] fieldNames = clazz.getDeclaredFields();
// 得到map中所有的key
Set<String> key = map.keySet();
// 遍历获取的成员变量
for (Field name : fieldNames) {
// 获取属性名
String field = name.getName();
if (key.contains(field)) {
// 给Student属性赋值
name.setAccessible(true); // 取消jvm的检查
name.set(obj, map.get(field));
}
}
}
@Test
public void test02() throws Exception {
// 采用get/set方法存入属性值
Map<String, Object> map = new HashMap<String, Object>();
map.put("name", "张三");
map.put("sex", "男");
map.put("age", 21);
map.put("address", "上海");
// 获取字节码
Class clazz = Class.forName("com.reflect.Student");
// 获取对象
Object obj = clazz.newInstance();
// 获取setXxx的成员方法
Method[] methods = clazz.getDeclaredMethods();
// 遍历map
for (String key : map.keySet()) {
// 将所有的key上加上set 不区分大小写
String mname = "set" + key;
for (Method method : methods) {
String m = method.getName();
if (mname.equalsIgnoreCase(m)) {
// 给属性赋值
method.invoke(obj, map.get(key));
}
}
}
}
}