文章目录
一、反射简介
1.1 先验知识
class也是一个对象
在面向对象的世界里,**java中的类(就是我们定义的那个class)也是一个实例对象,**是Class类的实例对象,这个对象在官网被称为(class type)。
类的加载信息
1.2 反射是什么?
在程序运行状态中:
- 对于任意一个类,都能够知道这个类的所有属性和方法;
- 对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
1.3 反射能干什么?
反射机制主要提供了以下功能:
- 在运行时判断任意一个对象所属的类;
- 在运行时判断任意一个类所具有的成员变量和方法;
- 在运行时构造任意一个类的对象;
- 在运行时调用任意一个对象的方法;
- 生成动态代理
【注意】:是运行时获取而不是编译时获取。
举例:很多时候我们在用ide 或eclipse等编译器写代码时,当我们输入一个点的时候(比如 student.) 编译器就会自动列出它的属性和方法,这里就会用到反射。
二、常用的反射操作
java的反射机制的实现要借助于4个类: Class、Constructor、Field、Method,常用的操作基本都围绕这四个类来。
下面以需求demo的方式来展示一些常用的操作。在展示之前,我们要做些准备工作。
2.1 准备工作
构造一个 Student 类,用于测试:
package reflectDemo;
public class Student {
//3个私有属性
private String name;
private Integer age;
private String sex;
//2个构造方法:无参 + 有参
public Student(){}
public Student(String name ,Integer age, String sex){
this.name = name;
this.age = age;
this.sex = sex;
}
//2个public函数:无参 + 有参
public String getNameWithAge(){
return this.name +"的年龄是" + this.age + "岁.";
}
public String getNameWithKeyInfo(String keyInfo){
return this.name +"的座右铭是" + keyInfo + ".";
}
//1个带参私有函数
private String getAllInfo(String keyInfo){
return this.name + "年龄是 " + this.getAge()
+", 性别是 " + this.getSex() + ", 座右铭是:" + keyInfo;
}
// 其他工具函数
public String getName(){
return this.name;
}
public String getSex(){
return this.sex;
}
public Integer getAge(){
return this.age;
}
@Override
public String toString(){
return this.name + "年龄是 " + this.getAge()
+", 性别是 " + this.getSex();
}
}
2.2 获取 class 对象
class对象是反射中最常用的,获取class对象的方式的主要有三种
- 根据类名:类名.class
- 根据对象:对象.getClass()
- 根据全限定类名:Class.forName(全限定类名)
demo如下:
public class ReflectStudent {
Student bigBear = new Student("BigBear", 29,"男");
public static void main(String[] args){
//1、通过全限定类名获取类信息
Class<?> class1 = Class.forName("reflectDemo.Student");
System.out.println("1、通过[全限定类名]获取类信息:" + class1.getName());
//2、通过对象获取类信息
Class<?> class2 = bigBear.getClass();
System.out.println("2、通过[对象]获取类信息:" + class2.getName());
//3、通过类名获取类信息
Class<?> class3 = Student.class;
System.out.println("3、通过[类名]获取类信息" + class3.getName());
}
}
输出结果:
1、通过[全限定类名]获取类信息:reflectDemo.Student
2、通过[对象]获取类信息:reflectDemo.Student
3、通过[类名]获取类信息reflectDemo.Student
获取到class对象用来干什么?能干的事情多了,可以获取 构造方法Constructor、方法Method、属性Field、创建对象等,下面分别举例说明。
2.3 获取构造方法(Constructor)
常用的获取构造方法的方式:
// 返回指定参数类型、具有public访问权限的构造函数对象(若无参数,可不填)
Constructor getConstructor(Class<?>... parameterTypes)
// 返回所有具有public访问权限的构造函数的Constructor对象数组
Constructor<?>[] getConstructors()
下面demo:
- step1:先通过 “对象.getClass() ” 方式获得Student的 class对象,
- step2:再通过class对象获取对应的 无参 和 带参 构造方法;
- step3: 使用构造方法来获取student对象实例。
public class ReflectStudent {
Student bigBear = new Student("BigBear", 29,"男");
public static void main(String[] args){
//case1: 获取无参构造方法
Constructor<?> constructor1 = bigBear.getClass().getConstructor();
// 使用无参构造方法创建对象
Student studentByConstructor1 = (Student)constructor1.newInstance();
System.out.println("studentByConstructor1 = " + studentByConstructor1.toString());
//case2: 获取带参构造函数
Constructor<?> constructor2 = bigBear.getClass().getConstructor(String.class, Integer.class, String.class);
//使用带参构造方法创建对象
Student studentByConstructor2 = (Student)constructor2.newInstance("王铁柱", 17 , "女");
System.out.println("studentByConstructor2 = " + studentByConstructor2.toString());
}
}
对应的输出结果:
studentByConstructor1 = null年龄是 null, 性别是 null
studentByConstructor2 = 王铁柱年龄是 17, 性别是 女
2.4 获取方法(Method)
一个类里的方法,按照访问限制分类,可以分为 public型 和 非public型;按照继承关系分类,可以分为本类自己的 和 从父类继承的。通过反射获取方法时,这4种情况可以归纳到两种case里:
case1: 使用:
//获取单个指定的方法
Method method = clazz.getMethod(String methodName, Class<?>... parameterTypes);
//获取所有方法
Method[] methods = clazz.getMethods()
可以获取类中有访问权限的方法(仅public方法,包括父类中继承的方法)
case2:使用
//获取单个指定的方法
Method method = clazz.getDeclaredMethod(String methodName, Class<?>... parameterTypes)
//获取所有方法
Method[] methods = clazz.getDeclaredMethods()
可以获取类中的所有方法(public方法 + 非public方法,不包括父类中继承的方法)
对应的demo:
public class ReflectStudent {
Student bigBear = new Student("BigBear", 29,"男");
public static void main(String[] args){
// case1: 调用无参public方法:getNameWithAge()
Method method = Student.class.getMethod("getNameWithAge");
System.out.println("method_反射测试_通过【类名】获取无参public方法: " + (String)method.invoke(bigBear));
Method method01 = bigBear.getClass().getMethod("getNameWithAge");
System.out.println("method_反射测试_通过【对象】获取无参public方法: " + method01.invoke(bigBear));
// case2: 调用带参public方法: getNameWithKeyInfo(String keyInfo)
Method method1 = Student.class.getMethod("getNameWithKeyInfo", String.class);
String result2 = (String)method1.invoke(bigBear,"踏实做人, 认真做事");
System.out.println("反射测试_调用带参public方法: " + result2);
//case3: 调用带参的private方法:
try {
//case3.1 使用 getMethod: 会报 java.lang.NoSuchMethodException 异常,无法取到private方法
Class<?> classForPrivate = bigBear.getClass();
Method methodForPrivate = classForPrivate.getMethod("getAllInfo", String.class);
String resultForPrivate1 = (String)methodForPrivate.invoke(bigBear, "踏实做人, 认真做事");
System.out.println("resultForPrivate1 = " + resultForPrivate);
}catch (Exception e){
System.out.println("resultForPrivate1 error! " + e);
}
try {
//case3.2 使用 getDeclaredMethod :会报 java.lang.IllegalAccessException 异常,能取到private方法,但不能调用
Class<?> classForPrivate2 = bigBear.getClass();
Method methodForPrivate2 = classForPrivate2.getDeclaredMethod("getAllInfo", String.class);
String resultForPrivate2 = (String)methodForPrivate2.invoke(bigBear, "踏实做人, 认真做事 ");
System.out.println("resultForPrivate2 = " + resultForPrivate2);
}catch (Exception e){
System.out.println("resultForPrivate2 error! " + e);
}
//case3.3 使用 getDeclaredMethod + setAccessible 设置,能取到private方法,也能调用该方法
Class<?> classForPrivate3 = bigBear.getClass();
Method methodForPrivate3 = classForPrivate3.getDeclaredMethod("getAllInfo", String.class);
methodForPrivate3.setAccessible(true);
String resultForPrivate3 = (String)methodForPrivate3.invoke(bigBear, "踏实做人, 认真做事 ");
System.out.println("resultForPrivate3 = " + resultForPrivate3);
}
}
对应的输出结果:
// case1的结果
method_反射测试_通过【类名】获取无参public方法: BigBear的年龄是29岁.
method_反射测试_通过【对象】获取无参public方法: BigBear的年龄是29岁.
// case2的结果
反射测试_调用带参public方法: BigBear的座右铭是:踏实做人, 认真做事.
// case3的结果
// case3.1 的结果
resultForPrivate1 error! java.lang.NoSuchMethodException: reflectDemo.Student.getAllInfo(java.lang.String)
// case3.2 的结果
resultForPrivate2 error! java.lang.IllegalAccessException: Class reflectDemo.ReflectStudent can not access a member of class reflectDemo.Student with modifiers "private"
// case3.3 的结果
resultForPrivate3 = BigBear年龄是 29, 性别是 男, 座右铭是:踏实做人, 认真做事:
在case3中:
- 使用 calzz.getMethod(methodName) 方式获取私有方法,会报 java.lang.NoSuchMethodException 异常;
- 使用了 calzz.getDeclaredMethod(methodName) 方式获取私有方法,在invoke该私有方法时,会报 java.lang.IllegalAccessException 异常;
- 只有使用 calzz.getDeclaredMethod(methodName) 方式获取私有方法,并且使用method.setAccessible(true)取消访问检查,才能达到访问私有对象的目的。
对于“私有方法的访问“一个场景是写单测的时候,可以直接测试指定的私有方法逻辑,比较方便。
2.5 获取属性(Filed)
一个类里的属性字段,按照访问限制分类,可以分为 public型 和 非public型;按照继承关系分类,可以分为本类自己的 和 从父类继承的。通过反射获取方法时,这4种情况可以归纳到两种case里:
case1:
//获取单个指定的属性
Field field = clazz.getField(String name);
//获取所有的属性
Field[] fields = clazz.getFields();
获取指定name名称的属性字段(仅public字段,包括父类中继承的属性)。
case2:
//获取单个指定的属性
Field field = clazz.getDeclaredField(String name)
//获取所有的属性
Field[] fields = clazz.getDeclaredFields();
可以获取类中的所有属性字段(public + 非public字段,不包括父类中继承的属性字段)
在Student里,我们的属性都是private类型,所以这里使用getDeclaredField方法来获取对于的属性,并赋值。相应的demo:
public class ReflectStudent {
Student bigBear = new Student("BigBear", 29,"男");
public static void main(String[] args){
//使用反射的方式,给这个 happyBird 对象属性赋值
Student happyBird = new Student();
Field nameField = happyBird.getClass().getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(happyBird, "HappyBird");
Field ageField = happyBird.getClass().getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(happyBird, 28);
Field sexField = Student.class.getDeclaredField("sex");
sexField.setAccessible(true);
sexField.set(happyBird, "女");
System.out.println("通过反射给所有字段赋值后的结果:" + happyBird.toString() );
//通过反射获取字段信息
Field nameField2 = happyBird.getClass().getDeclaredField("name");
nameField2.setAccessible(true);
Object nameValue = nameField2.get(happyBird);
System.out.println("通过反射取得的name结果:name info :" + nameValue);
}
}
输出结果是:
通过反射给所有字段赋值后的结果:HappyBird年龄是 28, 性别是 女
通过反射取得的name结果:name info :HappyBird
当我们拿到一个没有set方法的对象时,需要对其相关属性赋值,可以使用上面这种方式来搞。同样的,需要注意使用method.setAccessible(true)取消访问检查,才能达到访问私有对象的目的。
2.6 通过反射创建对象
有两种方法,一种是上面提到的通过 Constructor 来创建,一种是通过class对象来创建,先看下demo:
public class ReflectStudent {
Student bigBear = new Student("BigBear", 29,"男");
public static void main(String[] args){
// case1: 通过Class对象来创建, 只能使用无参构造函数
Class<?> studentClass = bigBear.getClass();
Student studentByClassNewInstance = (Student)studentClass.newInstance();
System.out.println("studentByClassNewInstance = " + studentByClassNewInstance.toString());
// case2: 通过 Constructor 来创建, 可以使用 无参或 带参 构造函数
Constructor<?> studentConstructorWithoutPar = bigBear.getClass().getConstructor();
Student studentByConstructorWithoutPar = (Student)studentConstructorWithoutPar.newInstance();
System.out.println("studentByConstructorWithNoPra = " + studentByConstructorWithoutPar.toString());
Constructor<?> studentConstructorWithPar = bigBear.getClass()
.getConstructor(String.class, Integer.class, String.class);
Student studentByConstructorWithPar = (Student)studentConstructorWithPar.newInstance("翠花", 17 , "男");
System.out.println("studentByConstructorWithPra = " + studentByConstructorWithPar.toString());
}
}
对应的结果:
//case1
studentByClassNewInstance = null年龄是 null, 性别是 null
//case2
studentByConstructorWithNoPra = null年龄是 null, 性别是 null
studentByConstructorWithPra = 翠花年龄是 17, 性别是 男
这里有2个问题可以简单探究下:
Q1、初始化一个类,生成一个实例的时候,new与newInstance() 有什么区别?
A1: 区别在于创建对象的方式不一样,前者是使用的是类加载机制。
此外,从可伸缩、可扩展,可重用等软件思想上看:
- newInstance: 弱类型,低效率,且只能调用无参构造;不过却是实现IOC、反射、面对接口编程 和 依赖倒置 等技术方法的必然选择;
- new: 强类型,相对高效,能调用任何public构造;不过却只能实现具体类的实例化,不适合于接口编程。