Java 反射

反射(reflect):通过类的Class对象来获取类的相关信息,动态操作类中的字段、调用类中的方法。

 

获取Class对象的三种方式:

  • Class.forName("全类名")    //调用Class类的静态方法来获取指定类的Class对象。
  • 类名.class   //通过该类的class属性来获取该类的Class对象。每个类都有class属性,class属性是类自带的,无需我们显式设置。
  • 对象名.getClass()   //通过该类的实例来获取Class对象

 

第二种方式最好。

  • 性能更优。第一种、第三种是调用方法,要为方法开辟内存区,为方法中的变量分配内存,方法执行完毕还要回收此方法的内存区,时间、空间开销大。
  • 安全性更好。第一种是通过字符串指定类名,编译时不会检查这个类存不存在,如果这个类不存在,执行时会抛出ClassNotFoundException。第二种编译时会检查类存不存在,如果不存在,通不过编译,其实写代码时IDE就会红色报错。
  • 最简便

 

但有一个问题:使用第二种不能动态创建对象,使用第一种可以。

 

示例:

1   Class<?> Class1 = Class.forName("test.Student");  //必须写成全类名。因为类名是String形式,编译时并不知道Class对象的类型,所以只能写成?,?相当于Object
2         Class<Student> Class2 = (Class<Student>) Class.forName("test.Student");  //强转后可以写成具体类型
3 
4         Class<Student> class3=Student.class;   //编译时知道Class对象的类型
5 
6         Student student=new Student();
7         Class<?> class4=student.getClass();
8         Class<Student> class5= (Class<Student>) student.getClass();  //需要强转

 

 

 

 

 

Class对象常用的方法

类是对对象的抽象,Class类是对所有类的抽象,Class类的实例称为Class对象。

类都有构造器、字段(成员变量+类变量)、方法(成员方法+类方法),Class对象提供了获取构造器、字段、方法的一系列方法。

 

获取构造器

  • ConStructor<T>  getStructor(Class<?>...   paramTypes)     //获取指定的构造器,此方法只能获取public修饰的构造器。参数要写成Class对象的形式,T表示该构造器创建的对象的类型。虽然构造器不写返回值类型,没有return语句,但构造器也是函数,也有返回值,构造器的返回值是创建的对象。
  • ConStructor<T>   getDeclaredStructor(Class<?>...   paramTypes)    //获取指定的构造器,此方法可获取任何权限的构造器。
  • ConStructor<?>[]   getStructors()    //获取所有的public构造器,复数,加了s,以数组形式返回。
  • ConStructor<?>[]   getDeclaredStructors()   //获取所有的构造器

T表示可以写成具体的类型,后面不用强转。

?相当于Object,后面需要强转。

 

获取字段

  • Field  getField(String fieldName)    //根据字段名获取指定的字段,注意获取到的是字段(Field),不是字段值。此方法只能获取public字段。
  • Field  getDeclaredField(String fieldName)   
  • Field[] getFields()
  • Field[]  getDeclaredFields()  

 

获取方法

  • Method getMethod(String  methodName, Class<?>...paramTypes)   //根据方法名获取指定方法,因为可能存在重载方法,所以还需要传入该方法的参数类型(Class对象形式)来区分。此方法只能获取public方法。
  • Method getDeclaredMethod(String  methodName, Class<?>...paramTypes)    //任意权限的方法
  • Method[] getMethods()
  • Method[]  getDeclaredMethods()

 

 

 

 

使用示例

Student类如下:

 1 class Student{
 2     private int id;
 3     private String name;
 4     private int age;
 5     private int score;
 6 
 7     public Student() {
 8     }
 9 
10     public Student(int id, String name, int age, int score) {
11         this.id = id;
12         this.name = name;
13         this.age = age;
14         this.score = score;
15     }
16 
17     public int getId() {
18         return id;
19     }
20 
21     public void setId(int id) {
22         this.id = id;
23     }
24 
25     public String getName() {
26         return name;
27     }
28 
29     public void setName(String name) {
30         this.name = name;
31     }
32 
33     public int getAge() {
34         return age;
35     }
36 
37     public void setAge(int age) {
38         this.age = age;
39     }
40 
41     public int getScore() {
42         return score;
43     }
44 
45     public void setScore(int score) {
46         this.score = score;
47     }
48 
49     @Override
50     public String toString() {
51         return "Student{" +
52                 "id=" + id +
53                 ", name='" + name + '\'' +
54                 ", age=" + age +
55                 ", score=" + score +
56                 '}';
57     }
58 }

 

 

 

使用反射来创建对象

1   Class<Student> studentClass = Student.class;  //获取Class对象
2         Constructor<Student> constructor = studentClass.getConstructor(int.class, String.class, int.class, int.class);  //获取指定的构造器。参数要写成Class对象的形式
3         Student student = constructor.newInstance(1, "chy", 20, 100);  //调用构造器的newInstance()创建对象,参数是实参值
4         System.out.println(student);

 

 

 

使用反射来操作字段

 1   Student student1 = new Student(1, "张三", 12, 90);  //创建对象
 2         Student student2 = new Student(2, "李四", 12, 90);
 3 
 4         Class<Student> studentClass = Student.class;  //获取Class对象
 5         Field nameField = studentClass.getDeclaredField("name");  //字段一般是私有的,所以要用Declared
 6         nameField.setAccessible(true);   //取消权限检查。
 7         /*
 8          public在类外可直接操作,所以不需要取消权限检查。
 9          private不允许在类外操作,需要取消权限检查才能操作private成员。
10          将通道设置为true,即打开通道,放行,通过检查。
11          */
12 
13         //获取字段的值
14         String name1 = (String) nameField.get(student1);  //根据student1的name字段的值,返回Object型
15         String name2 = (String) nameField.get(student2);
16         System.out.println(name1);  //张三
17         System.out.println(name2);  //李四
18 
19         //设置字段的值
20         nameField.set(student1,"zhangsan");  //第二个参数指定新值
21         nameField.set(student2,"lisi");
22         System.out.println(student1.getName());   //zhangsan
23         System.out.println(student2.getName());   //lisi
24         
25          /*
26         Field是通过Class对象获取的,不是通过这个类的某个对象获取的。Field包含了这个类所有对象的指定字段的值。
27         就是说nameField包含了Student类所有实例的name属性值。
28         所以要用一个参数指定操作的是哪个实例的字段。
29         
30         如果是8种基础数据类型的字段,使用的是:getInt()、setInt()等getXxx()、setXxx()系列方法,
31         获取值时返回的就是该种数据类型,不必强转
32         
33         如果是引用数据类型(包括String),使用的是get()、set()方法,获取值时返回的是Object类型,往往需要强转
34          */

 

 

 

使用反射来调用方法

 1 Student student = new Student(1, "张三", 12, 90);  //创建对象
 2         Class<Student> studentClass = Student.class;  //获取Class对象
 3 
 4         //调用无参的方法
 5         Method getNameMethod = studentClass.getMethod("getName");//方法一般是公有的,不必使用Declared。获取空参的getName()方法。第一个参数指定String类型的方法名
 6         //getNameMethod.setAccessible(true);   //方法一般是公有的,可以在类外使用,所以不必取消权限检查。
 7         String name = (String) getNameMethod.invoke(student);  //参数指定对象。
 8         /*
 9          会把被调方法的返回值作为invoke()的返回值。但不同的被调方法,返回值类型可能是不同的,所以invoke()的返回值类型声明是Object。
10          需要强转。
11          */
12         System.out.println(name);   //张三
13 
14 
15         //调用有参的方法
16         Method setNameMethod = studentClass.getMethod("setName",String.class);//第一个参数指定方法名,后面参数个数不确定,指定形参的对象类型。(区分重载方法)
17         setNameMethod.invoke(student,"李四");  //第一个参数指定对象,后面参数个数不确定,指定实参值
18         System.out.println(student.getName());   //李四

 

 

 

使用反射动态创建、操作数组

java.lang.reflect包下有一个Array类,此类有许多静态方法,可以动态创建数组,动态获取、设置数组元素的值。

 1 //动态创建数组
 2         Object obj = Array.newInstance(String.class, 10); //第一个参数指定数组的元素类型,第二参数指定元素个数
 3         String[] arr= (String[]) obj;  //动态创建数组的返回值返回值是Object,有时候需要强转
 4 
 5         //动态设置数组元素的值
 6         Array.set(obj, 0, "张三");  //第一个参数指定数组,第二个参数指定数组下标,第三个参数指定元素值
 7         Array.set(obj,1,"李四");  //虽然obj是Object类型,但要求第一个参数是数组,会自动向下转型
 8 
 9         //动态获取数组元素的值
10         Object first = Array.get(obj, 0);  //第一个参数是数组,第二个参数是数组下标
11         Object second=Array.get(obj, 1);
12         System.out.println(first);  //会自动向下转型。张三
13         System.out.println(second);  //李四
14 
15         /*
16         这三个方法均为静态方法,通过Array直接调用。
17         基本数据类型用setXxx()、getXxx(),比如setInt()、getInt(),获取时返回的就是该类型。
18         引用数据类型用set()、get(),获取值时返回值类型是Object。
19          */

 

 

 

 

反射的特点是动态操作。

原来我们创建对象:Student student=new Student(1,"chy",20,100);

操作字段、调用函数:student.setAge(22);

操作数组:arr[0]=1

把代码写死了。

 

 

使用反射:把数据作为参数传入。

比如调用方法:

Method method = studentClass.getMethod("setName",String.class);
method.invoke(student,"李四");  

把方法名、参数类型作为参数传入,把对象、实参作为方法传入。

传入什么方法,就调用什么方法。比如传入"setName",它就调用setName(),传入"getName",就调用getName()。它是动态调用的,传入什么,就调用什么。

不像原来student.setName("张三");把方法写死了,这句代码只会调用setName(),调用不了其他方法。

 

 

比如操作数组:

Array.set(arr, 0, "张三");

把数组、索引、元素值作为参数传入,传入哪个数组就操作哪个数组,传入哪个索引就操作哪个索引,传入什么值,就使用什么值,根据传入的东西来动态操作。

不像原来arr[0]="张三",都写死了,全是固定的,最多索引、元素值使用变量,但数组仍是固定的。

 

 

 

何谓反射?

原来我们创建对象、操作字段、调用方法、操作数组,都是要有这个东西才能使用。

比如IDE下,我们写 new  Student(),要有Student这个类才可以,

我们写student.getName()  ,要有student这个对象,且这个对象要有getName()这个方法才可以,

我们写 arr[0]="张三",要有arr这个数组才可以。

没有就通不过编译。

 

反射相当于从代码中映射出一张表,JVM按照这张映射表来进行相关操作。

 

比如调用方法:

Method method = studentClass.getMethod("setName",String.class);
method.invoke(student,"李四"); 
Method method = studentClass.getMethod("getName");
method.invoke(student); 

方法名 要操作的对象 形参类型 实参表
setName student String.class "李四"
getName student

不管方法名有没有,不管这个操作可不可行,都可以通过编译。
运行时,JVM按照这张从代码中映射出来的表来调用相关方法。
当然,这张表是虚构的,实际并不存在。

 

 

 

使用反射绕过泛型检测

 ArrayList<Integer> list=new ArrayList<>();
        list.add(1);
        list.add("hello");   //报错

有泛型检测,不能通过编译。

 

Class alClass = Class.forName("java.util.ArrayList");
        Method add = alClass.getMethod("add", Object.class);  //将add()的参数类型设置为Object
        ArrayList<Integer> arrayList=new ArrayList<>();
        add.invoke(arrayList,"hello");  //调用arrayList的add()方法,添加字符串"hello"
        System.out.println(arrayList);  //[hello]。成功加入String,绕过了泛型检测。

JVM中没有泛型的概念,创建Class对象时会扔掉泛型。比如ArrayList<Integer>,创建Class对象时创建的是ArrayList,是不带泛型的。没有ArrayList<Interger>这种数据类型(Class对象),ArrayList才是数据类型。

 

 

 

posted @ 2019-08-20 20:04  chy_18883701161  阅读(573)  评论(0编辑  收藏  举报