zoukankan      html  css  js  c++  java
  • 反射

    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 }
     
  • 相关阅读:
    POJ 3614 Sunscreen
    POJ 2431 Expedition
    如何解决inline-block元素的空白间距 css 完美解决
    li的inline-block出现间隙原因,解决方案
    基线baseline
    CSS IE Hack
    css实现页面文字不换行、自动换行、强制换行
    IE 常见bug
    IE haslayout 问题引起的常见 bug
    CSS Cross-Browser Inline-Block
  • 原文地址:https://www.cnblogs.com/carlosouyang/p/10897603.html
Copyright © 2011-2022 走看看