反射(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按照这张从代码中映射出来的表来调用相关方法。
当然,这张表是虚构的,实际并不存在。