反射(reflection):在运行时期,动态地去获取类中的信息(类的信息,方法信息,构造器信息,字段等信息进行操作);
一个类中包含的信息有: 构造器,字段,方法。相应的,当用利用反射时,有四个类可以用来描述这些信息:
在反射操作某一个类之前,应该先获取这个类的字节码实例,获取字节码实例有三种方式:
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 }
对于对象来说,可以直接使用对象 getClass() 或者 Class.forName(className);类名 .class 都可以获取 Class 实例。但是我们的基本数据类型,就没有类的权限定名,也没有 getClass 方法。但八大基本数据类型和 void关键字都是有字节码实例的,可以通过 .class 获取。
数组类型对象可以通过对象的 getClass() 或者用数组类型的 .class 方法获得字节码实例,但要注意所有具有相同元素类型和维数的数组才共享同一份字节码(Class对象);
类的构函数有:有参数构造函数,无参构造函数,公共构造函数,非公共构造函数。根据不同的构造函数 Class 提供了几种获取不同构造函数的方法:
parameterTypes : 如果构造函数有参数,传递的是参数的字节码实例
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 }
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 }
一个类创建对象以后,一般就要执行对象的方法等等,使用反射操作对象的方法,首先要获取方法,再去执行。
一个类中的方法有很多类型:无参,有参,静态,可变参数,私有方法等等,针对不同的方法处理,提供了不同的获取方案。
Name : 指定的方法名称 parameterTypes : 方法参数的类型
方法获取以后就需要执行,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 }
如果方法中的参数有可变参数、数组,且可变参数、数组的元素是引用类型,在用反射执行方式时底层有一个拆箱的功能,会将数组的元素拆成一个个参数传递过来。如果我们直接用数组作为参数调用方法,会报运行时异常(基础类型的不会)。
解决方案:将数组外面再包装一层数组,拆箱只会进行一次,拆箱一次后,得到还是一个数组。
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 }
类中的字段有各种数据类型和各种访问权限,针对这些情况,反射操作有对应的方法来获取和处理。
参数: obj:表示字段底层所属对象,若该字段是static的,该值应该设为null value:表示将要设置的值
参数: 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 }
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 }