反射
1 什么是反射
反射(reflection):在运行时期,动态地去获取类中的信息(类的信息,方法信息,构造器信息,字段等信息进行操作);
一个类中包含的信息有: 构造器,字段,方法。相应的,当用利用反射时,有四个类可以用来描述这些信息:
- Class : 描述类
- Method : 描述方法
- Constructor :描述构造器
- Field :描述字段
2 获取类的 Class 实例的三种方式
在反射操作某一个类之前,应该先获取这个类的字节码实例,获取字节码实例有三种方式:
- 类名.class
- 类的对象.getClass()
- Class.forName("类的全限定名")
1 public class User { 2 @Test 3 public void testName() throws Exception { 4 //1.使用类名.class 获取类的字节码实例 5 Class<User> clz1 = User.class; 6 System.out.println(clz1.toString()); 7 8 //2.对象.getClass() 9 User user = new User(); 10 Class<?> clz2 = user.getClass(); 11 System.out.println(clz2); 12 13 //3.Class.forName("全限定名") - 用的最多 14 Class<?> clz3 = Class.forName("reflect.User"); 15 System.out.println(clz3); 16 } 17 }
2.1 九大内置类的字节码实例
对于对象来说,可以直接使用对象 getClass() 或者 Class.forName(className);类名 .class 都可以获取 Class 实例。但是我们的基本数据类型,就没有类的权限定名,也没有 getClass 方法。但八大基本数据类型和 void关键字都是有字节码实例的,可以通过 .class 获取。
2.2 数组类型的字节码实例
数组类型对象可以通过对象的 getClass() 或者用数组类型的 .class 方法获得字节码实例,但要注意所有具有相同元素类型和维数的数组才共享同一份字节码(Class对象);
3 构造函数 - Constructor
类的构函数有:有参数构造函数,无参构造函数,公共构造函数,非公共构造函数。根据不同的构造函数 Class 提供了几种获取不同构造函数的方法:
- getConstructors() - 获取所有的公共构造函数,返回数组
- getDeclaredConstructors() - 获取所有的构造函数,和访问权限无关,返回数组
- getConstructor(Class<?>... parameterTypes) - 获取指定的公共构造函数
- getDeclaredConstrucotr(Class<?>... parameterTypes) - 获取和访问权限无关的指定的构造函数
parameterTypes : 如果构造函数有参数,传递的是参数的字节码实例
3.1 获取构造函数
1 public class ConstructorTest { 2 @Test 3 public void testName() throws Exception { 4 5 //1.获取Student的字节码实例 6 Class<?> stuClz = Student.class; 7 8 //2.获取所有的公共构造函数 9 Constructor<?>[] cts1 = stuClz.getConstructors(); 10 for (Constructor<?> ct : cts1) { 11 System.out.println(ct); 12 } 13 System.out.println("----------------------"); 14 //3.获取所有的构造函数包括私有的 15 Constructor<?>[] cts2 = stuClz.getDeclaredConstructors(); 16 for (Constructor<?> ct : cts2) { 17 System.out.println(ct); 18 } 19 System.out.println("----------------------"); 20 21 //4.获取指定的构造函数(clz.getConstructor(...))只能获取公共的构造函数 22 Constructor<?> ct1 = stuClz.getConstructor(); 23 System.out.println(ct1); 24 25 Constructor<?> ct2 =stuClz.getConstructor(String.class); 26 System.out.println(ct2); 27 //4.获取指定的构造函数(clz.getDeclaredConstructor(...))获取的构造函数和权限没有关系 28 Constructor<?> ct3=stuClz.getDeclaredConstructor(String.class,int.class); 29 System.out.println(ct3); 30 } 31 }
3.2 调用构造函数创建对象
Constructor<T> 类:表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器。
常用方法:
newInstance(Object... initargs):如调用带参数的构造器,只能使用该方式。
参数:initargs:表示调用构造器的实际参数
返回:返回创建的实例
如果一个类中的构造器可以直接访问(public 权限),同时没有参数,那么可以直接使用 Class 对象中的 newInstance 方法创建对象。
如果一个类中有私有(private)构造器,需要设置访问权限后才能构造对象。通过 setAccessible(true) 设置权限。
1 public class NewInstanceTest { 2 @Test 3 public void testName() throws Exception { 4 5 //1.获取Student的字节码实例 6 Class<?> clz = Class.forName("cn.sxt.reflect.Student"); 7 8 //1.1如果类有无参数公共构造函数,直接可以使用类的字节码实例就创建对象 9 Student stu0 = (Student) clz.newInstance(); 10 11 12 //2.获取一个参数的构造函数 13 Constructor<Student> ct1 = (Constructor<Student>) clz.getConstructor(String.class); 14 //2.1.创建对象 15 Student stu1 = ct1.newInstance("东方不败"); 16 17 //3.获取私有构造函数并创建对象 18 Constructor<Student> ct2 = (Constructor<Student>) clz.getDeclaredConstructor(String.class,int.class); 19 //3.1设置权限可以创建对象 20 ct2.setAccessible(true); 21 //3.2创建对象 22 Student stu2 = ct2.newInstance("西门吹雪",50); 23 } 24 }
4 方法 - Method
一个类创建对象以后,一般就要执行对象的方法等等,使用反射操作对象的方法,首先要获取方法,再去执行。
4.1 获取方法
一个类中的方法有很多类型:无参,有参,静态,可变参数,私有方法等等,针对不同的方法处理,提供了不同的获取方案。
- getMethods() - 获取所有的公共方法,包括父类的公共方法,返回数组
- getDeclaredMethods() - 获取所有本类的方法,包括本类的私有方法,返回数组
- getMethod(String name, Class<?>... parameterTypes) - 获取指定方法名称的方法
- getDeclaredMethod(String name, Class<?>... parameterTypes) - 获取指定方法名称的方法,和访问权限无关
Name : 指定的方法名称
parameterTypes : 方法参数的类型
4.2 执行方法
方法获取以后就需要执行,Method对象中提供方法执行的功能。
invoke(Object obj, Object... args) - 执行方法
Obj :如果是对象方法,传指定的对象,如果是类方法,传 null
Args: 方法的参数
如果方法有返回结果,可以接收
如果是私有方法,反射默认是无法直接执行的,使用 setAccessible() 的方法,设置为true,即可忽略访问权限。
1 public class GetMethodTest { 2 3 @Test 4 public void testName() throws Exception { 5 // 1.获取Person字节码实例 6 Class<Person> clz = Person.class; 7 // 2.创建对象 8 Person p = clz.newInstance(); 9 10 // 3.获取方法(使用反射),获取所有公共方法,包含父类的公共方法 11 Method[] methods1 = clz.getMethods(); 12 for (Method method : methods1) { 13 System.out.println(method); 14 } 15 System.out.println("------------------------------"); 16 // 4.获取自己类中的所有方法(包括私有) 17 Method[] methods2 = clz.getDeclaredMethods(); 18 for (Method method : methods2) { 19 System.out.println(method); 20 } 21 System.out.println("------------------------------"); 22 // 4.获取单个指定名称的方法 23 Method method = clz.getMethod("hello2", String.class); 24 System.out.println(method); 25 26 // 4.1执行方法 27 Object res = method.invoke(p, "陆小凤"); 28 System.out.println(res); 29 30 // 5.获取私有的方法 31 Method hello3 = clz.getDeclaredMethod("hello3", String.class, int.class); 32 System.out.println(hello3); 33 34 // 5.1设置忽略访问权限 35 hello3.setAccessible(true); 36 37 Object res1 = hello3.invoke(p, "叶孤城", 30); 38 System.out.println(res1); 39 40 // 6.获取静态方法 41 Method staticMethod = clz.getMethod("staticMethod", String.class); 42 43 // 6.1执行静态方法 44 staticMethod.invoke(null, "花满楼"); 45 } 46 }
5 参数可变的构造方法和普通方法
如果方法中的参数有可变参数、数组,且可变参数、数组的元素是引用类型,在用反射执行方式时底层有一个拆箱的功能,会将数组的元素拆成一个个参数传递过来。如果我们直接用数组作为参数调用方法,会报运行时异常(基础类型的不会)。
解决方案:将数组外面再包装一层数组,拆箱只会进行一次,拆箱一次后,得到还是一个数组。
1 //类的定义 2 public class User { 3 public void print(int... nums) { 4 System.out.println(Arrays.toString(nums)); 5 } 6 7 public void print(String[] strs) { 8 System.out.println(Arrays.toString(strs)); 9 } 10 11 public User(int... nums) { 12 System.out.println(Arrays.toString(nums)); 13 } 14 public User(String... strs) { 15 System.out.println(Arrays.toString(strs)); 16 } 17 } 18 //测试类 19 public class ReflectTest { 20 public static void main(String[] args) { 21 Class<User> clz = User.class; 22 try { 23 //int[] 类型参数的构造方法 24 Constructor<User> cons1 = clz.getConstructor(int[].class); 25 User user = cons1.newInstance(new int[]{1,2,3,4}); 26 //可变参数其实也是一个数组,用相应类型的数组的字节码实例作为参数 27 Constructor<User> cons2 = clz.getConstructor(String[].class); 28 //外包一层数组 29 User user2 = cons2.newInstance(new Object[] {new String[]{"a","b","c","d"}}); 30 Method method1 = clz.getMethod("print", int[].class); 31 //int[] 类型参数的方法 32 method1.invoke(user, new int[] {1,2,3,4}); 33 Method method2 = clz.getMethod("print", String[].class); 34 //引用类型如果不包一层数组,会报错 35 //method2.invoke(user, new String[] {"a", "b", "c"}); 36 method2.invoke(user, new Object[] {new String[] {"a", "b", "c"}}); 37 } catch (Exception e) { 38 e.printStackTrace(); 39 } 40 } 41 }
6 操作字段 - Field
6.1 获取字段
类中的字段有各种数据类型和各种访问权限,针对这些情况,反射操作有对应的方法来获取和处理。
- getFields() - 获取当前 Class 所表示类中所有的public的字段,包括继承的字段
- getDeclaredFields() - 获取当前 Class 所表示类中所有的字段,不包括继承的字段
- getField(String name) - 获取当前 Class 所表示类中该 fieldName 名字的字段,包括继承的字段
- getDeclaredField(String name) - 获取当前 Class 所表示类中该 fieldName 名字的字段,不包括继承的字段
6.2 字段的常用方法
- setXX(Object obj, XX value):为基本类型字段设置值,XX表示基本数据类型
- set(Object obj, Object value):表示为引用类型字段设置值
参数:
obj:表示字段底层所属对象,若该字段是static的,该值应该设为null
value:表示将要设置的值
- getXX(Object obj):获取基本类型字段的值,XX表示基本数据类型
- get(Object obj):表示获取引用类型字段的值
参数:
obj:表示字段底层所属对象,若该字段是static的,该值应该设为null
返回:返回该字段的值.
同样的,要访问 private 字段,一样需要设置忽略访问权限(setAccessible(true))。
1 public class FieldTest { 2 @Test 3 public void testName() throws Exception { 4 //1.获取People字节码 5 Class<People> clz = People.class; 6 People p = clz.newInstance(); 7 8 //2.获取所有公共字段 9 Field[] fields1 = clz.getFields(); 10 for (Field field : fields1) { 11 System.out.println(field); 12 } 13 System.out.println("---------------------"); 14 //3.获取所有字段,和访问权限无关 15 Field[] fields2 = clz.getDeclaredFields(); 16 for (Field field : fields2) { 17 System.out.println(field); 18 } 19 System.out.println("---------------------"); 20 //3.获取指定的公共字段 21 Field emialField = clz.getField("emial"); 22 System.out.println(emialField); 23 24 //为字段设置值 25 emialField.set(p, "zhagnsan@qq.com"); 26 System.out.println(p); 27 //4.获取指定所有的字段,和访问权限无关 28 Field nameFiled = clz.getDeclaredField("name"); 29 System.out.println(nameFiled); 30 //设置忽略访问权限 31 nameFiled.setAccessible(true); 32 nameFiled.set(p, "张三"); 33 System.out.println(p); 34 35 //5 获取age字段 36 Field ageFile = clz.getDeclaredField("age"); 37 ageFile.setAccessible(true); 38 //设置忽略访问权限 39 ageFile.setInt(p, 18); 40 System.out.println(p); 41 } 42 }
7 Class 的其它方法
1 @Test 2 public void testName() throws Exception { 3 Class<People> clz = People.class; 4 //获取所有的接口的字节码实例 5 Class<?>[] interfaces = clz.getInterfaces(); 6 for (Class<?> intface : interfaces) { 7 System.out.println(intface); 8 } 9 //获取全限定名 10 System.out.println(clz.getName()); 11 //获取简单类名 12 System.out.println(clz.getSimpleName()); 13 //获取包 14 System.out.println(clz.getPackage().getName()); 15 }