zoukankan      html  css  js  c++  java
  • 反射

    1. 什么是反射?

    我们通过案例来了解什么是反射。

    1.1. 问题1

    1.对象有编译类型和运行类型

               Object obj = new java.util.Date();

      编译类型:Object

      运行类型(其实就是obj对象真实的类型):java.util.Date

      需求:根据对象obj调用Date类中的一个方法,toLocaleString,如何来做?

        obj.toLocaleString()代码在编译阶段去编译类型Object中检查是否有该方法,若没有,编译失败.

    解决方案1:强制转换objDate类型,前提:必须知道对象的真实类型是什么?

          Date d = (Date)obj;

          d.toLocaleString();//YES

    如果我不知道obj的真实类型,那又如何去调用toLolcaeString方法. 如何去做?

    解决方案2 使用反射

    1.2. 问题2

    对象使用类描述的,但是Java中一些皆对象,其实所有的类,底层都有一个字节码对象,用什么来描述这一个个类底层的字节码对象

    解决方案 使用反射

    1.3. 反射

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

    一个类中包含的信息有: 构造器,字段,方法

    如何使用反射描述这些相关的信息 

    Class : 描述类

    Method : 描述方法

    Constructor :描述构造器

    Field :描述字段

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

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

    获取字节码实例有三种方式

    注意 :同一个类在JVM的字节码实例只有一份

    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("cn.sxt.reflect.User");
      15         System.out.println(clz3);
      16     }    
      17 }

      3. 获取九大内置类的字节码实例

      对于对象来说,可以直接使用对象.getClass()或者Class.forName(className); 类名.class都可以获取Class实例.

      但是我们的基本数据类型,就没有类的权限定名,也没有getClass方法.

       

      问题那么如何使用Class类来表示基本数据类型的Class实例?

      八大基本数据类型和 void关键字都是有 字节码实例的

      byte,short,int,long,char,float,double,boolean ,void关键字

      数据类型/void.class 即可

      每个基本数据类型都是包装类型 int ----Integer包装类型

      注意: 基本数据类型和包装数据类型底层的字节码实例是不相同

       1 //获取8大基本数据类型和void的字节码实例
       2 //byte,short,int,long,char,float,double,boolean ,void关键字
       3 public class BaiscDataTypeClassTest {
       4     @Test
       5     public void testName() throws Exception {
       6         //1.获取byte的字节码实例
       7         Class<?> byteClz = byte.class;
       8         System.out.println(byteClz);
       9         //....
      10         //获取void关键字的字节码实例
      11         Class<?> voidClz =void.class;
      12         System.out.println(voidClz);
      13         
      14         
      15         //所有的基本数据类型都有包装类型
      16         //int---Integer 
      17         //int 和Integer 的字节码是绝对不相等的
      18         Class<?> intClz1 = int.class;
      19         Class<?> intClz2 = Integer.class;
      20         System.out.println(intClz1);
      21         System.out.println(intClz2);
      22         System.out.println(intClz1 == intClz2);    
      23     }
      24 }

      4. 获取数组类型的字节码实例

      表示数组的Class实例:

         String[] sArr1 = {"A","C"};

         Class clz = String[].class;//此时clz表示就是一个String类型的一位数组类型

      所有具有相同元素类型和维数的数组才共享同一份字节码(Class对象);

         注意:和数组中的元素没有一点关系.

       1 package cn.sxt.reflect._01.getclass;
       2 
       3 import static org.junit.Assert.*;
       4 
       5 import org.junit.Test;
       6 
       7 //获取数组类型的字节码实例
       8 public class ArrayClassTest {
       9     @Test
      10     public void testName() throws Exception {
      11         //定义数组
      12         int[] arr = {1,2,3};
      13         Class<int[]> clz1 = (Class<int[]>) arr.getClass();
      14         System.out.println(clz1);
      15         Class<int[]>  clz2= int[].class;
      16         System.out.println(clz2);
      17         
      18         
      19         int[] arr2 = {2,3,4};
      20         Class<int[]> clz3 = (Class<int[]>) arr2.getClass();
      21         System.out.println(clz3);
      22         
      23         
      24         System.out.println(clz1 == clz2);//true
      25         System.out.println(clz1 == clz3);//true
      26     }
      27 }

      5. 构造函数-Constructor

      5.1. 获取构造函数

      类的构函数有 有参数构造函数,无参构造函数,公共构造函数,非公共构造函数,根据不同的构造函数 Class提供了几种获取不同构造函数的方法。

       Constructor<?>[]

      getConstructors() 
      获取所有的公共构造函数

       Constructor<?>[]

      getDeclaredConstructors() 
      获取所有的构造函数,和访问权限无关

       Constructor<T>

      getConstructor(Class<?>... parameterTypes) 
      获取指定的公共构造函数

      parameterTypes : 如果构造函数有参数,传递的是参数的字节码实例

       Constructor<T>

      getDeclaredConstructor(Class<?>... parameterTypes) 
      获取和访问权限无关的指定的构造函数

       1 //通过字节码实例获取构造器
       2 public class ConstructorTest {
       3     @Test
       4     public void testName() throws Exception {
       5         
       6         //1.获取Student的字节码实例
       7         Class<?>  stuClz = Student.class;
       8         
       9         //2.获取所有的公共构造函数
      10         Constructor<?>[] cts1 = stuClz.getConstructors();
      11         for (Constructor<?> ct : cts1) {
      12             System.out.println(ct);
      13         }
      14         System.out.println("----------------------");
      15         //3.获取所有的构造函数包括私有的
      16         Constructor<?>[] cts2 = stuClz.getDeclaredConstructors();
      17         for (Constructor<?> ct : cts2) {
      18             System.out.println(ct);
      19         }
      20         System.out.println("----------------------");
      21         
      22         //4.获取指定的构造函数(clz.getConstructor(...))只能获取公共的构造函数
      23         Constructor<?> ct1 = stuClz.getConstructor();
      24         System.out.println(ct1);
      25         
      26         Constructor<?> ct2 =stuClz.getConstructor(String.class);
      27         System.out.println(ct2);
      28         //4.获取指定的构造函数(clz.getDeclaredConstructor(...))获取的构造函数和权限没有关系
      29         Constructor<?> ct3=stuClz.getDeclaredConstructor(String.class,int.class);
      30         System.out.println(ct3);
      31     }
      32 }

       

      5.2. 调用构造函数创建对象

      调用构造器,创建对象

      Constructor<T>:表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器

      常用方法:

      public T newInstance(Object... initargs):如调用带参数的构造器,只能使用该方式.

          参数:initargs:表示调用构造器的实际参数

          返回:返回创建的实例,T表示Class所表示类的类型

      如果:一个类中的构造器可以直接访问,同时没有参数.,那么可以直接使用Class类中的newInstance方法创建对象.

       public Object newInstance():相当于new 类名();

      调用私有的构造器:

      5.2.1. Constructor 创建对象的方法

       T

      newInstance(Object... initargs) 
                使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。

      5.2.2. Class类中创建对象的方法

      如果使用Class直接创建对象,必须保证类中有一个无参数公共构造函数

       T

      newInstance() 
      创建此 Class 对象所表示的类的一个新实例。

      5.2.3. 设置忽略访问权限

      默认执行私有构造函数报如下错误

      如果是私有构造方法,反射默认是无法直接执行的,找到父类中AccessibleObject的方法,设置为true,即可忽略访问权限

       void

      setAccessible(boolean flag) 
      如果执行私有方法,设置忽略访问权限 为 true即可

       1 //使用构造器创建对象
       2 public class NewInstanceTest {
       3     @Test
       4     public void testName() throws Exception {
       5         
       6         //1.获取Student的字节码实例
       7         Class<?> clz = Class.forName("cn.sxt.reflect.Student");
       8         
       9         //1.1如果类有无参数公共构造函数,直接可以使用类的字节码实例就创建对象
      10         Student stu0 = (Student) clz.newInstance();
      11         
      12         
      13         //2.获取一个参数的构造函数
      14         Constructor<Student> ct1 = (Constructor<Student>) clz.getConstructor(String.class);
      15         //2.1.创建对象
      16         Student stu1 = ct1.newInstance("东方不败");
      17         
      18         //3.获取私有构造函数并创建对象
      19         Constructor<Student> ct2 =  (Constructor<Student>) clz.getDeclaredConstructor(String.class,int.class);
      20         //3.1设置权限可以创建对象
      21         ct2.setAccessible(true);
      22         //3.2创建对象
      23         Student stu2 = ct2.newInstance("西门吹雪",50);
      24     }
      25 }

      6. 操作方法-Method

      一个类创建对象以后,一般就要执行对象的方法等等,使用反射操作对象

      首先要获取方法,再去执行

      6.1. 获取方法和方法的执行

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

      6.1.1. 使用Class 获取对应的方法

       Method[]

      getMethods() 
      获取所有的公共方法,包括父类的公共方法

       Method[]

      getDeclaredMethods() 
      获取所有本类的方法,包括本类的私有方法

       Method

      getDeclaredMethod(String name, Class<?>... parameterTypes) 
      获取指定方法名称的方法,和访问权限无关

      Name : 指定的方法名称

      parameterTypes : 方法参数的类型

      6.1.2. Method执行方法

      方法获取以后就需要执行。Method对象中提供方法执行的功能

       Object

      invoke(Object obj, Object... args) 
      执行方法

      Obj :如果是对象方法,传指定的对象,如果是类方法,传 null

      Args: 方法的参数

      如果方法有返回结果,可以接收

      6.1.3. 设置忽略访问权限

      如果是私有方法,反射默认是无法直接执行的,找到父类中AccessibleObject的方法,设置为true,即可忽略访问权限

       void

      setAccessible(boolean flag) 
      如果执行私有方法,设置忽略访问权限 为 true即可

      6.2. 方法操作的代码

      6.2.1. Student

       1 package cn.sxt.reflect._03method;
       2 
       3 import java.util.Arrays;
       4 
       5 public class Person {
       6     
       7     public void hell1() {
       8         System.out.println("我是无参数无返回值方法");
       9     }
      10     
      11     public String hello2(String name) {
      12 
      13         return "你好 :"+name;
      14     }
      15     
      16     private String hello3(String name,int age) {
      17         return "我是 :"+name+",今年 :"+age;
      18     }
      19     
      20     
      21     
      22     public static void  staticMethod(String name) {
      23         System.out.println("我是静态方法 :"+name);
      24     }
      25     
      26     
      27     
      28     public static void method1(int[] intArr) {
      29         System.out.println(Arrays.toString(intArr));
      30     }
      31     
      32     public static void method2(String[] strArr) {
      33         System.out.println(Arrays.toString(strArr));
      34     }
      35     
      36 }

      6.2.2. 测试类

       1 package cn.sxt.reflect._03method;
       2 
       3 import static org.junit.Assert.*;
       4 
       5 import java.lang.reflect.Method;
       6 
       7 import org.junit.Test;
       8 
       9 //获取Person类的方法
      10 public class GetMethodTest {
      11 
      12     @Test
      13     public void testName() throws Exception {
      14         // 1.获取Person字节码实例
      15         Class<Person> clz = Person.class;
      16         // 2.创建对象
      17         Person p = clz.newInstance();
      18 
      19         // 3.获取方法(使用反射),获取所有公共方法,包含父类的公共方法
      20         Method[] methods1 = clz.getMethods();
      21         for (Method method : methods1) {
      22             System.out.println(method);
      23         }
      24         System.out.println("------------------------------");
      25         // 4.获取自己类中的所有方法(包括私有)
      26         Method[] methods2 = clz.getDeclaredMethods();
      27         for (Method method : methods2) {
      28             System.out.println(method);
      29         }
      30         System.out.println("------------------------------");
      31         // 4.获取单个指定名称的方法
      32         Method method = clz.getMethod("hello2", String.class);
      33         System.out.println(method);
      34 
      35         // 4.1执行方法
      36         Object res = method.invoke(p, "陆小凤");
      37         System.out.println(res);
      38 
      39         // 5.获取私有的方法
      40         Method hello3 = clz.getDeclaredMethod("hello3", String.class, int.class);
      41         System.out.println(hello3);
      42 
      43         // 5.1设置忽略访问权限
      44         hello3.setAccessible(true);
      45     
      46 // 5.1执行方法
      47         Object res1 = hello3.invoke(p, "叶孤城", 30);
      48         System.out.println(res1);
      49 
      50         // 6.获取静态方法
      51         Method staticMethod = clz.getMethod("staticMethod", String.class);
      52 
      53         // 6.1执行静态方法
      54         staticMethod.invoke(null, "花满楼");
      55 
      56         // 7.获取有整数数组参数的方法
      57         Method method1 = clz.getMethod("method1", int[].class);
      58         method1.invoke(null, new Object[] {new int[] { 1, 2, 3, 4 }});
      59 
      60         // 8.获取有整数数组参数的方法
      61         /*
      62          * 如果反射传递的参数是引用类型,底层有一个拆箱的功能,会将数组的元素拆成一个个参数传递过来
      63          * 解决方案: 将数组外面在包装一层数组,如果拆箱一次,得到还是一个数组
      64          */
      65         Method method2 = clz.getMethod("method2", String[].class);
      66         method2.invoke(null,new Object[] {new String[] {"AA","BB","CC"}});
      67     }
      68 }

      6.2.3. 可变参数的方法执行

      如果方法中有可变参数(数组),如果反射传递的可变参数是引用类型底层有一个拆箱的功能,会将数组的元素拆成一个个参数传递过来

      解决方案: 将数组外面在包装一层数组,如果拆箱一次,得到还是一个数组

       1 package cn.sxt.reflect._03method;
       2 
       3 import java.util.Arrays;
       4 
       5 public class Person {
       6     
       7     public static void method1(int... intArr) {
       8         System.out.println(Arrays.toString(intArr));
       9     }
      10     
      11     public static void method2(String...strArr) {
      12         System.out.println(Arrays.toString(strArr));
      13     }
      14 }
       1         // 7.获取有整数数组参数的方法
       2         Method method1 = clz.getMethod("method1", int[].class);
       3         method1.invoke(null, new Object[] {new int[] { 1, 2, 3, 4 }});
       4 
       5         // 8.获取有整数数组参数的方法
       6         /*
       7          * 如果反射传递的参数是引用类型,底层有一个拆箱的功能,会将数组的元素拆成一个个参数传递过来
       8          * 解决方案: 将数组外面在包装一层数组,如果拆箱一次,得到还是一个数组
       9          */
      10         Method method2 = clz.getMethod("method2", String[].class);
      11         method2.invoke(null,new Object[] {new String[] {"AA","BB","CC"}});

      7. 操作字段(成员变量)-Field

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

      7.1. Class中获取字段方法

       Field[]

      getFields()

      获取当前Class所表示类中所有的public的字段,包括继承的字段.

       Field[]

      getDeclaredFields()

      获取当前Class所表示类中所有的字段,不包括继承的字段.

       Field

      getField(String name)

      获取当前Class所表示类中

       Field

      getDeclaredField(String name)

      :获取当前Class所表示类中该fieldName名字的字段,不包括继承的字段.

      7.2. Field操作设置值/获取值

      给某个类中的字段设置值和获取值:

      1,找到被操作字段所在类的字节码

      2,获取到该被操作的字段对象

      3,设置值/获取值

      Field类常用方法:

       void setXX(Object obj, XX value) :为基本类型字段设置值,XX表示基本数据类型

       void set(Object obj, Object value) :表示为引用类型字段设置值

        参数:

            obj:    表示字段底层所属对象,若该字段是static,该值应该设为null

            value:  表示将要设置的值

      -------------------------------------------------------------------------------------

       XX getXX(Object obj) :获取基本类型字段的值,XX表示基本数据类型

       Object get(Object obj) :表示获取引用类型字段的值

         参数:

            obj:    表示字段底层所属对象,若该字段是static,该值应该设为null

         返回:返回该字段的值.

      void

      set(Object obj, Object value) 

      设置引用类型的值,非基本数据类型

      Obj: 要设置值得对象

      Value : 要设置的值

       1 package cn.sxt.reflect._04Field;
       2 
       3 import static org.junit.Assert.*;
       4 
       5 import java.lang.reflect.Field;
       6 
       7 import org.junit.Test;
       8 
       9 public class FieldTest {
      10     
      11     @Test
      12     public void testName() throws Exception {
      13         //1.获取People字节码
      14         Class<People> clz = People.class;
      15         
      16         People p = clz.newInstance();
      17         
      18         //2.获取所有公共字段
      19         Field[] fields1 = clz.getFields();
      20         for (Field field : fields1) {
      21             System.out.println(field);
      22         }
      23         System.out.println("---------------------");
      24         //3.获取所有字段,和访问权限无关
      25         Field[] fields2 = clz.getDeclaredFields();
      26         for (Field field : fields2) {
      27             System.out.println(field);
      28         }
      29         System.out.println("---------------------");
      30         //3.获取指定的公共字段
      31         Field emialField = clz.getField("emial");
      32         System.out.println(emialField);
      33         
      34         //为字段设置值
      35         emialField.set(p, "zhagnsan@qq.com");
      36         System.out.println(p);
      37         //4.获取指定所有的字段,和访问权限无关
      38         Field nameFiled = clz.getDeclaredField("name");
      39         System.out.println(nameFiled);
      40         //设置忽略访问权限
      41         nameFiled.setAccessible(true);
      42         nameFiled.set(p, "张三");
      43         System.out.println(p);
      44         
      45         
      46         //5 获取age字段
      47         Field ageFile = clz.getDeclaredField("age");
      48         ageFile.setAccessible(true);
      49         //设置忽略访问权限
      50         ageFile.setInt(p, 18);
      51         System.out.println(p);
      52         
      53     }
      54 }

      8. Class的其他API方法

       1 //Class字节码的其他api
       2     @Test
       3     public void testName() throws Exception {
       4         
       5         //1.获取UserDaoImpl的字节码实例
       6         Class<?> clz = Class.forName("cn.sxt.reflect._05otherapi.UserDaoImpl");
       7         
       8         //2.获取所有的接口
       9         Class<?>[] interfaces = clz.getInterfaces();
      10         for (Class<?> intface : interfaces) {
      11             System.out.println(intface);
      12         }
      13         
      14         //3.获取全限定名
      15         System.out.println(clz.getName());
      16         
      17         //4.获取简单类名
      18         System.out.println(clz.getSimpleName());
      19         //5.获取包
      20         System.out.println(clz.getPackage().getName());
      21     }

      9. JavaBean

      问题: 什么是javaBean?

      答: JavaBean就是一个个Java类,在java中符合JavaBean特点类才叫做JavaBean

      9.1. JavaBean的三个特点

      1. JavaBean类的修饰符必须是public,也就是一个JavaBean必须是一个对应一个类文件
      2. JavaBean必须有无参数公共构造方法(以便于反射直接通过直接通过字节码实例创建对象)
      3. JavaBean中的成员变量/字段必须有get/set方法提供对应的属性

      9.1.1. JavaBean中的属性

      Java类有成员变量,成员变量绝对不是属性JavaBean中的属性是有get/set方法确定的

      9.1.1.1. Get方法确定属性

      public String getName() {

      return name;

      }

      属性确定规则 : get方法去掉 get前缀 ,剩余部分 首字母小写

      属性名称 : name

      9.1.1.2. Set方法确定属性

      public void setName(String name) {

      this.name = name;

      }

      属性确定规则 : set方法去掉 set前缀 ,剩余部分 首字母小写

      属性名称 : name

      如果一个成员变量get/set方法都有确定的属性就只有一个

      问题 get/set方法确定确定的属性一般不就和成员变量一样啊,为什么要有属性了

      答: 一般情况下,Eclipse工具有自动生成get/set方法的功能,确定的JavaBean的属性只是恰好和成员变量相同,但是成员变量不是属性

      如下特殊性情况,属性和成员变量名称不同

      1 //Filed
      2 private String firstName;
      3 //Filed
      4 private String lastName;
      5     
      6 //属性 : fullName
      7 public String getFullName() {
      8 return this.firstName + this.lastName;
      9 }

      10. 小结

      1.理解反射概念?反射能干啥?

      (1) 反射: jvm运行阶段,动态的获取类的信息(字节码实例,构造器,方法,字段),动态进行对象的创建,方法执行,字段操作。

      2.反射的常用类

      (1) Class :所有类的字节码实例的描述

      (2) Constructor :构造器

      (3) Method :方法

      (4) Field :字段

      3.JDBC+反射 代码封装

      (1) 通用 crud 方法

      ① 思路清晰

  • 相关阅读:
    TortoiseGit状态图标不能正常显示的解决办法
    git安装及命令使用和github网站
    git commit 代码时提示: Warning: Your console font probably doesn‘t support Unicode.
    msysgit之git bash方便的复制粘贴,默认工作目录,窗口大小,提高速度等小窍门
    Apache与Tomcat的整合
    Android UI效果实现——Activity滑动退出效果
    多平台Native库打入JAR包发布实战
    WPF使用Win32进行GDI截屏笔记
    使用Gradle将Maven库打包成Jar
    【转载/修改】ScrollLayout代码修正,追加模仿viewpager滚动速度
  • 原文地址:https://www.cnblogs.com/qq308015824/p/10923386.html
Copyright © 2011-2022 走看看