反射

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 }

转载于:https://www.cnblogs.com/jc1997/p/10961169.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值