目录
什么是反射
定义
反射是所有第三方框架的基础,Java的反射(reflection)机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到,那么我们就可以修改部分类型信息;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射(reflection)机制。
动态获取类信息以及动态调用该对象的属性和方法的功能称之为反射。(此处的动态,指的是运行时获取,javac 生产 class文件,反射就存在于java运行一个主类时)
例如下面我们在IDEA中双击 shift 查看HashMap的源代码,可以看到它里面所有的类和方法,不论 是公有的私有的都可以看到,这就是通过反射来实现的。
反射操作的四个核心类
这四个类都在 java.lang.reflect 中
1. Class类:反射操作的核心类
Class类也是类的一种,只是名字和class关键字高度相似,注意首字母大写。它是一个描述类的类,也可以生成对象,
Class类的对象内容是你创建的类的类型信息,比如你创建一个shapes类,那么,Java会生成一个内容是shapes的Class类的对象
但是,Class类的对象不能像普通类一样,以 new shapes() 的方式创建,它的对象只能由JVM创建,因为这个类没有public构造函数
对于每个类而言,在 JRE 中有且仅有一个不变的 Class 类型的对象,这个对象只能由系统建立,封装了当前对象所对应类的信息,如属性、方法、接口等等。
Class类的作用是运行时提供或获得某个对象的类型信息
我们知道,
- .java文件,就是当前编写的代码文件
- .class文件,就是编译过后的文件(JVM只识别 .class 文件)
javac 将源代码编译成 JVM 可以识别的代码(.java -> .class).class这个二进制文件就是编译后的文件,给 JVM 来阅读,包含了编译后该类的所有信息。被编译后的Java文件.class 也被JVM解析为 一个对象,这个对象就是 java.lang.Class .这样当程序在运行时,每个java文件就最终变成了Class类对象的一个 实例。我们通过Java的反射机制应用到这个实例,就可以去获得甚至去添加改变这个类的属性和动作,使得这个类 成为一个动态的类
获取Class对象
要想通过反射操作类或者对象,第一步要获得该类的 Class 对象,获取的方式有三种
- 通过类名获取, 类名 . class
- 通过该类的实例化对象获取, 对象 . getClass()
- 通过全类名获取, Class.forName ( "全类名" )。全类名:类所在的包名+类名
示例:获取 Students 类的 Class对象
class Person{
}
public class Students extends Person {
public static void main(String[] args) throws Exception {
//1.直接通过类名称.class
Class c1 = Students.class;
//2.通过Students类的任意一个实例化对象,调用getClass 方法
Class c2 = new Students().getClass();
//通过Class的forName方法
Class c3 = Class.forName("article2.Students");
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
}
}
//输出:
class article2.Students
class article2.Students
class article2.Students
如上面所说,一个类只对应一个 Class 对象,所有c1、c2、c3是一样的。
编译期类型和运行时类型(RTTI)
我们来看下面的代码:
Person per = new Students();
System.out.println(per.getClass());
//输出:class article2.Students
在编译时,per 这个引用的类型就是 Person,最终在 JVM 运行的时候,per的类型就是运行时类型:Students ,因为这个引用是通过 Students 类 new 出来的
反射的应用—通过反射来创建一个对象的实例
class Person{
}
public class Students extends Person {
private String name;
public int age;
String country;
public Students(){
//没有参数
System.out.println("Non Parameter");
}
public Students(String name){
this.name = name;
}
public Students(String name,int age,String country){
this.name = name;
this.age = age;
this.country = country;
}
@Override
public String toString() {
return "Students{" +
"name='" + name + '\'' +
", age=" + age +
", country='" + country + '\'' +
'}';
}
public static void main(String[] args) throws Exception {
//1.先获取该类的Class对象
Class<Students> cls = Students.class;
//2.调用Class对象的newInstance方法产生Student类的示例
Students stu = cls.newInstance();
System.out.println(stu);
}
}
//输出:
Non Parameter
Students{name='null', age=0, country='null'}
可以看到 ,通过class对象的newlnstance方法默认调用该类的无参构造,当无参构造不存在或者无参构造不可见(private,不同包)时,运行就会报错,这个方法无法使用。
这时我们就需要用到另外一个反射类: Constructor
2.Constructor类:与构造方法相关的类
当需要使用类的其他构造方法来创建对象时,需要用到反射类Constructor
常用方法:
getConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
getConstructors() 和 getDeclaredConstructors()都无法获取到父类的Constructor对象,只能取得当前类的。
①getConstructors()
我们将上面的代码做如下修改,注意 ReflectTest 类和其他两个类不在同一个包中。
package article2;
public class Person {
private String personName;
public int personAge;
public Person() {
System.out.println("Person的无参构造");
}
private Person(String personName){
System.out.println("Person的有参构造");
this.personName = personName;
}
}
package article2;
public class Students extends Person {
private String name;
public int age;
String country;
private Students(){
//没有参数
System.out.println("Non Parameter");
}
//包访问权限
Students(String name){
System.out.println("One Parameter");
this.name = name;
}
public Students(String name,int age,String country){
System.out.println("Three Parameters");
this.name = name;
this.age = age;
this.country = country;
}
@Override
public String toString() {
return "Students{" +
"name='" + name + '\'' +
", age=" + age +
", country='" + country + '\'' +
'}';
}
}
package attempt;
import article2.Students;
import java.lang.reflect.Constructor;
import java.util.Arrays;
public class ReflectTest {
public static void main(String[] args) throws Exception {
//1.先获取该类的Class对象
Class<Students> cls = Students.class;;
//2.通过其他的构造方法来产生Students类的实例
Constructor[] constructors = cls.getConstructors();
System.out.println(Arrays.toString(constructors));
}
}
输出:
可以看到,只获取了Students类的公有构造方法,所以,Class类的getConstructors() :只能获取当前类的所有public权限的构造方法
②getDeclaredConstructors()
我们只需要把上面的代码做如下修改:
Constructor[] constructors = cls.getDeclaredConstructors();
System.out.println(Arrays.toString(constructors));
//输出:
[public article2.Students(java.lang.String,int,java.lang.String),
article2.Students(java.lang.String),
private article2.Students()]
下图将输出结果和对应的构造方法匹配,便于我们分析。可见,不论公有的、私有的、包访问权限的统统都能访问到,所以,Class类的getDeclaredConstructors()可以拿到当前类的所有构造方法,包括私有构造。
获得指定的构造方法
若我们想到获得特点的构造方法,只需要getDeclaredConstructors方法的's'去掉
//不传参,表示拿到的是无参构造的 constructor 对象
Constructor constructor = cls.getDeclaredConstructor();
//一个参数的有参构造,通过参数类型区别
Constructor constructor1 = cls.getDeclaredConstructor(String.class);
System.out.println(constructor);
System.out.println(constructor1);
//输出
private article2.Students()
article2.Students(java.lang.String)
实例化对象
拿到特定的 Constructor 对象后,就可以通过该对象来实例化Students对象
public static void main(String[] args) throws Exception {
//1.先获取该类的Class对象
Class<Students> cls = Students.class;
//不传参,表示拿到的是无参构造的 constructor 对象
Constructor constructor = cls.getDeclaredConstructor();
//破坏封装
constructor.setAccessible(true);
//通过constructor对象破坏封装以后,获得操作权限来创建对象
Students stu = (Students) constructor.newInstance();
Students stu1 = (Students) constructor.newInstance();
Constructor constructor1 = cls.getDeclaredConstructor();
Students stu2 = (Students) constructor1.newInstance();
}
}
输出:
可以看到,stu 和 stu1 都成功创建了,而 stu2 运行时却出错了。因为破坏封装的对象只有constructor ,而constructor1是没有破坏封装的,而我们的无参构造是私有的访问权限,所以它创建失败,就像我们正常创建对象也会出错一样。
3.Method类:与类方法相关的类
getMethods() | 获得该类和父类所有公有的方法 |
getDeclaredMethods() | 获得该类所有方法 |
getMethods()
我们在Students类中添加下面的方法:
private void fun(){
System.out.println("Students类的fun方法,private权限");
}
public int getAge(int num){
System.out.println("Students类的getAge方法,public权限");
return this.age + num;
}
在Person类中添加下面的方法:
private void test(){
System.out.println("Person的test方法,private权限");
}
public void testPerson(){
System.out.println("Person的testPerson方法,public权限");
}
创建MethodTest类:
public class MethodTest {
public static void main(String[] args) throws Exception {
//要是有反射,先拿到Class对象
Class<?> cls = Class.forName("article2.Students");
Method[] method = cls.getMethods();
for (int i = 0; i < method.length; i++) {
System.out.println(method[i]);
}
}
}
输出:
可以看到,getMethods 方法可以拿到当前类以及其父类的所有public方法。
getDeclaredMethods()
public class MethodTest {
public static void main(String[] args) throws Exception {
//要是有反射,先拿到Class对象
Class<?> cls = Class.forName("article2.Students");
Method[] method = cls.getDeclaredMethods();
for (int i = 0; i < method.length; i++) {
System.out.println(method[i]);
}
}
}
输出:
可以看到,getDeclaredMethods()仅能拿到当前类定义的所有方法,包含private方法。
获得指定的方法
同样,去掉 getDeclaredMethods() 方法的 's',传入方法名称和参数类型
//fun方法没有参数,所以不需要参数类型
Method method = cls.getDeclaredMethod("fun");
Method method1 = cls.getDeclaredMethod("getAge", int.class);
System.out.println(method);
System.out.println(method1);
//输出:
private void article2.Students.fun()
public int article2.Students.getAge(int)
通过反射来调用类的方法
- 先获取该类的class对象
- 通过getDec或getMethod获取这个方法的method反射对象
- 若该方法是一个成员方法,需要产生该类的实例
- invoke(该类的实例-静态方法就是null,具体的方法参数);来调用
具体实现:我们先将Students类的无参构造化为公有,再添加一个静态方法
public static void staticFunc(String test){
System.out.println(test);
System.out.println("Students类的静态方法");
}
public class MethodTest {
public static void main(String[] args) throws Exception {
//要是有反射,先拿到Class对象
Class<?> cls = Class.forName("article2.Students");
//拿到private的fun方法
Method method = cls.getDeclaredMethod("fun");
method.setAccessible(true);
//fun方法是成员方法,必须通过Students类的对象来调用
Students stu = (Students) cls.newInstance();
//开始调用
//method.invoke通过哪个对象来调用,方法的参数
method.invoke(stu);
System.out.println("-------------");
Method sc = cls.getDeclaredMethod("staticFunc", String.class);
//调用staticFunc方法
//静态方法不需要对象
sc.invoke(null,"张三");
}
//输出:
Person的无参构造
Non Parameter
Students类的fun方法,private权限
-------------
张三
Students类的静态方法
4.Field类:与类属性相关的类
getFields() | 拿到当前类和父类的所有public属性 |
getDeclaredFields() | 拿到当前类的所有属性,包括private属性。 |
获取类的属性
public class FieldTest {
public static void main(String[] args) {
//拿到Class对象
Class<?> cil = new Students().getClass();
System.out.println("-------");
Field[] fields = cil.getFields();
for (int i = 0; i < fields.length; i++) {
System.out.println(fields[i]);
}
System.out.println("----------");
Field[] field1 = cil.getDeclaredFields();
for (int i = 0; i < field1.length; i++) {
System.out.println(field1[i]);
}
}
}
//输出:
Person的无参构造
Non Parameter
-------
public int article2.Students.age
public int article2.Person.personAge
----------
private java.lang.String article2.Students.name
public int article2.Students.age
java.lang.String article2.Students.country
获取指定的属性值
public class FieldTest {
public static void main(String[] args) throws Exception{
//拿到Class对象
Class<?> cil = new Students().getClass();
//拿到指定的name属性
Field field = cil.getDeclaredField("name");
field.setAccessible(true);
//name属性是一个成员变量,需要产生Students类的对象
Students students = (Students) cil.newInstance();
// field.set(给哪个对象设置值,设置的具体值);
field.set(students,"张三");
//你是通过哪个具体的Students对象来获取属性的
System.out.println(field.get(students));
}
}
//输出:
Person的无参构造
Non Parameter
Person的无参构造
Non Parameter
张三
set方法就是设置属性值,get方法就是取得属性值,这里我们若不设置,name的值就是null。