> 文章列表 > 【Java基础】反射详述

【Java基础】反射详述

【Java基础】反射详述

🚩 本文已收录至专栏:JAVA基础
👍希望能对你有所帮助

一.概述

反射是指对于任何一个Class类,在运行的时候都可以直接得到这个类全部成分,使得我们可以动态操作Java代码,同时反射也破坏了Java的封装性

例如:在运行时,可以直接得到这个类的构造器对象Constructor)、成员变量对象(Field)、成员方法对象(Method),不管是否为私有,这种运行时动态获取类信息以及动态调用类中成分的能力称为Java语言的反射机制
【Java基础】反射详述

反射的关键和核心思想:必须先获取编译后的Class类对象,然后就可以分析类对象中的全部成分并进行一些操作。

HelloWorld.java -> javac -> HelloWorld.class
// 获取类对象
Class c = HelloWorld.class;
// 获取其他类中的各个成分....

反射的用途:反射为绝大部分Java框架的底层实现原理。它常常被用于开发各种具有通用性的框架或者工具

二.获取类对象

(1) 引入

反射的第一步就是先获取Class类对象,然后我们便可以基于获取的类对象解析类中的全部成分。我们有三种方式可以获取类对象:
【Java基础】反射详述

三种方式对比显然直接通过类名.class方式获取最简单,但我们依旧需要看使用场景选择不同的方式。例如:假如我们需要获取方法参数传递对象的类对象,只能使用对象.getClass()

我们先创建一个学生类,用于获取类对象。

// 反射学习
package relate;public class Student {
}

(2) 第一种方式

我们可以通过Class类的一个静态方法forName("全限名")获取类对象

package relate;public class Test {public static void main(String[] args) throws ClassNotFoundException {// 获取类对象Class c = Class.forName("relate.Student");// 打印类对象,也可以直接通过getSimpleName获取类名System.out.println(c+"====>"+c.getSimpleName());}
}// 打印结果
// class relate.Student====>Student

全限名:可以看作是包名+类名

(3) 第二种方式

我们也可以通过一种更为简单的方式获取:类名.class

package relate;public class Test {public static void main(String[] args) throws ClassNotFoundException {// 获取类对象Class c = Student.class;// 打印类对象,,也可以直接通过getSimpleName获取类名System.out.println(c+"====>"+c.getSimpleName());}
}
// 打印结果相同
// class relate.Student====>Student

(4) 第三种方式

此外我们还可以通过Object类中的getClass方法获取类对象:对象.getClass()

package relate;public class Test {public static void main(String[] args) throws ClassNotFoundException {// 获取类对象Student student = new Student();Class c = student.getClass();// 打印类对象,,也可以直接通过getSimpleName获取类名System.out.println(c+"====>"+c.getSimpleName());}
}
// 打印结果也相同
// class relate.Student====>Student

我们已经获取了类对象,接下来便可以逐步解析类对象中的各个成分了,尽管类中存在一些私有的成分,但我们仍然可以获取到,也就是说可以破坏封装性

三.获取构造器对象

(1) 引入

我们可以通过反射获取类对象的任意构造器,然后再创建对象,尽管有些类的构造器是私有的,但并不妨碍我们基于此创建对象。
【Java基础】反射详述

Class类提供了一些方法供我们获取构造器对象:

方法 说明
Constructor<?>[] getConstructors() 返回所有构造器对象的数组(只能拿public的
Constructor<?>[] getDeclaredConstructors() 返回所有构造器对象的数组,存在(包括private)就能拿到
Constructor getConstructor(Class<?>… parameterTypes) 返回单个构造器对象(只能拿public的)
Constructor getDeclaredConstructor(Class<?>… parameterTypes) 返回单个构造器对象,存在(包括private)就能拿到

(2) 获取多个构造器

(2.1) 公有构造器

通过getConstructors方法我们只能获取所有的公有构造器:

//    public Student() {
//    }//    public Student(String name, int age) {
//        this.name = name;
//        this.age = age;//   }public class Test {public static void main(String[] args) {// 1.获取类对象Class c = Student.class;// 2.获取公有public构造器对象Constructor[] constructors = c.getConstructors();// 3.打印结果for (Constructor constructor : constructors) {System.out.println(constructor);}}
}// 可以看到我们声明的两个构造器
// public relate.Student()
// public relate.Student(java.lang.String,int)

假如我们将带参的构造器改为私有的:

private Student(String name, int age) {this.name = name;this.age = age;
}

再次运行发现我们只能获取到公有的无参构造器,而私有的带参构造器则无法获取到

// 打印结果
// public relate.Student()

(2.2) 任意构造器

我们只需要使用getDeclaredConstructors即可获取所有构造器,包括私有构造器,这也是我们常用的。用法与上述类似:

//    public Student() {
//    }//    private Student(String name, int age) {
//        this.name = name;
//        this.age = age;//   }public class Test {public static void main(String[] args) {// 1.获取类对象Class c = Student.class;// 2.获取所有存在的构造器对象Constructor[] constructors = c.getDeclaredConstructors();// 3.打印结果for (Constructor constructor : constructors) {System.out.println(constructor);}}
}
// 打印结果
// public relate.Student()
// private relate.Student(java.lang.String,int)

(3) 获取单个构造器

我们也可以根据构造器参数的类型与数量获取单个构造器。

(3.1) 公有构造器

我们可以通过getConstructor获取单个构造器,与上述类似,只能获取公有构造器

//    public Student() {
//    }//   public Student(String name, int age) {
//       this.name = name;
//        this.age = age;
//   }package relate;import java.lang.reflect.Constructor;public class Test {public static void main(String[] args) throws Exception {// 1.获取类对象Class c = Student.class;// 2.获取单个公有构造器对象Constructor constructor1 = c.getConstructor();  // 无参直接方法名获取Constructor constructor2 = c.getConstructor(String.class, int.class); // 带参数需要指定每个参数类型// 3.打印结果System.out.println("无参构造器===》" + constructor1);System.out.println("带参构造器===》" + constructor2);}
}
// 打印结果
// 无参构造器===》public relate.Student()
// 带参构造器===》public relate.Student(java.lang.String,int)

(3.2) 任意构造器

我们还可以通过getDeclaredConstructor获取单个任意构造器

//    public Student() {
//    }//    public Student(String name, int age) {
//        this.name = name;
//        this.age = age;
//   }package relate;import java.lang.reflect.Constructor;public class Test {public static void main(String[] args) throws Exception {// 1.获取类对象Class c = Student.class;// 2.获取任意构造器对象Constructor constructor1 = c.getDeclaredConstructor(); // 无参直接方法名获取Constructor constructor2 = c.getDeclaredConstructor(String.class, int.class); // 带参数需要指定每个参数类型// 3.打印结果System.out.println("无参构造器===》" + constructor1);System.out.println("带参构造器===》" + constructor2);}
}
// 打印结果
// 无参构造器===》public relate.Student()
// 带参构造器===》private relate.Student(java.lang.String,int)

(4) 创建对象

我们可以通过如下方法利用构造器对象创建对象。

方法 说明
T newInstance(Object… initargs) 根据指定的构造器创建对象(若构造器为private需要先取消检查)
public void setAccessible(boolean flag) 设置为true,表示取消访问检查,进行暴力反射 ,无视private

(4.1) 基于公有构造器

我们可以基于获取到的构造器对象来创建对象。

//    public Student() {
//    }package relate;import java.lang.reflect.Constructor;public class Test {public static void main(String[] args) throws Exception {// 1.获取类对象Class c = Student.class;// 2.获取构造器对象Constructor constructor = c.getDeclaredConstructor();// 3.根据无参构造器创建对象,0个参数Student student = (Student) constructor.newInstance();System.out.println(student);}
}
// 打印结果
// Student{name='null', age=0}

(4.2) 基于私有构造器

尽管我们可以获取到私有构造器,但是通过其创建对象时会发现程序会抛出异常,这时因为我们还要通过setAccessible打开权限。这也暴露了我们使用单例模式并不能一定保证只有一个对象。

//   private Student(String name, int age) {
//       this.name = name;
//        this.age = age;
//   }package relate;import java.lang.reflect.Constructor;public class Test {public static void main(String[] args) throws Exception {// 1.获取类对象Class c = Student.class;// 2.获取构造器对象Constructor constructor = c.getDeclaredConstructor(String.class, int.class);// 3.打开权限constructor.setAccessible(true);// 4.创建对象Student student = (Student) constructor.newInstance("观止", 19);System.out.println(student);}
}
// 打印结果
// Student{name='观止', age=19}

四.获取成员变量对象

(1) 引入

我们同样可以通过反射获取类对象的任意成员变量并进行赋值或获取值。
【Java基础】反射详述

Class类也提供了一些方法供我们获取成员变量对象:

方法 说明
Field[] getFields() 返回所有成员变量对象的数组(只能拿public的)
Field[] getDeclaredFields() 返回所有成员变量对象的数组,存在就能拿到
Field getField(String name) 返回单个成员变量对象(只能拿public的)
Field getDeclaredField(String name) 返回单个成员变量对象,存在就能拿到

需要注意的是:

  • getFields将返回所有的公共字段,包括从父类中继承来的公共字段
  • getDeclaredFields只返回自身包含的全部字段。

(2) 获取多个成员变量

(2.1) 公有成员变量

可以通过getFields获取所有公有成员变量

 //   public String name;//   private int age;import java.lang.reflect.Field;public class Test {public static void main(String[] args) throws Exception {// 1.获取类对象Class c = Student.class;// 2.获取公有成员变量Field[] fields = c.getFields();// 3. 遍历打印结果for (Field field : fields) {System.out.println(field + "===>" + field.getName());}}
}
// 打印结果
// public java.lang.String relate.Student.name ===> name ===> class java.lang.String

(2.2) 任意成员变量

可以通过getDeclaredFields获取所有任意成员变量

 //   public String name;//   private int age;package relate;import java.lang.reflect.Field;public class Test {public static void main(String[] args) throws Exception {// 1.获取类对象Class c = Student.class;// 2.获取任意成员变量Field[] fields = c.getDeclaredFields();// 3. 遍历打印结果for (Field field : fields) {System.out.println(field + " ===> " + field.getName());}}
}
// 打印结果
// public java.lang.String relate.Student.name ===> name ===> class java.lang.String
// private int relate.Student.age ===> age ===> int

(3) 获取单个成员变量

(3.1) 公有成员变量

可以通过getField获取单个公有成员变量

package relate;import java.lang.reflect.Field;public class Test {public static void main(String[] args) throws Exception {// 1.获取类对象Class c = Student.class;// 2.根据名称获取公有成员变量Field name = c.getField("name");// 3.打印结果System.out.println(name + " ===> " + name.getName()+ " ===> " + name.getType());}
}
// 打印结果
// public java.lang.String relate.Student.name ===> name ===> class java.lang.String

(3.2) 任意成员变量

可以通过getDeclaredField获取单个任意成员变量

package relate;import java.lang.reflect.Field;public class Test {public static void main(String[] args) throws Exception {// 1.获取类对象Class c = Student.class;// 2.根据名称获取任意成员变量Field age = c.getDeclaredField("age");// 3.打印结果System.out.println(age + " ===> " + age.getName()+ " ===> " + age.getType());}
}
// 打印结果
// private int relate.Student.age ===> age ===> int

(4) 赋值&获取值

我们可以获取或修改对象的字段值,尽管有限字段是私有不对外暴露的,但依旧可以通过反射进行操作。

方法 说明
void set(Object obj, Object value): 赋值
Object get(Object obj) 获取值。

我们必须先创建对象,再进行操作,因为不基于对象,对象值是毫无意义的。

package relate;import java.lang.reflect.Field;public class Test {public static void main(String[] args) throws Exception {// 1.获取类对象Class c = Student.class;// 2.获取成员变量Field name = c.getDeclaredField("name");Field age = c.getDeclaredField("age");// 3. 赋予访问私有字段权限age.setAccessible(true);// 3.给字段赋值Student student = new Student();name.set(student, "观止");age.set(student, 20);// 打印对象并获取字段值System.out.println(student + "===>" + name.get(student) + "===>" + age.get(student));}
}// 打印结果
// Student{name='观止', age=20}===>观止===>20

我们以前都是通过对象.getXX获取字段值和对象.setXX()设置值,而通过反射则是以XX.get(对象)获取字段值和xx.set(对象,X)设置值,刚好相反。

五.获取方法对象

(1) 引入

我们同样可以通过反射获取类对象的任意方法对象并且手动进行触发操作。

【Java基础】反射详述

Class类也提供了一些方法供我们获取方法对象:

方法 说明
Method[] getMethods() 返回所有成员方法对象的数组(只能拿public的)
Method[] getDeclaredMethods() 返回所有成员方法对象的数组,存在就能拿到
Method getMethod(String name, Class<?>… parameterTypes) 返回单个成员方法对象(只能拿public的)
Method getDeclaredMethod(String name, Class<?>… parameterTypes) 返回单个成员方法对象,存在就能拿到

需要注意的是:

  • getMethods将返回所有的公共方法,包括从父类中继承来的公共方法
  • getDeclaredMethods只返回自身包含的全部方法。

我们为学生类添加三个方法用于测试:

    public void run() {System.out.println("invoke public run...");}public void sing(String name) {System.out.println("sing " + name);}private void dance() {System.out.println("invoke private dance...");}private void eat(String name) {System.out.println("eat " + name);}

(2) 获取多个方法

(2.1) 公有方法

我们可以通过getMethods获取类中所有的方法包括其父类中的方法。

package relate;import java.lang.reflect.Method;public class Test {public static void main(String[] args) throws Exception {// 1.获取类对象Class c = Student.class;// 2.获取多个成员方法Method[] methods = c.getMethods();// 3.遍历for (Method method : methods) {System.out.println("方法名称:"+method.getName()+",返回值类型:"+method.getReturnType()+",参数个数:"+method.getParameterCount());}}
}
// 除了Student类中的方法,其继承自Object的方法也会打印
// 方法名称:run,返回值类型:void,参数个数:0
// 方法名称:toString,返回值类型:class java.lang.String,参数个数:0
// 方法名称:wait,返回值类型:void,参数个数:2
// 方法名称:wait,返回值类型:void,参数个数:1
// 方法名称:wait,返回值类型:void,参数个数:0
// 继承自Object.....

(2.2) 任意方法

我们可以看到上述虽然获取到了很多方法,但并没有获取到私有的两个方法,我们可以通过getDeclaredMethods获取任意方法。

package relate;import java.lang.reflect.Method;public class Test {public static void main(String[] args) throws Exception {// 1.获取类对象Class c = Student.class;// 2.获取多个成员方法Method[] methods = c.getDeclaredMethods();// 3.遍历for (Method method : methods) {System.out.println("方法名称:"+method.getName()+",返回值类型:"+method.getReturnType()+",参数个数:"+method.getParameterCount());}}
}
// Student类中声明的任意方法
// 方法名称:run,返回值类型:void,参数个数:0
// 方法名称:toString,返回值类型:class java.lang.String,参数个数:0
// 方法名称:dance,返回值类型:void,参数个数:0
// 方法名称:eat,返回值类型:void,参数个数:1

(3) 获取单个方法

(3.1) 公有方法

我们可以通过getMethod获取单个公有方法对象。

public class Test {public static void main(String[] args) throws Exception {// 1.获取类对象Class c = Student.class;// 2.获取公有成员方法Method run = c.getMethod("run"); // 无参直接方法名获取Method sing = c.getMethod("sing", String.class); // 带参数需要指定每个参数类型// 3.打印结果System.out.println("方法名称:"+run.getName()+",返回值类型:"+run.getReturnType()+",参数个数:"+run.getParameterCount());System.out.println("方法名称:"+sing.getName()+",返回值类型:"+sing.getReturnType()+",参数个数:"+sing.getParameterCount());}
}// 打印结果
// 方法名称:run,返回值类型:void,参数个数:0
// 方法名称:sing,返回值类型:void,参数个数:1

(3.2) 私有方法

我们可以通过getMethod获取任意方法对象。

public class Test {public static void main(String[] args) throws Exception {// 1.获取类对象Class c = Student.class;// 2.获取私有成员方法Method dance = c.getDeclaredMethod("dance"); // 无参直接方法名获取Method eat = c.getDeclaredMethod("eat", String.class); // 带参数需要指定每个参数类型// 3.打印结果System.out.println("方法名称:"+dance.getName()+",返回值类型:"+dance.getReturnType()+",参数个数:"+dance.getParameterCount());System.out.println("方法名称:"+eat.getName()+",返回值类型:"+eat.getReturnType()+",参数个数:"+eat.getParameterCount());}
}
// 打印结果
// 方法名称:dance,返回值类型:void,参数个数:0
// 方法名称:eat,返回值类型:void,参数个数:1

(4) 触发方法

我们可以触发获取到的方法,不管它是否为私有方法。

方法 说明
Object invoke(Object obj, Object… args) 参数一:通过对象调用该方法 参数二:调用方法的传递的参数(如果没有可以不写) 返回值:方法的返回值(如果没有可以不写)
public class Test {public static void main(String[] args) throws Exception {// 1.获取类对象Class c = Student.class;// 2.获取私有成员方法Method run = c.getDeclaredMethod("run");Method eat = c.getDeclaredMethod("eat", String.class);// 2.1 获取执行私有方法权限eat.setAccessible(true);// 3.创建对象用于触发方法Student student = new Student();// 4. 触发方法Object runRes = run.invoke(student); // 没返回值runRes为nullObject eatRes = eat.invoke(student, "水果");  // 没返回值eatRes为null// 3.打印结果System.out.println("runRes===>" + runRes + ",eatRes===>" + eatRes);}
}
// 打印结果
// invoke public run...
// eat 水果
// runRes===>null,eatRes===>null

六.作用

(1) 泛型擦除

由于反射是作用在运行时的技术,而泛型只在编译阶段可以约束只能操作某种数据类型,使用反射泛型将不能产生对我们产生约束,此时就相当于泛型被擦除了。

例如:我们编写如下代码时,往集合添加Integer类型数据不会产生报错,而添加其他类型数据将无法通过编译。

ArrayList<Integer> list = new ArrayList<>();
list.add(100);   // 合法
// list.add(“观止"); // 产生编译错误
list.add(99);  // 合法

而使用反射操作,此时集合的泛型将不能产生约束,可以为集合存入其他任意类型的元素的。泛型只是在编译阶段可以约束集合只能操作某种数据类型,在编译成Class文件进入运行阶段的时候,其真实类型都是ArrayList了。

public class Test {public static void main(String[] args) throws Exception {// 1.创建对象ArrayList<Integer> list = new ArrayList<>();// 尝试添加list.add(666);
//        list.add("观止"); // 报错// 2.获取类对象Class listClass = list.getClass();// 3.获取add方法Method add = listClass.getDeclaredMethod("add", Object.class);// 4.添加其他类型元素add.invoke(list,"观止");add.invoke(list,999);// 5.打印查看数据是否添加System.out.println(list);}
}
// 打印结果
// [666, 观止, 999]

可以看到外面不单单存在Integer类型数据,还存在它所不允许的String类型数据,跳过了泛型对我们的约束作用。

(2) 通用框架的底层原理

几乎所有的框架实现都用到了反射技术,使用反射可以帮助我们很好的处理多变的情况。

例如:给你任意一个对象,在不清楚对象字段的情况下,可以把对象的字段名称和对应值存储到文件中去。

【Java基础】反射详述

不使用反射显然我们无法对可变的情况进行固定的操作,更别说获取一些不对外暴露的私有字段。使用反射却能很好的帮助我们解决这种情况。


public class Test {public static void main(String[] args) throws Exception {Student s = new Student("彭于晏", 40, '男', 177.5, "男星");printAllFiled(s);Teacher t = new Teacher("观止", 6000);printAllFiled(t);}public static void printAllFiled(Object obj) throws IllegalAccessException {// 1.获取类对象Class objClass = obj.getClass();// 2. 获取类名打印横幅System.out.println("========" + objClass.getSimpleName() + "========");// 3.获取所有字段Field[] fields = objClass.getDeclaredFields();// 4.打印所有字段以及字段值for (Field field : fields) {// 5. 允许访问操作私有字段field.setAccessible(true);System.out.println(field.getName() + " = " + field.get(obj));}}
}

可以看到即使我们的类结构存在差异它也能正常的获取并打印数值。

【Java基础】反射详述