Day24:反射机制


任务 3使用反射机制
关键步骤如下。
➢使用反射获取类的信息。
➢使用反射创建 对象。
➢使用反射访问属性和方法。
➢使用Array动态创建和访问数组。

认识反射

1.反射机制

java 的反射机制是 java的特性之一,反射机制是构建柜架技 术的基础所在。灵话掌握 Java反射机制,对以后学习框架技术将有很大的帮助。

Java反射机制是指在运行状态中,动态获取信息以及动态调用对象方法的功能。
反射有3个动态性质。
➢运行时生成对象实例。
➢运行期间调用方法。
➢运行时更改属性。
要想Java程序能够运行,Java 类必须被Java虚拟机加载。运行的程序都是在编译时就已经加载了所需要的类。
Java程序执行过程:Person.java→编译器→Person.class→Java虚拟机→运行程序

Java反射机制在编译时并不确定是哪个类被加载了,而是在程序运行时才加载、探知、使用,这样的特点就是反射。这类似于光学中的反射概念。所以把Java的这种机制称为反射机制。在计算机科学领域,反射是指一类应用, 它们能够自描述和自控制。
Java反射?→编译器⬅➡运行程序

Java反射机制能够知道类的基本结构,这种对Java类结构探知的能力,称为Java类的“自审”。
通过Java反射,可以实现以下功能。
➢在运行时判断任意一 个对象所属的类。
➢在运行时构造任意一个类的对象。
➢在运行时判断任意一个类所具有的方法和属性。
➢在运行时调用任意一个对象的方法。

2. Java反射常用API

使用java反射技术常用的类如下。
➢Class类:反射的核心类,反射所有的操作都是围绕该类来生成的。通过Class类,
可以获取类的属性、方法等内容信息。
➢Field类: 表示类的属性,可以获取和设置类中属性的值。
➢Method类: 表示类的方法,可以用来获取类中方法的信息,或者执行方法。
➢Constructor 类:表示类的构造方法。

在Java程序中使用反射的基本步骤如下。
(1)导入java.lang.reflect.*.
(2)获得需要操作的类的Java.lang.Class对象。
(3)调用Class的方法获取Field、Method 等对象。
(4)使用反射API进行操作。

反射的应用

1.获取类的信息

通过反射获取类的信息分为两步,首先获取Class对象,然后通过Class对象获取信息。

(1)获取Class对象。

每个类被加载后,系统就会为该类生成一个对应的Class对象,通过该Class对象就可以访问Java虚拟机中的这个类。Java 程序中获得Class对象通常有如下3种方式。
1)调用对象的getClass()方法
getClass()方法是java.lang.Object 类中的一个方法, 所有的Java对象都可以调用该方法,该方法会返回该对象所属类对应的Class 对象。使用方式如以下代码所示:

Student p=new Student();
//Student为自定义的学生类型
Class cla=p.getClass();
//cla为class对象

2)调用类的class属性
调用某个类的class属性可获取该类对应的Class对象,这种方式需要在编译期间就知道类的名称。使用的方式如以下代码所示。

Class cla=Student.class;
//Student 为自定义的学生类型

上述代码中,Student.class将会返回Student类对应的Class对象。

3)使用Class类的forName()静态方法
使用Class类的forName()静态方法也可以获取该类对应的Class对象。该方法需要传入字符串参数,该字符串参数的值是某个类的全名,即要在类名前添加完整的包名。

Class cla=Class.forName('"com.pb.jadv.reflection.Student"); //正确
Class cla=Class.forName( "Student "); //错误

在上述代码中,如果传入的字符串不是类的全名,就会抛出一个ClassNotFoundException异常.

后两种方式都是直接根据类来获取该类的Class对象,相比之下调用某个类的class属性来获取该类对应的Class 对象这种方式更有优势,原因为如下两点。
➢代码更安全,程序在编译阶段就可以检查需要访问的Class对象是否存在。
➢程序性能更高,因为这种方式无须调用方法,所以性能更好。
因此,大部分时候都应该使用调用某个类的class属性的方式来获取指定类的Class对象。

(2)从Class对象获取信息。

在获得了某个类所对应的Class对象之后,程序就可以调用Class对象的方法来获取该类的详细信息。Class类提供了大量实例方法来获取Class对象所对应类的详细信息。
1)访问Class对应的类所包含的构造方法
访问Class对应的类所包含的构造方法的常用方法如表3-8所示。
表3-8访问类包含 的构造方法的常用方法

方法 说明
Constructor getConstructor(Class[] params) 返回此Class对象所包含的类的指定的public构造方法,params参数是按声明顺序指定该方法参数类型的Class对象的一 个数组。构造方法的参数类型与params所指定的参数类型匹配。例如:Constructor co=C.getConstructor(String.class,List.class);//c为某Class对象
Constructor[ ]getConstructors() 返回Class对象所包含的类的所有public构造方法
Constructor getDeclaredConstructor (Class[] params) 返回CIass对象所包含的类的指定构造方法,与构造方法的访问级别无关
Constructor[] getDeclaredConstructors() 返回此Class对象所包含的类的所有构造方法,与构造方法的访问级别无关

2)访问Class对应的类所包含的方法
访问Class对应的类所包含的方法的的常用方法如表3-9所示。

表3-9访问类包含的方法的常用方法

方法 说明
Method getMethod(String name,Class[] params) 返回此Class对象所包含的类的指定的public方法, name参数用于指定方法名称,params参数是按声明顺序标志该方法参数类 型的Class对象的一个数组。例如:c.getMethod(“info”. String.class);//为某Class对象c.getMethoud(“info” String class,Integer.class);
Method[] getMethods() 返回此Class对象所包含的类的所有public方法
Method getDeclaredMethod(String name,Class[]params) 返回此Clas对象,所包含的类的指定方法,与方法的访问级别无关
Method[] getDeclaredMethods() 返回此Class对象所包含的类的全部方法,与方法的访问级别无关

3)访问Class对应的类所包含的属性
访问Class对应的类所包含的属性的常用方法如表3-10所示。
表3-10访问类 包含的属性的常用方法

方法 说明
Field getField(String name) 返回此Class对象所包含的类的指定的public属性,name参数用于指定属性性名称。例如:c.getField(“age”); //c为某Class对象, age为 属性名
Field[] getFields() 返回此Class对象所包含的类的所有public属性
Field getDeclaredField(String name) 返回此Class对象所包含的类的指定属性,与属性的访问级别无关,name表示属性名称
Field[] getDeclaredFields() 返回此Class对象所包含的类的全部属性,与属性的访问级别无关

4)访问Class对应的类所包含的注解
访问Class对应的类所包含的注解的常用方法如表3-11所示。
表3-11访问类包含 的注解的常用方法

方法 说明
< A extends Annotation> A getAnnotation(Class < A> annotationClass) 试图获取 该Class对象所表示类上指定类型的注解,如果该类型的注解不存在则返回null.其中annotationClass参数对应于注解类型的Class对象
Annotation[] getAnnotations() 返回此类上存在的所有注解
Annotation[] getDeclaredAnnoations() 返回直接存在 于此类上的所有注解

5)访问Class对应的类的其他信息
访问Class对应的类的其他信息的常用方法如表3-12所示。
表3-12访问类信息 的其他常用方法

方法 说明
Class[] getDeclaredClasses() 返回该Class对象所对应类里包含的全部内部类
Class[] getDeclaringClass() 访问Class对应的类所在的外部类
Class[] getInterfaces() 返回该Class对象对应类所实现的全部接口
int getModifiers() 返回此类或接口的所有修饰符,返回的修饰符由public、 protected、private、final、 static和abstract等 对应的常量组成,返回的整数应使用Modifer工具类的方法来解码,才可以获取真实的修饰符
Package getPackage() 获取此类的包
String getName() 以字符串形式返回此Class对象所表示的类的名称
String getSimpleName() 以字符串形式返回此Class对象所表示的类的简称
Class getSuperclass() 返回该Class所表示的类的超类对应的Class对象

Class对象可以获得该类里的成员,包括方法、构造方法及属性。其中方法由Method对象表示,构造方法由Constructor 对象表示,属性由Field对象表示。

Method、Constructor、 Field 这3个类都定义在java.lang.reflect包下,并实现了java.lang.reflect.Member接口,程序可以通过Method对象来执行对应的方法,通过Constructor对象来调用相应的构造方法创建对象,通过Field对象直接访问并修改对象的属性值。

2.创建对象

通过反射来创建对象有如下两种方式。
➢使用Class对象的newInstance()方法创建对象。
➢使用Constructor对象创建对象。
使用Class对象的newInstance()方法来创建该Class对象对应类的实例。这种方式要求该Class对象的对应类有默认构造方法,而执行newInstance()方法时实际上是利用默认构造方法来创建该类的实例。而使用Constructor 对象创建对象,要先使用Class对象获取指定的Constructor 对象,再调用Constructor 对象的newInstance()方法创建该Class对象对应类的实例。通过这种方式可以选择使用某个类的指定构造方法来创建实例。
示例 9
使用newlnstance()方法创建对象。
关键代码:

public class Test {
    public static void main(String[] args) throws  Exception{
        Class cla=Date.class;
        Date d=(Date)cla.newInstance();
        System.out.println(d.toString());
    }
}

如果创建Java 对象时不是利用默认构造方法,而是使用指定的构造方法,则可以利用Constructor对象,每个Constructor对应个构造方法。指定构造方法创建Java对象需要如下3个步骤。
(1)获取该类的Class 对象。
(2)利用Class 对象的getConstructor()方法来获取指定构造方法。
(3) 调用Constructor的newInstance()方法创建Java对象。
示例10
利用Constructor对象指定的构造方法创建对象。
关键代码:

public class Test {
    public static void main(String[] args) throws Exception {
        //获取Date对应的Class对象
        Class cla = Date.class;
        //获取Date中带一个长整型参数的构造方法
        Constructor cu = cla.getConstructor(long.class);
//调用Constructor的newInstance()方法创建对象
        Date d = (Date) cu.newInstance(1987);
        System.out.println(d.toString());

    }
}

上述代码中,要使用Date类中带一个long 类型参数的构造方法,首先要在获取Constructor 时指定参数值为long. class,然后在使用newInstance()方法时传递一个实际的值1987。

3.访问类的属性

使用Field对象可以获取对象的属性。通过Field对象可以对属性进行取值或赋值操作,主要方法如表3.13所示。
表3-13访问类的属性的常用方法

方法 说明
Xxx gexXxx(Object obj) 该方法中Xxx对应8个基本数据类型,如int。obj为该 属性所在的对象。例如:Student p=new Student();nameField为Field对象
Object get(Object obj) 得到引用类型属性值。例如:Student P=\new Student();nameFiled.get§;//nameFiled为Fied对象
void seXxx(Objet obj,Xxx val) 将obj对象的该属性 设置成val值。此处的Xxx 对应8个基本数据类型
void set(Objet obj,objiect val) 将obj对 象的该属性设置成val值。针对引用类型赋值
void setAccessible(bool flag) 对获取到的属性设置f访问权限,参数为true,可以对私有属性取值和赋值

示例 11
访问学生(Student) 类的私有属性并赋值。
关键代码:

import java.lang.reflect.*;

/*自定义学生类*/
class Student {
    private String name;
    //姓名
    private int age;
    //年龄

    @Override
    public String toString() {
        return "name is" + name + ",age is" + age;
    }

    /*测试类*/
    public  class Test {
        public static void main(String[] args) throws Exception {
            //创建一个Student对象
            Student p = new Student();
//获取Student对应的Class 对象
            Class cla = Student.class;
            //获取Student类的name属性,使用getDeclaredField()方法可获取各种访问级别的属性
            Field nameField = cla.getDeclaredField("name");
//1设置通过反射访问该Field时取消权限检查
            nameField.setAccessible(true);
//1调用set()方法为p对象的指定Field 设置值
            nameField.set(p, "Jack");
            //获取Student类的age属性,使用geDeleaedFieltd方法可获取各种访问级别的属性
            Field ageField = cla.getDeclaredField("age");
            //1设置通过反射访问该Field时取消权限检查
            ageField.setAccessible(true);
//调用setnt()方法为p对象的指定Field设置值
            ageField.setInt(p, 20);
            System.out.println(p);
        }
    }
}

通常情况下,Student类的私有属性name和age只能在Student里访问,但示例i代码通过反射修改了Student对象的name和age属性值。 在这里,并没有用getField()方法来获取属性,因为getField()方法只能获取public访问权限的属性,而使用getDeclaredField()方法则可以获取所有访问权限的属性。

另外,为name和age赋值的方式不同,为name赋值只用了set()方法,而为age赋值则使用了setInt() 方法,因为前者是引用类型(String), 后者为值类型(int)。Student 类中的私有属性name和age分别被设成了Jack和20。

4.访问类的方法

使用Method对象可以调用对象的方法。在Method类中包含 一个invoke()方法法定义如下:

Object invoke(Object obj,Objec args)

其中,obj是执行该方法的对象,args是执行该方法时传入该方法的参数。
示例12
通过反射调用Student类的方法。
关键代码:

class Student {
    private String name;
    //姓名
    private int age;
    public String getName() {
//年龄
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String toString() {
        return "name is " + name + ", age is " + age;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        //获取Student对应的Class 对象
        Class cla = Student.class;
//创建Student对象
        Student p = new Student();
//得到setName方法
        Method met1 = cla.getMethod("setName", String.class);//调用setName,为name赋值
        met1.invoke(p, "Jack");
//得到getName方法
        Method met = cla.getMethod(" getName", null);//调用getName,获取name的值
        Object o = met.invoke(p, null);
        System.out.println(o);
    }
}

如果把Student类的setName()方法的访问权限设为私有再运行程序,则会抛出NoSuchMethodException异常。这是因为当通过Method的invoke()方法调用对应的方法时,Java 会要求程序必须有调用该方法的权限。如果程序确实需要调用某个对象的private方法,可以先调用setAccessible()方法,将Method对象的acessible标志设置为指示的布尔值,值为true则表示该Method在使用时应该取消Java语言访问权限检查;值为false则表示该Method在使用时应该进行Java语言访问权限检查。

5.使用Array类动态创建和访问数组

在java.lang.reflect包下还提供了个Array类,此Array类的对象可以代表所有的
数组。程序可以通过使用Array类来动态地创建数组、操作数组元素等。例如下面的代
码中,创建了数组arr,并为元素赋值。

//1创建一个元素类型为String, 长度为10的数组
    Object arr=Array.newInstance(String. class,10);
//依次为arr数组中index为5,6的元素赋值
    Array.set(arr,5,"Jack");
    Array.set(arr,6,"John");
    //依次取出arr数组中index为5, 6的元素的值
    Obiect ol=Array.get(arr,5);
    Obiect o2=Array.get(arr,6);

使用Array类动态地创建和操作数组很方便,大大简化了程序。关于Arrary类更多的方法可以在使用时查看API。
注意
使用反射虽然会很大程度。上提高代码的灵活性,但是不能滥用反射,因为通过反射创建对象时性能要稍微低一些。实际上,只有当程序需要动态创建某个类的对象时才会考虑使用反射,通常在开发通用性比较广的框架、基础平台时可能会大量使用反射。因为在很多Java框架中都需要根据配置文件信息来创建Java对象,从配置文件读取的只是某个类的字符串类名,程序需要根据字符串来创建对应的实例,就必须使用反射。
在实际开发中,没有必要使用反射来访问已知类的方法和属性,只有当程序需要动态创建某个类的对象的时候才会考虑使用。例如, 从配置文件中读取以字符串形式表示的类时,就要使用反射来获取它的方法和属性。

猜你喜欢

转载自blog.csdn.net/sanjiang521/article/details/107859559