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 方法

      ① 思路清晰

  • 相关阅读:
    PostgreSQL中的partition-wise join
    Partition-wise join
    外观模式 门面模式 Facade 结构型 设计模式(十三)
    桥接模式 桥梁模式 bridge 结构型 设计模式(十二)
    组合模式 合成模式 COMPOSITE 结构型 设计模式(十一)
    创建型设计模式对比总结 设计模式(八)
    原型模式 prototype 创建型 设计模式(七)
    单例模式 创建型 设计模式(六)
    建造者模式 生成器模式 创建型 设计模式(五)
    抽象工厂模式 创建型 设计模式(四)
  • 原文地址:https://www.cnblogs.com/qq308015824/p/10923386.html
Copyright © 2011-2022 走看看