反射

1 什么是反射

反射(reflection):在运行时期,动态地去获取类中的信息(类的信息,方法信息,构造器信息,字段等信息进行操作);

一个类中包含的信息有: 构造器,字段,方法。相应的,当用利用反射时,有四个类可以用来描述这些信息:

  1. Class : 描述类
  2. Method : 描述方法
  3. Constructor :描述构造器
  4. Field :描述字段

2 获取类的 Class 实例的三种方式

在反射操作某一个类之前,应该先获取这个类的字节码实例,获取字节码实例有三种方式:

  1. 类名.class
  2. 类的对象.getClass()
  3. 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 获取方法

一个类中的方法有很多类型:无参,有参,静态,可变参数,私有方法等等,针对不同的方法处理,提供了不同的获取方案。

  1. getMethods() - 获取所有的公共方法,包括父类的公共方法,返回数组
  2. getDeclaredMethods() - 获取所有本类的方法,包括本类的私有方法,返回数组
  3. getMethod(String name, Class<?>... parameterTypes) - 获取指定方法名称的方法
  4. 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 获取字段

类中的字段有各种数据类型和各种访问权限,针对这些情况,反射操作有对应的方法来获取和处理。

  1. getFields() - 获取当前 Class 所表示类中所有的public的字段,包括继承的字段
  2. getDeclaredFields() - 获取当前 Class 所表示类中所有的字段,不包括继承的字段
  3. getField(String name) - 获取当前 Class 所表示类中该 fieldName 名字的字段,包括继承的字段
  4. 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 }

 

 
posted @ 2019-05-21 01:15  Carlos_Ouyang  阅读(311)  评论(0编辑  收藏  举报