java反射

Java 反射机制是 Java 语言的一个重要特性。在学习 Java 反射机制前,大家应该先了解两个概念,编译期和运行期。

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

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

Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。在 Java 中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。

Java 反射机制在服务器程序和中间件程序中得到了广泛运用。在服务器端,往往需要根据客户的请求,动态调用某一个对象的特定方法。此外,在 ORM 中间件的实现中,运用 Java 反射机制可以读取任意一个 JavaBean 的所有属性,或者给这些属性赋值。
 


Java 反射机制主要提供了以下功能,这些功能都位于java.lang.reflect包。

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


要想知道一个类的属性和方法,必须先获取到该类的字节码文件对象。获取类的信息时,使用的就是 Class 类中的方法。所以先要获取到每一个字节码文件(.class)对应的 Class 类型的对象.

众所周知,所有 Java 类均继承了 Object 类,在 Object 类中定义了一个 getClass() 方法,该方法返回同一个类型为 Class 的对象。例如,下面的示例代码:

 
  1. Class labelCls = label1.getClass(); // label1为 JLabel 类的对象


利用 Class 类的对象 labelCls 可以访问 labelCls 对象的描述信息、JLabel 类的信息以及基类 Object 的信息。表 1 列出了通过反射可以访问的信息。
 

表 1 反射可访问的常用信息
类型访问方法返回值类型说明
包路径getPackage()Package 对象获取该类的存放路径
类名称getName()String 对象获取该类的名称
继承类getSuperclass()Class 对象获取该类继承的类
实现接口getlnterfaces()Class 型数组获取该类实现的所有接口
构造方法getConstructors()Constructor 型数组获取所有权限为 public 的构造方法
getDeclaredContruectors()Constructor 对象获取当前对象的所有构造方法
方法getMethods()Methods 型数组获取所有权限为 public 的方法
getDeclaredMethods()Methods 对象获取当前对象的所有方法
成员变量getFields()Field 型数组获取所有权限为 public 的成员变量
getDeclareFileds()Field 对象获取当前对象的所有成员变量
内部类getClasses()Class 型数组获取所有权限为 public 的内部类
getDeclaredClasses()Class 型数组获取所有内部类
内部类的声明类getDeclaringClass()Class 对象如果该类为内部类,则返回它的成员类,否则返回 null


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

当使用构造方法创建对象时,构造方法可以是有参数的,也可以是无参数的。同样,通过反射创建对象的方式也有两种,就是调用有参和无参构造方法,下面针对这两种方式进行详细讲解,具体如下:

通过反射创建对象

1、通过无参构造实例化对象

如果想通过Class类本身实例化其他类的对象,那么就可以使用newInstance()方法,但是必须要保证被实例化的类中存在一个无参构造方法。接下来通过一个案例来演示如何通过无参构造实例化对象,如例1所示。

例1 ReflectDemo01.java

 1    package cn.itcast.chapter08.reflection;
 2    class Person {
 3        private String name;    //定义属性name,表示姓名
 4        private int age;        //定义属性age,表示年龄
 5        public String getName() {
 6            return name;
 7        }
 8        public void setName(String name) { //设置name属性
 9            this.name = name;
 10        }
 11        public int getAge() {
 12            return age;
 13        }
 14        public void setAge(int age) { //设置age属性
 15            this.age = age;
 16        }
 17        public String toString() {    //重写toString()方法
 18            return "姓名:"+this.name+",年龄:"+this.age;
 19        }
 20    }
 21    public class ReflectDemo01 {
 22        public static void main(String[] args) throws Exception{
 23            // 传入要实例化类的完整“包.类”名称
 24            Class clazz = Class.forName("cn.itcast.chapter08.reflection.Person");
 25            // 实例化Person对象
 26            Person p = (Person) clazz.newInstance();
 27            p.setName("李芳");
 28            p.setAge(18);
 29            System.out.println(p);
 30        }
 31    }

运行结果如图1所示。

图1 运行结果

在例1中,第24行代码通过Class.forName()方法实例化Class对象,向方法中传入的完整“包.类”名称的参数,第26行代码直接调用newInstance()方法实现对象的实例化操作。从图1中可以看出,程序输出了p对象的信息。

2、通过有参构造实例化对象

当通过有参构造方法实例化对象时,需要分为三个步骤完成,具体如下:

(1)通过Class类的getConstructors()方法取得本类中的全部构造方法。

(2)向构造方法中传递一个对象数组进去,里面包含了构造方法中所需的各个参数。

(3)通过Constructor类实例化对象。

需要注意的是,Constructor类表示的是构造方法,该类有很多常用的方法,具体如表1所示。

表1 Constructor类的常用方法

方法声明功能描述
int getModifiers()获取构造方法的修饰符。
String getName()获得构造方法的名称。
Class<?>[] getParameterTypes()获取构造方法中参数的类型。
T newInstance(Object... initargs)向构造方法中传递参数,实例化对象。

接下来,通过一个案例来演示如何通过有参构造实例化对象,如例2所示。

例2 InstanceDemo02.java

 1    package cn.itcast.chapter08.reflection;
 2    import java.lang.reflect.Constructor;
 3    class Person {
 4        private String name;    //定义属性name,表示姓名
 5        private int age;        //定义属性age,表示年龄
 6        public Person(String name, int age) {
 7            this.name = name;
 8            this.age = age;
 9        }
 10        public String getName() {
 11            return name;
 12        }
 13        public void setName(String name) { //设置name属性
 14            this.name = name;
 15        }
 16        public int getAge() {
 17            return age;
 18        }
 19        public void setAge(int age) { //设置age属性
 20            this.age = age;
 21        }
 22        public String toString() {    //重写toString()方法
 23            return "姓名:"+this.name+",年龄:"+this.age;
 24        }
 25    }
 26    public class ReflectDemo02 {
 27        public static void main(String[] args) throws Exception{
 28             //传入要实例化类的完整“包.类”名称
 29            Class clazz = Class.forName("cn.itcast.chapter08.reflection.Person");
 30            //通过反射获取全部构造方法
 31            Constructor cons[] = clazz.getConstructors();
 32             //向构造方法中传递参数,并实例化Person对象
 33            //因为只有一个构造方法,所以数组小标为0
 34            Person p = (Person) cons[0].newInstance("李芳",30);
 35            System.out.println(p);
 36        }
 37    }

运行结果如图2所示。

图2 运行结果

在例2中,第29行代码用于获取Person类的Class实例,第31行代码通过Class实例取得了Person类中的全部构造方法,并以对象数组的形式返回,第34行代码用于向构造方法中传递参数,并实例化Person对象,由于在Person类中只有一个构造方法,所以直接取出对象数组中的第一个元素即可。

需要注意的是,在实例化Person对象时,还必须考虑到构造方法中参数的类型顺序,第一个参数的类型为String,第二参数的类型为Integer。

 通过反射访问属性

通过反射不仅可以创建对象,还可以访问属性。在反射机制中,属性的操作是通过Filed类实现的,它提供的set()和get()方法分别用于设置和获取属性。需要注意的是,如果访问的属性是私有的,则需要在使用set()或get()方法前,使用Field类中的setAccessible(true)方法将需要操作的属性设置成可以可被外界访问的。

为了帮助大家更好地理解如何通过反射访问属性,接下来,通过一个案例来演示,如例1所示。

例1 ReflectDemo03.java

 1    package cn.itcast.chapter08.reflection;
 2    import java.lang.reflect.Field;
 3    class Person {
 4        private String name;    //定义属性name,表示姓名
 5        private int age;        //定义属性age,表示年龄
 6        public String toString() {    //重写toString()方法
 7            return "姓名:"+this.name+",年龄:"+this.age;
 8        }
 9    }
 10    public class ReflectDemo03 {
 11        public static void main(String[] args) throws Exception{
 12            //获取Person类对应的Class对象
 13            Class clazz = Class.forName("cn.itcast.chapter08.reflection.Person");
 14            //创建一个Person对象
 15            Object p = clazz.newInstance();
 16            //获取Person类中指定名称的属性
 17            Field nameField = clazz.getDeclaredField("name");
 18            //设置通过反射访问该属性时取消权限检查
 19            nameField.setAccessible(true);
 20            //调用set方法为p对象的指定属性赋值
 21            nameField.set(p, "李四");
 22            //获取Person类中指定名称的属性
 23            Field ageField = clazz.getDeclaredField("age");
 24            //设置通过反射访问该属性时取消权限检查
 25            ageField.setAccessible(true);
 26            //调用set方法为p对象的指定属性赋值
 27            ageField.set(p, 20);
 28            System.out.println(p);
 29        }
 30    }

运行结果如图1所示。

图1 运行结果

在例1中,第3~9行代码定义了一个Person类,类中定义了两个私有属性name和age。第10~30行代码定义了ReflectDemo03类,其中第19行代码用于取消访问属性时的权限检查,第20行代码用于调用set方法为对象的指定属性赋值。

通过反射调用方法 

当获得某个类对应的Class对象后,就可以通过Class对象的getMethods()方法或getMethod()方法获取全部方法或者指定方法,getMethod()方法和getMethods()这两个方法的返回值,分别是Method对象和Method对象数组。每个Method对象都对应一个方法,程序可以通过获取Method对象来调用对应的方法。在Method里包含一个invoke()方法,该方法的定义具体如下:

public Object invoke(Object obj, Object... args)

在上述方法定义中,参数obj是该方法最主要的参数,它后面的参数args是一个相当于数组的可变参数,用来接收传入的实参。

为了帮助大家更好地学习如何通过反射调用方法,接下来通过一个案例来演示,如例1所示。

例1 ReflectDemo04.java

 1    package cn.itcast.chapter08.reflection;
 2    import java.lang.reflect.Method;
 3    class Person {
 4        private String name;    //定义属性name,表示姓名
 5        private int age;        //定义属性age,表示年龄
 6        public String getName() {
 7            return name;
 8        }
 9        public void setName(String name) { //设置name属性
 10            this.name = name;
 11        }
 12        public int getAge() {
 13            return age;
 14        }
 15        public void setAge(int age) { //设置age属性
 16            this.age = age;
 17        }
 18        public String sayHello(String name,int age) {    //定义sayHello()方法
 19            return "大家好,我是"+name+",今年"+age+"岁!";
 20        }
 21    }
 22    public class ReflectDemo04 {
 23        public static void main(String[] args) throws Exception{
 24            // 实例化Class对象
 25            Class clazz = Class.forName("cn.itcast.chapter08.reflection.Person");
 26            // 获取Person类中名为sayHello的方法,该方法有两个形参,分别为String类型和int类型
 27            Method md = clazz.getMethod("sayHello", String.class, int.class);
 28            // 调用sayHello()方法
 29            String result = (String) md.invoke(clazz.newInstance(), "张三",35);
 30            System.out.println(result);
 31        }
 32    }

运行结果如图1所示。

图1 运行结果

在例1中,第25行代码用于获取Person类的Class实例,第27行代码用于返回sayHello()方法所对应的Method对象,由于sayHello()方法本身要接收两个参数,因此在使用Class实例的getMethod()方法时,除了要指定方法名称外,也需要指定方法的参数类型。在第29行代码中,通过Method对象的invoke()方法实现sayHello()方法的调用,并接收sayHello()方法所传入的实参。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值