Java基础知识(19)- Java 反射机制 (一) | 反射机制简介、通过反射访问构造方法、通过反射访问成员变量

 

Java 反射机制 (Reflection)是 Java 语言的一个重要特性,在服务器程序和中间件程序中广泛运用。在服务器端,往往需要根据客户的请求,动态调用某一个对象的特定方法。

在 ORM 中间件的实现中,运用 Java 反射机制可以读取任意一个 JavaBean 的所有属性,或者给这些属性赋值。

 

1. 反射机制简介

    1) 编译期和运行期

        在了解 Java 反射机制前,先了解以下两个概念。

        编译期:
        
            是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中就是把 Java 代码编成 class 文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作,比如检查错误。

        运行期:
        
            是指把编译后的文件(.class)交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。

    2) 反射机制
    
        反射机制是在运行期,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。
    
        简单来说,反射机制指的是程序在运行时能够获取自身的信息。一个类有多个组成部分,例如:成员变量、方法、构造方法等,反射就是加载类,并解剖出类的各个组成部分。

        正常方式:

            引入需要的包类名称 -> 通过 new 实例化 -> 取得实例化对象
    
        反射方式:

            实例化对象 -> getClass 方法 -> 得到完整的包类名称

    3) 反射机制的特点

        反射机制主要提供以下功能:

            (1) 在运行时判断任意一个对象所属的类。
            (2) 在运行时构造任意一个类的对象。
            (3) 在运行时判断任意一个类所具有的成员变量和方法。
            (4) 在运行时调用任意一个对象的方法。
            (5) 生成动态代理。

        反射机制优点:

            (1) 能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性。
            (2) 与 Java 动态编译相结合,可以实现无比强大的功能。
            (3) 对于 Java 这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

        反射机制缺点:

            (1) 反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
            (2) 反射调用方法时可以忽略权限检查,获取这个类的私有方法和属性,因此可能会破坏类的封装性而导致安全问题。

        Java 反射机制在一般的 Java 应用开发中很少使用,即便是 Java EE 阶段也很少使用。


    4) 反射机制的类

        Java 反射机制的类位于 java.lang.reflect 包中,java.lang.Class 类是 Java 反射机制 API 中的核心类。

        (1) java.lang.Class 类

            java.lang.Class 类是实现反射的关键所在,Class 类的一个实例表示 Java 的一种数据类型,包括类、接口、枚举、注解(Annotation)、数组、基本数据类型和 void。
        
            Class 类没有公有的构造方法,Class 实例是由 JVM 在类加载时自动创建的。

            在程序代码中获得 Class 实例可以通过如下代码实现:

                // 1. 通过类型class静态变量
                Class clz1 = String.class;
                String str = "Hello";

                // 2. 通过对象的getClass()方法
                Class clz2 = str.getClass();

            每一种类型包括类和接口等,都有一个 class 静态变量可以获得 Class 实例。另外,每一个对象都有 getClass() 方法可以获得 Class 实例,该方法是由 Object 类提供的实例方法。

            反射可访问的常用信息:

类型 访问方法 返回值类型 说明
包路径 getPackage() Package 对象 获取该类的存放路径
类名称  getName() String 对象 获取该类的名称
继承类          getSuperclass() Class 对象                    获取该类继承的类
实现接口        getlnterfaces() Class 型数组 获取该类实现的所有接口
构造方法  

getConstructors()

getDeclaredContruectors()

Constructor 型数组

Constructor 对象    

获取所有权限为 public 的构造方法

获取当前对象的所有构造方法

方法    

getMethods()

getDeclaredMethods()

Method 型数组       

Method 对象       

获取所有权限为 public 的方法

获取当前对象的所有方法

成员变量    

getFields()

getDeclareFileds()

Field 型数组 

Field 对象            

获取所有权限为 public 的成员变量

获取当前对象的所有成员变量

内部类

getClasses()

getDeclaredClasses()

Class 型数组 

Class 型数组       

获取所有权限为 public 的内部类

获取所有内部类  

内部类的声明类 getDeclaringClass() Class 对象 如果该类为内部类,则返回它的成员类,否则返回 null


            如上表所示,在调用 getFields() 和 getMethods() 方法时将会依次获取权限为 public 的字段和变量,然后将包含从超类中继承到的成员变量和方法。而通过 getDeclareFields() 和 getDeclareMethod() 只是获取在本类中定义的成员变量和方法。

        (2) java.lang.reflect 包

            包提供了反射中用到类,主要的类说明如下:

                 a) Constructor 类:提供类的构造方法信息。
                 b) Field 类:提供类或接口中成员变量信息。
                 c) Method 类:提供类或接口成员方法信息。
                 d) Array 类:提供了动态创建和访问 Java 数组的方法。
                 e) Modifier 类:提供类和成员访问修饰符信息。


2. 通过反射访问构造方法

    为了能够动态获取对象构造方法的信息,首先需要通过下列方法之一创建一个 Constructor 类型的对象或者数组。

        getConstructors()
        getConstructor(Class<?>…parameterTypes)
        getDeclaredConstructors()
        getDeclaredConstructor(Class<?>...parameterTypes)

    如果是访问指定的构造方法,需要根据该构造方法的入口参数的类型来访问。例如,访问一个入口参数类型依次为 int 和 String 类型的构造方法,下面的两种方式均可以实现。

        objectClass.getDeclaredConstructor(int.class,String.class);
        objectClass.getDeclaredConstructor(new Class[]{int.class,String.class});

    创建的每个 Constructor 对象表示一个构造方法,然后利用 Constructor 对象的方法操作构造方法。

        表1 Constructor类的常用方法

方法名称 说明
isVarArgs()  查看该构造方法是否允许带可变数量的参数,如果允许,返回 true,否则返回false
getParameterTypes()  按照声明顺序以 Class 数组的形式获取该构造方法各个参数的类型
getExceptionTypes()  以 Class 数组的形式获取该构造方法可能抛出的异常类型
newInstance(Object … initargs) 通过该构造方法利用指定参数创建一个该类型的对象,如果未设置参数则表示采用默认无参的构造方法
setAccessiable(boolean flag)    如果该构造方法的权限为 private,默认为不允许通过反射利用 netlnstance() 方法创建对象。如果先执行该方法,并将入口参数设置为 true,则允许创建对象
getModifiers() 获得可以解析出该构造方法所采用修饰符的整数


    通过 java.lang.reflect.Modifier 类可以解析出 getMocMers() 方法的返回值所表示的修饰符信息。在该类中提供了一系列用来解析的静态方法,既可以查看是否被指定的修饰符修饰,还可以字符串的形式获得所有修饰符。

        表2 Modifier类的常用方法

静态方法名称                    说明
isStatic(int mod)        如果使用 static 修饰符修饰则返回 true,否则返回 false
isPublic(int mod)       如果使用 public 修饰符修饰则返回 true,否则返回 false
isProtected(int mod)    如果使用 protected 修饰符修饰则返回 true,否则返回 false
isPrivate(int mod)        如果使用 private 修饰符修饰则返回 true,否则返回 false
isFinal(int mod)        如果使用 final 修饰符修饰则返回 true,否则返回 false
toString(int mod)        以字符串形式返回所有修饰符


实例:

 1        import java.lang.reflect.Constructor;
 2 
 3         public class App {
 4             public static void main( String[] args ) {
 5 
 6                 Class book = Book.class;
 7 
 8                 // 通过反射访问构造方法
 9                 Constructor[] declaredCon = book.getDeclaredConstructors();
10                 for (int i = 0; i < declaredCon.length; i++) {
11 
12                     Constructor con = declaredCon[i];
13 
14                     System.out.println("构造方法名称 -> " + con.getName());
15 
16                     System.out.print("参数类型 -> ( ");
17                     Class[] parameterTypes = con.getParameterTypes();
18                     for (int j = 0; j < parameterTypes.length; j++) {
19                         System.out.print(parameterTypes[j]);
20                         if (j < parameterTypes.length-1)
21                             System.out.print(", ");
22                     }
23                     System.out.print(" )\n");
24 
25                     System.out.println("是否带可变数量参数 -> " + con.isVarArgs());
26 
27                     /*
28                     * 构造方法的访问权限为 private,会拋出异常;
29                     * 或调用 setAccessible(true) 设置允许访问 private 成员
30                     */
31                     //con.setAccessible(true);
32 
33                     try {
34                         Book book2 = null;
35 
36                         if (parameterTypes.length == 0) {
37                             // 通过执行默认构造方法
38                             book2 = (Book) con.newInstance();
39                         } else if (parameterTypes.length == 2) {
40                             // 通过执行带两个参数的构造方法
41                             book2 = (Book) con.newInstance("Java Reflection", 101);
42                         } else {
43                             // 通过执行可变数量参数的构造方法
44                             Object[] parameters = new Object[] { new String[] { "Java Reflection 2", "102" } };
45                             book2 = (Book) con.newInstance(parameters);
46                         }
47                         book2.print();
48 
49                     } catch (Exception e) {
50                         //e.printStackTrace();
51                         System.out.println("通过该构造方法实例化失败:error -> " + e.getMessage());
52                     }
53 
54                     System.out.println("----------------------------------------------");
55                 }
56 
57             }
58         }
59 
60         class Book {
61             String name;
62             int price;
63 
64             private Book() {
65             }
66 
67             protected Book(String name, int price) {
68                 this.name = name;
69                 this.price = price;
70             }
71 
72             // 带可变参数的构造方法
73             public Book(String... args) throws NumberFormatException {
74                 if (0 < args.length)
75                     name = String.valueOf(args[0]);
76                 if (1 < args.length)
77                     price = Integer.valueOf(args[1]);
78             }
79 
80             public void print() {
81                 System.out.println("name: " + name + ", price: " + price);
82             }
83         }


    输出:

        构造方法名称 -> com.example.Book
        参数类型 -> ( class [Ljava.lang.String; )
        是否带可变数量参数 -> true
        name: Java Reflection 2, price: 102
        ----------------------------------------------
        构造方法名称 -> com.example.Book
        参数类型 -> ( class java.lang.String, int )
        是否带可变数量参数 -> false
        name: Java Reflection, price: 101
        ----------------------------------------------
        构造方法名称 -> com.example.Book
        参数类型 -> (  )
        是否带可变数量参数 -> false
        通过该构造方法实例化失败:error -> Class com.example.ReflectionApp can not access a member of class com.example.Book with modifiers "private"


3. 通过反射访问成员变量

    通过下列任意一个方法访问成员变量时将返回 Field 类型的对象或数组。

        getFields()
        getField(String name)
        getDeclaredFields()
        getDeclaredField(String name)

    上述方法返回的 Field 对象代表一个成员变量。例如,要访问一个名称为 price 的成员变量,示例代码如下:

        object.getDeciaredField("price");

    Field 类的常用方法如表所示

方法名称 说明
getName() 获得该成员变量的名称
getType() 获取表示该成员变量的 Class 对象
get(Object obj) 获得指定对象 obj 中成员变量的值,返回值为 Object 类型
set(Object obj, Object value) 将指定对象 obj 中成员变量的值设置为 value
getlnt(0bject obj) 获得指定对象 obj 中成员类型为 int 的成员变量的值
setlnt(0bject obj, int i) 将指定对象 obj 中成员变量的值设置为 i
setFloat(Object obj, float f) 将指定对象 obj 中成员变量的值设置为 f
getBoolean(Object obj) 获得指定对象 obj 中成员类型为 boolean 的成员变量的值
setBoolean(Object obj, boolean b) 将指定对象 obj 中成员变量的值设置为 b
getFloat(Object obj) 获得指定对象 obj 中成员类型为 float 的成员变量的值
setAccessible(boolean flag) 此方法可以设置是否忽略权限直接访问 private 等私有权限的成员变量
getModifiers() 获得可以解析出该方法所采用修饰符的整数


实例:

 1         import java.lang.reflect.Field;
 2 
 3         public class App {
 4             public static void main( String[] args ) {
 5 
 6                 // 通过反射访问成员变量
 7                 //BookVariable bookVariable = new BookVariable();
 8                 //Class bookVariableClass = bookVariable.getClass();
 9 
10                 // 这里使用 Class 的 newInstance() 代替 new;
11                 // Class 的 newInstance() 只能调用无参的构造函数。
12                 Class bookVariableClass = null;
13                 BookVariable bookVariable = null;
14                 try {
15                     bookVariableClass = Class.forName("com.example.BookVariable");
16                     bookVariable = (BookVariable) bookVariableClass.newInstance();
17                 } catch (Exception e) {
18                     //e.printStackTrace();
19                     System.out.println("Create BookVariable instance failed:  " + e.getMessage());
20                     return;
21                 }
22 
23                 Field[] declaredFields = bookVariableClass.getDeclaredFields();
24 
25                 // 遍历所有的成员
26                 for (int i = 0;i < declaredFields.length;i++) {
27 
28                     Field field = declaredFields[i];
29 
30                     System.out.println("变量名称 -> " + field.getName());
31                     Class fieldType = field.getType();
32                     System.out.println("变量类型 -> " + fieldType);
33 
34                     // 变量的访问权限为 private,会拋出异常;
35                     // 或调用 setAccessible(true) 设置允许访问 private 成员
36                     field.setAccessible(true);
37 
38                     try {
39                         System.out.println("变量默认值 -> " + field.get(bookVariable));
40 
41                         if(fieldType.equals(int.class)) {
42                             field.setInt(bookVariable, 99);
43                             System.out.println("setInt(99) = " + field.get(bookVariable));
44                         } else if(fieldType.equals(float.class)) {
45                             field.setFloat(bookVariable, 21.02f);
46                             System.out.println("setFloat(21.02f) = " + field.get(bookVariable));
47                         } else if(fieldType.equals(boolean.class)) {
48                             field.setBoolean(bookVariable, true);
49                             System.out.println("setBoolean(true) = " + field.get(bookVariable));
50                         } else {
51                             field.set(bookVariable, "Java Variable");
52                             System.out.println("set('Java Variable') = " + field.get(bookVariable));
53                         }
54                     } catch (Exception e) {
55                         //e.printStackTrace();
56                         System.out.println("访问成员变量失败:error -> " + e.getMessage());
57                     }
58 
59                     System.out.println("----------------------------------------------");
60                 }
61                 bookVariable.print();
62             }
63         }    
64 
65         class BookVariable {
66             String name;
67             public int number;
68             private float price;
69             protected boolean isOpen;
70 
71             public void print() {
72                 System.out.println("name: " + name + ", number: " + number + ", price: " + price + ", isOpen: " + isOpen);
73             }
74         }


    输出:

        变量名称 -> name
        变量类型 -> class java.lang.String
        变量默认值 -> null
        set('Java Variable') = Java Variable
        ----------------------------------------------
        变量名称 -> number
        变量类型 -> int
        变量默认值 -> 0
        setInt(99) = 99
        ----------------------------------------------
        变量名称 -> price
        变量类型 -> float
        变量默认值 -> 0.0
        setFloat(21.02f) = 21.02
        ----------------------------------------------
        变量名称 -> isOpen
        变量类型 -> boolean
        变量默认值 -> false
        setBoolean(true) = true
        ----------------------------------------------
        name: Java Variable, number: 99, price: 21.02, isOpen: true

posted @ 2022-03-02 17:38  垄山小站  阅读(235)  评论(0)    收藏  举报