反射
Java反射机制是Java语言的一个重要特性。在学习Java反射机制之前,我们先介绍两个概念,编译时和运行时。
编译时间是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中也就是把 Java 代码编成 class 文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作。
运行是把编译后的文件交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。
Java反射机制处于运行状态。通过Java的反射机制,可以控制程序的运行过程。例如,通过类名,可以动态获取类所拥有的构造函数、属性和类方法,并调用任意类的任意方法。这种动态获取信息、动态调用对象方法的功能称为Java语言的反射机制。
JVM为每个加载的类创建一个对应的Class实例,并在实例中保存了该类的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果你获取 一旦我们有了某个Class实例,我们就可以通过这个Class实例获取该实例对应的类的所有信息。这种通过Class实例获取类信息的方法称为反射。
Java 反射机制主要提供了以下功能,这些功能都位于java.lang.reflect
包。
Constructor
类:提供类的构造方法信息。Field
类:提供类或接口中成员变量信息。Method
类:提供类或接口成员方法信息。Array
类:提供了动态创建和访问 Java 数组的方法。Modifier
类:提供类和成员访问修饰符信息。
要想知道一个类的属性和方法,必须先获取到该类的字节码文件对象。获取类的信息时,使用的就是 Class
类中的方法。所以先要获取到每一个字节码文件(.class
)对应的 Class 类型的对象。
Class类
Class类在执行过程中由JVM动态加载。对于任何Object类,当JVM加载Object类时,它首先将Object.class文件读入内存,然后为Object类创建一个Class实例。 JVM 第一次读取 Class 类型时,会将其加载到内存中。
java.lang.Class
类是实现反射的关键所在,Class 类的一个实例表示 Java 的一种数据类型,包括类、接口、枚举、注解(Annotation)、数组、基本数据类型和 void。Class 没有公有的构造方法,Class 实例是由 JVM 在类加载时自动创建的。
每一种类型包括类和接口等,都有一个 class 静态变量
可以获得 Class 实例。另外,每一个对象都有 getClass() 静态方法
可以获得 Class 实例,该方法是由 Object 类提供的实例方法。Class类还提供了forNmae()静态方法
通过类的全限定名获取Class类。
获取Class类的三种方法
- 通过
类名.class
静态变量获取Class类
Class class=[类名].class
public class Test {
Class studentClass=Student.class;
public static void main(String[] args) {
System.out.println("获取Student类的类名:"+new Test().studentClass.getName());
System.out.println("获取Student类的包名:"+new Test().studentClass.getPackage());
}
}
- 通过实例对象的
getClass()
静态方法获取Class类
String string =new String();
Class stringClass=string.getClass();
class TestOne{
private Student student=new Student();
Class studentClass=student.getClass();
}
public class Test {
public static void main(String[] args) {
System.out.println("获取Student类的类名:"+new TestOne().studentClass.getName());
System.out.println("获取Student类的包名:"+new TestOne().studentClass.getPackage());
}
}
- 通过全类名为参数的的静态方法
Class.forName()
获取Class类
class TestThree{
TestThree() throws ClassNotFoundException {
Class studentClass=Class.forName("com.company.reflecttion.Student");
}
}
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
System.out.println("获取Student类的类名:"+new TestThree().getClass().getName());
System.out.println("获取Student类的包名:"+new TestThree().getClass().getPackage());
}
}
反射获得相同的实例。该实例在 JVM 中是唯一的。只有new才会为新实例分配内存空间。
用instanceof
判断类型,instanceof不但匹配指定类型,还匹配指定类型的子类。而用```==``判断class实例可以精确地判断数据类型,但不能作子类型比较。
public class TaskOne {
public static void main(String[] args) throws ClassNotFoundException {
Student student=new Student();
Class class1=Student.class;
Class class2=student.getClass();
Class class3=Class.forName("com.company.reflecttion.Student");
System.out.println(class1==class2 && class1==class3 && class2==class3);
}
}
//结果:
true
访问Class对象的信息
对任意的一个Object实例,只要我们获取了它的Class,就可以获取它的一切信息。
Field是 Java 编程语言中类的一个成员,主要用来存储对象的状态,有时也可称为成员字段或成员变量。
Package是Java的一个存放包路径的对象。
获取成员变量
Field getField(String name)
:根据字段名获取某个public的Field类Field getDeclaredField(String name)
:根据字段名获取当前类的某个Field类Field[] getFields()
:获取所有public的Field类Field[] getDeclaredFields()
:获取当前类的所有Fieldl类
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class clazz=Class.forName("com.company.reflecttion.School");
Field field=clazz.getField("schoolName");
System.out.println(field);
}
/*结果 public java.lang.String com.company.reflecttion.School.schoolName 返回的是public修饰的 schoolName的成员变量 */
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class clazz=Class.forName("com.company.reflecttion.School");
Field[] fields=clazz.getFields();
System.out.println(fields);
}
/*结果 [Ljava.lang.reflect.Field;@6d6f6e28 返回结果是所有public修饰的成员变量,返回的是地址 */
Field 对象包含有关字段的所有信息
如果成员变量是private私有修饰,需要忽略权限:
将setAccessible()方法参数设为true。出现IllegalAccessException
错误就是访问权限错误。
getName()
:返回字段名称;getType()
:返回字段类型,也是一个Class实例,例如,String.class;getModifiers()
:返回字段的修饰符,它是一个int,不同的数字表示不同的含义。
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class clazz=Class.forName("com.company.reflecttion.School");
Field field=clazz.getField("schoolName");
System.out.println(field.getName());
System.out.println(field.getType());
System.out.println(field.getModifiers());
}
/*结果 schoolName class java.lang.String 1 */
获取实例字段的值
想要获取对象成员变量的值,必须有实例,不能通过静态方法
Class.forName
或类名.class
获取。
通过Class获取Field可以获取成员变量的所有信息,但实际获取变量的值更有意义。如何通过Field获取实例变量的值?
get(Object obj)
获取实例对象的成员变量的值set(Object obj,Object value)
修改实例对象的成员变量的值setAccessible(boolean flag)
解除私有访问权限
以实例作为参数,get和set方法获取实例的成员变量的值。所以参数是类的实例。
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
School school=new School();
Class schoolClass=school.getClass();
Field field=schoolClass.getField("phone");
System.out.println("获取的Filed成员为:"+field);
System.out.println("Filed实例的值为:"+field.get(school)); //参数为School的实例化对象
field.set(school,987654321);
System.out.println("修改实例对象的值为987654321后的值"+field.get(school));
}
//School类
package com.company.reflecttion;
public class School {
public String schoolName="某某学校";
public int phone=123456789;
private String idCard;
private int core;
private String IdCardSet(){
return this.idCard="0000000";
}
private int coreSet(){
return this.core=900000;
}
public String getIdCard(){
return IdCardSet();
}
public int getCore(){
return coreSet();
}
}
访问构造方法
为了动态获取对象构造方法的信息,首先需要通过以下方法之一创建Constructor类型的对象或数组。
getConstructors()
获取修饰词为public的所有构造方法getConstructor(Class<?>…parameterTypes)
获取public修饰的某个构造方法getDeclaredConstructors()
获取所有构造方法getDeclaredConstructor(Class<?>...parameterTypes)
获取某个构造方法
》
有参构造方法的参数为
Class<T> ... paramterTyes
表示形参的Class类,如String.class
。
实例化这些类构造一个构造方法对象存储实例的构造方法信息。创建的每个 Constructor 对象表示一个实例的构造方法,然后利用 Constructor 对象的方法操作实例的构造方法。
调用Class.newInstance()
的局限是,它只能调用该类的public
参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。
为了调用任意的构造方法,Java的反射API提供了Constructor
对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法。
Constructor
类的方法的形参是构造方法参数的Class
类(字节码文件),没有就不写。newInstance()
方法用于构造方法的实例化,形参是实际要传输的参数。例如:构造方法的
Person(String name,int age)
的形参是String
和int
,那么getConstructors()
方法的参数就为String的Class类,int的Class类即Constructorconstructor=Person.class.getConstructors(String.class,int.class)
。
newInstance()方法中传递参数如:constructor.newInstance("张三",20)
。
//创建类
public class SchoolEx extends School{
public String name="李华";
private String studentId="000000";
SchoolEx(){
System.out.println(this.name+"-----"+this.studentId);
}
SchoolEx(String name,String studentId){
System.out.println(name+"-----"+studentId);
}
}
//获取并调用构造方法
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//获取构造方法
Constructor constructor=SchoolEx.class.getDeclaredConstructor();
//调用构造方法
constructor.newInstance();
Constructor constructor1=SchoolEx.class.getDeclaredConstructor(String.class,String.class);
constructor1.newInstance("张三","111111");
}
/* 获取构造方法随着newInstance()的实例化自动调用 */
访问方法
通过Class
实例可以获取所有Field
对象,同样的,java.lang.reflect.Method
类可以通过Class实例获取所有Method
信息即成员方法的全部信息。
Class类提供了以下方法来获取Method:
getMethod(String name,Class<?> …parameterTypes)
获取某个public修饰的MethodgetMethods()
获取所有public修饰的MethodgetDeclaredMethod(String name,Class<?> …parameterTypes)
获取某个MethodgetDeclaredMethods()
获取所有Method
带参数的方法中第一个参数是方法名,第二个参数是形参的Class类。
调用
Method类中有众多方法获取从Class类获取的方法的信息,其中最重要的是
invoke(Object obj,Object agrs)
方法,该方法的作用是实例方法的实例化(调用实例方法)。第一个参数是实例对象,第二个参数是形参的Class类(字节码文件类)。
对Method实例调用invoke就相当于调用该方法,invoke的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致。如果获取到的Method代表的是静态方法,那么在调用静态方法时,由于不需要指定实例对象,所以invoke方法传入的第一个参数始终为null。
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Person person=new Person();
//获取public修饰的无参方法
Method method=person.getClass().getMethod("getName");
//调用无参反方法
System.out.println(method.invoke(person));
//获取private修饰的有参方法
Method method1=person.getClass().getDeclaredMethod("setName", String.class);
//解除private限制
method1.setAccessible(true);
method1.invoke(person,"张三");
//调用有参方法
System.out.println(method.invoke(person));
}
需要注意的是通过Class类构造Method对象时
getMethod(String name,Object Class<T> parameterTypes)
第一个参数时方法名为String类型,第二个参数是参数的Class类对象。
调用将得到一个
IllegalAccessException
。为了调用非public方法,我们通过Method.setAccessible(true)
允许其调用。
访问修饰符
通过 java.lang.reflect.Modifier 类可以解析出 getMocMers()
方法的返回值所表示的修饰符信息
。在该类中提供了一系列用来解析的静态方法
,既可以查看是否被指定的修饰符修饰,还可以字符串的形式获得所有修饰符。
每个反射中都有一个getMocMers()
方法,但是该方法返回的是一个int
类型,并不知道修饰符到底是public
,default
,private
,protected
或类的修饰符等。
Modifier
类提供了众多方法来辨别修饰符类型,返回值是boolean类型。如1
是public
那么:
public static void main(String[] args) {
System.out.println(Modifier.isPublic(1));
}
//结果:
true