1. 反射机制概述
① 反射机制的含义
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
② 反射机制的作用
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法
③ 反射机制的优缺点
优点:
- 可以动态的创建对象和编译,最大限度发挥了java的灵活性。
- 特别是在
J2EE
的开发中,它的灵活性就表现的十分明显。比如,一个大型的软件,总会有不同的版本进行发布,每次新版本的发布不可能要求用户把旧版本卸载,再重新安装新版本。如果采用反射机制,它就可以不用卸载,只需要在运行时才动态的创建和编译。
缺点:
- 反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。
④ 反射机制的应用
spring
的IOC机制底层用的就是反射技术来构建用户提供的bean实例。jdbc
为各个厂商提供了驱动规范
,各个厂商分别有自己对应的实现,jdbc就是通过用户使用不同类型的驱动类去构建驱动对象的
,初始化的时候,通过反射去调用驱动对象的各个方法。- jdbc中有一行代码:
Class.forName('com.mysql.jdbc.Driver')
,那个时候只知道加载驱动,这就是反射。
2. Class类
① 反射和Class类共同提供反射支持
Class
和java.lang.reflect
一起对反射提供了支持。
② Class类
- Class是一个类,封装了
当前对象所对应的类的信息
,描述方法的Method
,描述字段的Filed
,描述构造器的Constructor
等属性. Class类是一个对象照镜子的结果
,对象可以看到自己有哪些属性、方法、构造器、实现了哪些接口等等。- 对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。
- Class 对象只能由系统建立对象,一个类(而不是一个对象)在 JVM 中只会有一个Class实例。
③ 获取Class对象的三种方式
-
通过类名获取:
Object.class
-
通过对象获取:
obj.getClass()
-
通过类的完全限定名获取:
Class.forName(String className)
1.通过类名,这也告诉我们:任何一个类都有一个隐含的静态成员变量class(知道类名时用) clazz = Person.class; 2.通过对象名,这种方式是用在传进来一个对象,却不知道对象类型的时候使用(知道对象时用) Object obj = new Person(); clazz = obj.getClass(); 3.通过全类名(会抛出ClassNotFoundException异常),一般框架开发中这种用的比较多, 因为配置文件中一般配的都是全类名,通过这种方式可以得到Class实例 String className=" com.atguigu.java.fanshe.Person"; clazz = Class.forName(className);
④ 通过newInstance实例化类
-
可以通过class对象的
newInstance()
方法示例化类 —— 通过class对象实例化,只能调用无参构造函数方法 用法 Object newInstance() 调用 缺省构造函数
,返回该Class对象的一个实例1.获取Class对象 String className="com.atguigu.java.fanshe.Person"; Class clazz = Class.forName(className); 2.利用Class对象的newInstance方法创建一个类的实例 Object obj = clazz.newInstance(); System.out.println(obj);
3. 反射
① 反射类
java.lang.reflect
类库主要包含了以下三个类:
Field
:可以使用get() 和 set()
方法读取和修改 Field 对象关联的字段;Method
:可以使用invoke()
方法调用与 Method 对象关联的方法;Constructor
:可以用 Constructor 创建新的对象
。
② 通过反射获取Field并使用的几种方法
- 通过反射机制获取类中的属性后,可以通过
f.set(obj, value)
去设置obj对象的属性f的值;可以通过f.get(obj)
去获取obj对象的属性f的值。1.获取Class对象 Class stuClass = Class.forName("fanshe.field.Student"); 2.获取所有的公有字段 Field[] fieldArray = stuClass.getFields(); for(Field f : fieldArray){ System.out.println(f.getName()); } 3.获取所有的字段(包括私有、受保护、默认的) fieldArray = stuClass.getDeclaredFields(); for(Field f : fieldArray){ System.out.println(f.getName()); } 4.获取公有字段,并调用 Field f = stuClass.getField("name"); System.out.println(f.getName()); //获取一个对象 Object obj = stuClass.newInstance();//产生Student对象–》Student stu = new Student(); //为字段设置值 f.set(obj, "刘德华");//为Student对象中的name属性赋值–》stu.name = ”刘德华” //验证 Student stu = (Student)obj; System.out.println("验证姓名:" + stu.name); 5.获取私有字段并调用 f = stuClass.getDeclaredField("phoneNum"); System.out.println(f.getName()); //暴力反射,解除私有限定 ,如果字段是私有的,不管是读值还是写值,都必须先调用setAccessible(true)方法 f.setAccessible(true); f.set(obj, "18888889999"); System.out.println("验证电话:" + stu.getPhoneNum());
③ 通过反射获取Method并使用的几种方法
- 通过反射机制获取类中的method后,可以通过
m.invoke(obj, [方法参数...])
去调用一个obj对象的m方法。1.获取Class对象 Class stuClass = Class.forName("fanshe.method.Student"); 2.获取所有公有方法 Method[] methodArray = stuClass.getMethods(); for(Method m : methodArray){ System.out.println(m.getName()); } 3.获取所有的方法,包括私有的 methodArray = stuClass.getDeclaredMethods(); for(Method m : methodArray){ System.out.println(m.getName()); } 4.获取指定的公有方法并使用 Method m = stuClass.getMethod("show1", String.class); //实例化一个Student对象 Object obj = stuClass.newInstance(); m.invoke(obj, "刘德华"); 5.获取指定的私有方法并使用 m = stuClass.getDeclaredMethod("show4", int.class); m.setAccessible(true);//解除私有限定 m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(由反射获取),一个是实参
④ 通过反射获取Constructor(构造器)并使用的几种方法
- 通过反射机制获取一个类的构造方法后,可以通过
c.newInstance([构造方法的参数 ... ])
去创建该类的一个对象。public static void main(String[] args) throws Exception { 1.加载Class对象 Class clazz = Class.forName("fanshe.Student"); 2.获取所有公有构造方法 Constructor[] conArray = clazz.getConstructors(); for(Constructor c : conArray){ System.out.println(c); } 3. 获取所有的构造方法(包括:私有、受保护、默认、公有) conArray = clazz.getDeclaredConstructors(); for(Constructor c : conArray){ System.out.println(c); } 4. 获取公有的无参构造方法 Constructor con = clazz.getConstructor(null); //① 因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型 //② 返回的是描述这个无参构造函数的类对象。 System.out.println("con = " + con); //调用构造方法 Object obj = con.newInstance(); 5. 获取私有、带参数的构造方法,并调用 con = clazz.getDeclaredConstructor(char.class); System.out.println(con); //调用构造方法 con.setAccessible(true); obj = con.newInstance('M'); }
⑤ 一些总结
如何创建对象实例?或如何利用反射实例化一个类?
-
通过
Class类
的newInstance()
方法:Class stuClass = Class.forName("fanshe.field.Student"); Object obj = stuClass.newInstance();//产生Student对象–》Student stu = new Student();
-
通过
Constructor类
的newInstance()
方法:Class clazz = Class.forName("fanshe.Student"); Constructor con = clazz.getConstructor(null); //调用构造方法创建对象 Object obj = con.newInstance();
4. 通过反射改变String对象的值
① 通过反射改变String对象的值
- String的不可变的原因,见博客:Java基础(一)
- 通过分析我们知道,要想修改String对象的值,就需要修改其value属性
- 在实践时发现,只修改value属性,其hashCode不会同步更新。
- 自己当时脑壳有包,想着内容都变了,hashCode也应该一起变,所以将hashCode也置零了
- 现在想想,同一个string对象hashCode应该满足幂等性,你改变了是咋回事 😂
public static void main(String[] args) { String string = "Hello, world!"; System.out.println("value: " + string + ", hashCode: " + string.hashCode()); try { // 获取class对象 Class stringClass = string.getClass(); // 获取value属性,设置为可访问 Field valueField = stringClass.getDeclaredField("value"); valueField.setAccessible(true); // 获取对象中value属性的值并更新 char[] value = (char[]) valueField.get(string); value[7] = 'W'; valueField.set(string, value); // 重新打印string的值和hashCode System.out.println("value: " + string + ", hashCode: " + string.hashCode()); // 发现hashCode在value更新后,未重新计算。 // 更新value的同时,需要将hashCode置为0,使其在获取hashCode时,重新计算 Field hashCodeField = stringClass.getDeclaredField("hash"); hashCodeField.setAccessible(true); // 直接将对象的hashCode置零 hashCodeField.setInt(string, 0); // 重新打印,发现hashCode被重新计算 System.out.println("value: " + string + ", hashCode: " + string.hashCode()); } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } }
② 获取和修改成员变量的值
-
基本类型的成员变量的值获取方法:
byte getByte(Object obj) //获取一个静态或实例 byte 字段的值。 int getInt(Object obj) //获取 int 类型或另一个通过扩展转换可以转换为 int 类型的基本类型的静态或实例字段的值。 short getShort(Object obj) //获取 short 类型或另一个通过扩展转换可以转换为 short 类型的基本类型的静态或实例字段的值。 long getLong(Object obj) //获取 long 类型或另一个通过扩展转换可以转换为 long 类型的基本类型的静态或实例字段的值。 float getFloat(Object obj) //获取 float 类型或另一个通过扩展转换可以转换为 float 类型的基本类型的静态或实例字段的值。 double getDouble(Object obj) //获取 double 类型或另一个通过扩展转换可以转换为 double 类型的基本类型的静态或实例字段的值。 boolean getBoolean(Object obj) //获取一个静态或实例 boolean 字段的值。 char getChar(Object obj) //获取 char 类型或另一个通过扩展转换可以转换为 char 类型的基本类型的静态或实例字段的值。
-
基本类型的成员变量的值修改方法:
void setByte(Object obj, byte b) //将字段的值设置为指定对象上的一个 byte 值。 void setShort(Object obj, short s) //将字段的值设置为指定对象上的一个 short 值。 void setInt(Object obj, int i) //将字段的值设置为指定对象上的一个 int 值。 void setLong(Object obj, long l) //将字段的值设置为指定对象上的一个 long 值。 void setFloat(Object obj, float f) //将字段的值设置为指定对象上的一个 float 值。 void setDouble(Object obj, double d) //将字段的值设置为指定对象上的一个 double 值。 void setBoolean(Object obj, boolean z) //将字段的值设置为指定对象上的一个 boolean 值。 void setChar(Object obj, char c) //将字段的值设置为指定对象上的一个 char 值。
-
引用类型的成员变量的值获取、修改方法:
Object get(Object obj) //返回指定对象上此 Field 表示的字段的值。 void set(Object obj, Object value) //将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
参考链接: