Java反射基本原理和使用方式
什么是反射
概述
反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
使用反射的前提条件
必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码),原因是要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象。
类的正常加载过程如下图
优缺点
优点
- 可以在运行程序过程中,操作这些对象。
- 可以降低耦合度,提高程序的可扩展性。
缺点
- 反射很强大,但是耗性能.主要是为了做工具和框架使用的.
反射的使用
class类概述
Class
类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象(包括基本数据类型)。Class
没有公共构造方法。Class
对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass
方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。得到Class的实例
- 对象.getClass();得到对象的真实类型,每个方法都有所以在Object里,Student stu = new User(),Class clz = stu.getClass();
- 数据类型.class(就是一份字节码),Class<Student> clz = Student.class;
- Class.forName(String className),根据一个类的全限定名来构建Class对象,Class<?> clz = Class.forName("com.gbx.Student"),这里泛型写<?>原因是此时class不知道类,因为传的是字符串。
代码演示(以Student类为例):
package com.gbx; public class Demo { public static void main(String[] args) { //obj.getClass()方式获取Class对象 Student stu1 = new Student();//new 产生一个Student对象,一个Class对象。 Class stuClass = stu1.getClass();//获取Class对象 //类名.class方式获取Class对象 Class stuClass2 = Student.class; //Class.forName(String className)方式获取Class对象 try { Class stuClass3 = Class.forName("com.gbx.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名 } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
注意:
这三种方法获取的都是同一个class对象,因为表示都是JVM中共同的一份字节码。
Java内置9大的Class实例
对于对象来说,可以直接使用obj.getClass()获取Class实例,但是基本数据类型,就没有类的权限定名,也没有getClass方法,那么如何使用Class类来表示基本数据类型的Class实例?
byte,short,int,long,char,float,double,boolean ,void关键字,上述8种类型和void关键字,都有class属性。例子如下:
- 表示int的Class对象: Class clz = int.class;
- 表示boolean的Class对象: boolean.class;
- void: Class clz = void.class;
- 表示数组的Class实例:String[] sArr1 = {"A","C"},Class clz = String[].class;//所有具有相同元素类型和维数的数组才共享同一份字节码(Class对象);
所有的数据类型都有class属性,表示都是Class对象.
注意:
int的包装类是Integer,那么 Integer.class == int.class ?结果是false,说明是两份字节码.
Integer 和 int 不是同一种数据类型,在八大基本数据类型的包装类中都有一个常量:TYPE,TYPE表示的是该包装类对应的基本数据类型的Class实例。例子如下:
- Integer.TYPE—>int.class
- Integer.TYPE == int.class;//YES
- Integer.TYPE == Integer.class;//ERROR
获取类中的构造器
Class类获取构造器方法:
Constructor<T>类:表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器
批量获取的方法:
- public Constructor<?>[] getConstructors():该方法只能获取当前Class所表示类的public修饰的构造器。
- public Constructor<?>[] getDeclaredConstructors():获取当前Class所表示类的所有的构造器,和访问权限无关。
获取单个的方法:
- public Constructor<T> getConstructor(Class<?>... parameterTypes) :获取当前Class所表示类中指定的一个public的构造器。
- public Constructor getDeclaredConstructor(Class<?>... parameterTypes):获取当前Class所表示类的所有构造器中指定的一个构造器(可以是私有、受保护、默认、公有等)。
参数parameterTypes表示:构造器参数的Class类型。如:public Student(String name),Constructor c = clz.getConstructor(String.class)
演示方法如下,Student类:
package com.gbx;
public class Student { public Student() { System.out.println("---"); } public Student(String name) { System.out.println(name); } public Student(String name, String school) { System.out.println(name + " " + school); } }
测试类:
package com.gbx; public class Constructors { public static void main(String[] args) throws Exception { //1.加载Class对象 Class clazz = Class.forName("com.gbx.Student"); //2.获取所有公有构造器 System.out.println("****所有公有构造器****"); Constructor[] conArray = clazz.getConstructors(); for(Constructor c : conArray){ System.out.println(c); } //3.获取所有构造器 System.out.println("****所有的构造器****"); conArray = clazz.getDeclaredConstructors(); for(Constructor c : conArray){ System.out.println(c); } //3.获取单个公有无参构造器 System.out.println("****获取单个公有、无参的构造器****"); Constructor con = clazz.getConstructor(null); System.out.println(con); //4.获取单个指定公有构造器 System.out.println("****获取单个指定的公有构造器****"); con = clazz.getConstructor(String.class); System.out.println(con); //5.获取单个指定构造器 System.out.println("****获取单个指定的构造器****"); con = clz.getDeclaredConstructor(String.class); System.out.println(con); } }
调用构造器创建对象
常用方法:
public T newInstance(Object... initargs):使用此 Constructor
对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例,参数initargs:表示调用构造器的实际参数,返回:返回创建的实例,T表示Class所表示类的类型。
提示:
如果一个类中的构造器可以直接访问,同时没有参数,那么可以直接使用Class类中的newInstance方法创建对象,public Object newInstance():相当于new 类名()
代码演示如下:
public class newInstances { public static void main(String[] args) throws Exception { //1.加载Class对象 Class<Student> clz = Student.class; //2.获取构造器 Constructor<Student> c = clz.getConstructor(String.class); //3.调用构造器,创建对象 Student stu1 = c.newInstance("gbx"); //直接访问不带参数的构造器可以直接使用Class的newInstance()方法 Student stu2 = clz.newInstance(); } }
获取类中的成员变量并调用
批量获取的方法:
- Field[] getFields():获取当前Class所表示类的所有"公有字段"。
- Field[] getDeclaredFields():获取当前Class所表示类的所有字段,包括:私有、受保护、默认、公有。
获取单个的方法:
- public Field getField(String fieldName):获取当前Class所表示类的某个"公有的"字段。
- public Field getDeclaredField(String fieldName):获取当前Class所表示类的某个字段(可以是私有的)。
设置字段的值:
- public void set(Object obj,Object value) 参数 obj:要设置的字段所在的对象,value:要为字段设置的值
代码演示,Student类:
package com.gbx.field; public class Student { public Student(){} //**字段**// public String name; protected int age; char sex; private String phoneNum; }
测试类:
package com.gbx.field; import java.lang.reflect.Field; public class Fields { public static void main(String[] args) throws Exception { //1.获取Class对象 Class stuClass = Class.forName("com.gbx.field.Student"); //2.获取字段 System.out.println("***获取所有公有的字段***"); Field[] fieldArray = stuClass.getFields(); for(Field f : fieldArray){ System.out.println(f); } System.out.println("***获取所有的字段(包括私有、受保护、默认)***"); fieldArray = stuClass.getDeclaredFields(); for(Field f : fieldArray){ System.out.println(f); } System.out.println("***获取某个"公有的"字段并设置字段值***"); Field f = stuClass.getField("name"); System.out.println(f); System.out.println("***获取私有字段并设置字段值***"); f = stuClass.getDeclaredField("phoneNum"); System.out.println(f); //获取构造器,调用构造器获取一个对象 Object obj = stuClass.getConstructor().newInstance();//产生Student对象,相当于Student stu = new Student(); //为字段设置值 f.set(obj, "张三");//为Student对象中的name属性赋值,相当于stu.name = "张三" //检验 Student stu = (Student)obj; System.out.println("姓名:" + stu.name); f.setAccessible(true);//Student类中的成员变量phoneNum为private,故必须解除“phoneNum”私有限定 f.set(obj, "12345678900"); System.out.println("电话:" + stu.phoneNum); } }
控制台输出:
***获取所有公有的字段*** public java.lang.String com.gbx.field.Student.name ***获取所有的字段(包括私有、受保护、默认的)*** public java.lang.String com.gbx.field.Student.name protected int com.gbx.field.Student.age char fanshe.com.gbx.Student.sex private java.lang.String com.gbx.field.Student.phoneNum ***获取某个“公有的”字段并设置字段值*** public java.lang.String com.gbx.field.Student.name 姓名:张三 ***获取私有字段并设置字段值*** private java.lang.String com.gbx.field.Student.phoneNum 电话:12345678900
提示:
Java反射机制提供的setAccessible()方法可以取消Java的权限控制检查。
获取类中的成员方法并调用
批量获取的方法:
- public Method[] getMethods():获取当前Class所表示类和继承过来的所有"公有"方法(包含了父类的方法也包含Object类)。
- public Method[] getDeclaredMethods():获取当前Class所表示类的所有成员方法,包括私有的(不包括继承的,和访问权限无关)。
获取单个的方法:
- public Method getMethod(String name,Class<?>... parameterTypes):获取当前Class所表示类的某个"公有"方法(包括继承的)。
- public Method getDeclaredMethod(String methodName,Class<?>... parameterTypes):获取当前Class所表示类的某个成员方法(不包括继承的)。
参数 methodName:方法名,Class ... parameterTypes:形参的Class类型对象
调用方法:
- public Object invoke(Object obj,Object ... args) 参数 obj:要调用方法的对象,args:调用方法时所传递的实参
代码演示,Student类:
package com.gbx.method; public class Student { //****成员方法****// public void show1(String s){ System.out.println("调用公有的String参数的show1(): s = " + s); } protected void show2(){ System.out.println("调用受保护的无参的show2()"); } void show3(){ System.out.println("调用默认的无参的show3()"); } private String show4(int age){ System.out.println("调用私有的且有返回值的int参数的show4(): age = " + age); return age; } }
测试类:
package com.gbx.method; import java.lang.reflect.Method; public class MethodClass { public static void main(String[] args) throws Exception { //1.获取Class对象 Class stuClass = Class.forName("com.gbx.method.Student"); //2.获取所有公有方法 System.out.println("****获取所有”公有“方法****"); Method[] methodArray = stuClass.getMethods(); for(Method m : methodArray){ System.out.println(m); } System.out.println("****获取所有本类方法,包括私有****"); methodArray = stuClass.getDeclaredMethods(); for(Method m : methodArray){ System.out.println(m); } System.out.println("****获取公有的show1()方法****"); Method m = stuClass.getMethod("show1", String.class); System.out.println(m); //实例化一个Student对象 Object obj = stuClass.getConstructor().newInstance(); //调用方法,相当于stu.show1("张三") m.invoke(obj, "张三"); System.out.println("****获取私有的show4()方法****"); m = stuClass.getDeclaredMethod("show4", int.class); System.out.println(m); m.setAccessible(true);//解除私有限定 Object result = m.invoke(obj, 20); System.out.println("返回值:" + result); } }
控制台输出:
****获取所有”公有“方法**** public void com.gbx.method.Student.show1(java.lang.String) public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException public final void java.lang.Object.wait() throws java.lang.InterruptedException public boolean java.lang.Object.equals(java.lang.Object) public java.lang.String java.lang.Object.toString() public native int java.lang.Object.hashCode() public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll() ****获取所有本类方法,包括私有**** public void com.gbx.method.Student.show1(java.lang.String) private java.lang.String com.gbx.method.Student.show4(int) protected void com.gbx.method.Student.show2() void com.gbx.method.Student.show3() ****获取公有的show1()方法**** public void com.gbx.method.Student.show1(java.lang.String) 调用公有的String参数的show1(): s = 张三 ****获取私有的show4()方法**** private java.lang.String com.gbx.method.Student.show4(int) 调用私有的且有返回值的int参数的show4(): age = 20 返回值:20
特例:反射main方法
代码演示,student类:
package com.gbx.main; public class Student { public static void main(String[] args) { System.out.println("main方法执行"); } }
测试类:
package com.gbx.main; import java.lang.reflect.Method; public class Main { public static void main(String[] args) { try { //1、获取Student对象的字节码 Class clazz = Class.forName("com.gbx.main.Student"); //2、获取main方法 Method methodMain = clazz.getMethod("main", String[].class);//第一个参数:方法名称,第二个参数:方法形参的类型, //3、调用main方法 // methodMain.invoke(null, new String[]{"a","b","c"});//错误形式 //第一个参数,对象类型,因为方法是static静态的,所以为null可以,第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数,这里拆的时候将 new String[]{"a","b","c"} 拆成3个对象,所以需要将它强转。 methodMain.invoke(null, (Object)new String[]{"a","b","c"});//方式一 // methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});//方式二 } catch (Exception e) { e.printStackTrace(); } } }
反射方法的其它使用之通过反射运行配置文件内容
student类:
public class Student { public void show(){ System.out.println("It is show()"); } }
配置文件以txt文件为例子(p.txt):
className = com.gbx.Student
methodName = show
测试类:
import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.lang.reflect.Method; import java.util.Properties; public class Demo { public static void main(String[] args) throws Exception { //通过反射获取Class对象 Class stuClass = Class.forName(getValue("className"));//"com.gbx.Student" //2获取show()方法 Method m = stuClass.getMethod(getValue("methodName"));//show //3.调用show()方法 m.invoke(stuClass.getConstructor().newInstance()); } //此方法接收一个key,在配置文件中获取相应的value public static String getValue(String key) throws IOException{ Properties p = new Properties();//获取配置文件的对象 FileReader fr = new FileReader("p.txt");//获取字符输入流 p.load(fr);//将流加载到配置文件对象中 in.close(); return p.getProperty(key);//返回根据key获取的value值 } }
优点:
当我们升级这个系统时,不要Student类,而需要新写其他类时,这时只需要更改p.txt里的文件内容就可以了,代码不用改动。
反射方法的其它使用之通过反射越过泛型检查
泛型用在编译期,编译过后泛型擦除(消失),所以是可以通过反射越过泛型检查的。
例如:有一个String泛型的集合,怎样能向这个集合中添加一个Integer类型的值?
测试类:
import java.lang.reflect.Method; import java.util.ArrayList; public class Demo { public static void main(String[] args) throws Exception{ ArrayList<String> list = new ArrayList<>(); list.add("aaa"); list.add("bbb"); //list.add(100); //获取ArrayList的Class对象,反向的调用add()方法,添加数据 Class listClass = list.getClass(); //得到 list对象的字节码对象 //获取add()方法 Method m = listClass.getMethod("add", Object.class); //调用add()方法 m.invoke(list, 100); //遍历集合 for(Object obj : list){ System.out.println(obj); } } }
控制台输出:
aaa
bbb
100
这样说明可以通过反射越过泛型检查,向String泛型的集合中添加一个Integer类型的值。
总结
反射的基本知识在上面已经讲到,涉及到反射机制与反射的具体使用方式都已展开叙述,反射主要的用处在于设计框架,而使用框架时是不需要用到反射的。