1.反射的概念
1.1普通方式获取对象
定义了一个Pet类(宠物类)
实例化获取对象:Pet p = new Pet("猫","花花",2) 正射 正向
一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。我们就可以直接实例化
对于程序而言,有两个时期,编译期和运行期;编译期就是编译器帮咱们把源代码翻译成机器能识别的代码,比如编译器把java代码编译成jvm识别的字节码文件,而运行期指的是将可执行文件交给JVM去执行。
简单来说:程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性
1.2反射的简介
反射(Reflection)是java中的一个强大的特性,它允许程序在运行时查询、访问和修改类、接口、字段和方法的信息。反射提供了一种动态地操作类的能力,这在很多框架和库中被广泛使用,例如Spring框架的依赖注入。
简而言之,通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。
反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
Java 反射主要提供以下功能:
-
在运行时判断任意一个对象所属的类;
-
在运行时构造任意一个类的对象;
-
在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
-
在运行时调用任意一个对象的方法
重点:是运行时而不是编译时
1.3反射方法创建对象
反射(Reflect), 在Java中, 可以在程序运行的过程中, 动态的获取类、 获取类中的成员(属性、方法、构造方法), 并进行访问。 这里的动态获取, 指的是可以通过一个字符串进行获取。 例如: 通过一个类名字符串, 可以获取到这个类, 并进行对象的实例化。 通过属性名字字符串, 获取到一个属性, 并可以访问。 通过一个方法的名字字符串, 获取一个方法, 并进行访问。 这样的机制, 叫做反射。
简单来说,就是通过一个字符串来获取这个类,以及类里的成员信息,并进行访问(对象的实例化,方法的使用,属性的访问)。这就是反射机制。
2.Class类的介绍
1. 这个类型是用来描述java中的各种类型的,比如可以描述某一个类的类名,所有属性,所有构造器,所有的方法,以及他们的 * 修饰词,返回值类,参数类型等
2. Class这个类的每个具体实例都是用来描述某一个类型的,而且是单例的。 即比如描述String类型的Class的实例在整个系统中就有且只有一个。
3. 获取某一个类的描述类对象的方式有三种
- 对象.getClass()
//获取com.se.day_11.bDiyClass.Pet
//方法1:
Pet pet = new Pet("Cat","花花",2);
Class<? extends Pet> aClass = pet.getClass();
- 类名.class :
Class<Pet> bClass = Pet.class;
System.out.println(aClass==bClass);//true, 表示类的描述对象,是唯一的,是单例的
- Class.forName(String fullName)
public static void main(String[] args) {
Class<?> aClass1 = null;
try {
aClass1 = Class.forName("com.se.day_11.bDiyClass.Pet");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Field[] fields = aClass1.getDeclaredFields();
for (Field field : fields){
System.out.println(field.getName());
}
}
由于在反射中,重点强调动态性。 类的获取、 属性的获取、 方法的获取、 构造方法的获取, 都是要通过一个字符串进行获取的, 我们甚至可以将一个需要加载的类名写到一个配置文件中, 在程序中读取这个配置文件中的数据, 加载不同的类。 这样一来, 当需要进行不同的类加载的时候, 直接改这个配置文件即可, 其他程序不用修改。 再例如, 可以通过属性的名字, 用反射获取到对应的属性, 并进行访问; 通过方法的名字, 用反射获取对应的方法, 并进行访问。 因此, 上述的两种Class对象的获取都不够动态, 我们需要用来获取Class信息, 更多的使用的就是这个方法。
这里会出现异常: ClassNotFoundException, 就是字面意思, 没有找到这个类, 很可能是因为类的名字写错了。 这里, 通过类的名字进行类的获取, 需要写类的全限定名, 即从最外层的包开始, 逐层向内查找, 直到找到这个类。 如果是内部类, 则需要用$进行向内查询。
这个方法, 会将类加载到内存, 如果是第一次加载, 会触发这个类中的静态代码段。
3.反射机制实例化对象
在反射中的对象实例化, 和之前面向对象部分不太一样。 在反射部分, 我们更多强调的是动态, 动态的进行一个类的对象实例化。 只需要通过一个类名字符串, 即可完成对这个对象的实例化。 很多时候, 我们可以将需要加载的不同的类, 以配置文件的形式写入到一个文件中, 可以使用程序读取这个文件, 从而得到一个类的名字, 通过反射进行不同的对象的实例化。
3.1newInstance() :本质调用的是无参构造器
这个方法, 是Class类中的一个非静态的方法。 需要首先获取到Class对象, 使用这个Class对象进行方法的调用, 完成对象的实例化。
public static void main(String[] args) {
try {
//第一步:通过字符串获取指定类的描述类对象,字符串的获取使用了下面的方法
//注意:c这个变量里的地址指向的对象是描述Person类型的信息
Class<?> aClass1 = Class.forName(getClassName());
//调用Class类里的非静态方法newInstance方法来实例化对象
///注意:本质调用的是类的无参构造器。如果没有提供无参构造器,会报异常:NoSuchMethodException
Object o = aClass1.newInstance();
if (o instanceof Person){
Person p = (Person)o;
//实例化对象成功,可以调用方法了
p.play("蛋仔");
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static String getClassName(){
try(FileReader fr = new FileReader("./Reflection.properties")){
Properties p = new Properties();
p.load(fr);
String classname = p.getProperty("classname");
return classname;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
可能会出现的异常:
-
InstantiationException: 类中没有无参构造方法, 有可能是因为在类中写了有参构造方法了。
-
IllegalAccessException: 类中的无参构造方法的访问权限不足, 在类外无法进行访问。
3.2getConstructor(........parameter):使用发射机制,调用指定的构造器获取某一个类的对象,即有参数的构造器
使用上述的方法获取类全名。
public static void main(String[] args) {
try {
//第一步:通过字符串获取指定类的描述类对象,字符串的获取使用了下面的方法
//注意:c这个变量里的地址指向的对象是描述Person类型的信息
Class<?> aClass1 = Class.forName(getClassName());
//如何指定想要的构造器:方法中传入参数类型的描述类对象
Constructor<?> constructor = aClass1.getConstructor(String.class, String.class);
//使用构造器的newInstance(Object .....parameter)给构造器的参数赋值即可
Object o = constructor.newInstance("1236548974", "michael");
if (o instanceof Person){
Person p = (Person)o;
//实例化对象成功,可以调用方法了
p.play("蛋仔");
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException |
InvocationTargetException e) {
throw new RuntimeException(e);
}
}
3.3getDeclaredConstructor :使用权限不足的构造器来获取
public static void main(String[] args) {
try {
//第一步:通过字符串获取指定类的描述类对象,字符串的获取使用了下面的方法
//注意:c这个变量里的地址指向的对象是描述Person类型的信息
Class<?> aClass1 = Class.forName(getClassName());
//如何指定想要的构造器:方法中传入参数类型的描述类对象
Constructor<?> constructor = aClass1.getDeclaredConstructor(String.class, String.class, int.class);
//如果访问的是私有的,需要关闭权检验, true 表示关闭,false表示开启
constructor.setAccessible(true);//关闭权检验
Object o = constructor.newInstance("741258963321456", "tom", 20);
if (o instanceof Person){
Person p = (Person)o;
//实例化对象成功,可以调用方法了
p.play("蛋仔");
}
System.out.println(o);//idcard='741258963321456, name='tom,age=20gender= isMarry=false
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException |
InvocationTargetException e) {
throw new RuntimeException(e);
}
}
3.4 getConstructors()(返回一个数组)获取一个类中所有的构造方法
public static void main(String[] args) {
try {
//先获取描述类的对象,描述Person类型
Class<?> aClass = Class.forName(getClassName());
//获取所有的构造器
Constructor<?>[] constructors = aClass.getConstructors();
//遍历数组
for (Constructor constructor:constructors){
//获取构造器的修饰词
System.out.println("构造器的修饰词:"+constructor.getModifiers());
//获取构造器的名字
System.out.println("构造器的名字:"+constructor.getName());
//获取构造器具的参数
Parameter[] parameters = constructor.getParameters();
for (Parameter parameter :parameters){
//获取参数类型
System.out.println("参数类型:"+parameter.getType());
//获取参数的名字
System.out.println("参数的名字:"+parameter.getName());
}
System.out.println("----------------------- ");
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
3.5获取一个构造器的权限getModifiers()、参数getParameters()(返回一个数组)、异常getExceptionTypes()(返回一个数组)
案例演示:如上
4.成员的访问
4.1属性的访问:
获取属性的修饰词getModifiers(),类型getType(),名称getName()
public static void main(String[] args) {
try {
//先获取描述类的对象,描述Person类型
Class<?> aClass = Class.forName(getClassName());
Field[] fields = aClass.getDeclaredFields();
for (Field field:fields){
//获取属性的修饰词
System.out.println("修饰词"+field.getModifiers());
//获取属性的类型
System.out.println("类型"+field.getType());
//获取属性的名字
System.out.println("属性名:"+field.getName());
}
//假如不知道有哪些属性,可以指定一个属性,查看是否返回正确
//如果不存在,抛NoSuchFieldException
Field gender = aClass.getDeclaredField("gender");
System.out.println(gender);//private char com.se.day_12.Vo.Person.gender
} catch (ClassNotFoundException | NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
4.2修饰词对应的是常量 public:1 private:2 protected:4 static:8 final:16 synchronized:32
4.3方法的访问
获取方法的修饰词,返回值类型getReturnType(),方法名,参数类型,参数名
public static void main(String[] args) {
try {
//先获取描述类的对象,描述Person类型
Class<?> aClass = Class.forName(getClassName());
// 获取一个类中所有的方法,包括私有的
Method[] methods = aClass.getDeclaredMethods();
for (Method method : methods){
System.out.println("方法的修饰词:"+method.getModifiers());
System.out.println("方法的返回值类型:"+method.getReturnType());
System.out.println("方法的名字:"+method.getName());
Parameter[] parameters = method.getParameters();
for (Parameter parameter:parameters){
System.out.println("参数类型:"+parameter.getType());
System.out.println("参数名:"+parameter.getName());
}
}
//假如不知道有哪些属性,可以指定一个属性,查看是否返回正确
//如果不存在,抛NoSuchFieldException异常
Method setAge = aClass.getDeclaredMethod("setAge", int.class);
System.out.println(setAge);//public void com.se.day_12.Vo.Person.setAge(int)
} catch (ClassNotFoundException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
4.3获取到的属性初始化、属性的重新赋值(调用提供的set方法进行修改值)调用静态方法和非静态方法、私有的方法。
属性与对象关联set(object obj,Object value)
方法与对象关联invoke(Object obj , Object ....args)
public static void main(String[] args) {
try {
//先获取描述类的对象,描述Person类型
Class<?> aClass = Class.forName(getClassName());
//获取无参构造器,实例化对象
Object o = aClass.newInstance();
//获取一个属性,实例化之后,属性都是默认值,需要进行初始化覆盖默认值
Field name = aClass.getDeclaredField("name");//私有属性,
//关闭校验
name.setAccessible(true);
//与对象关联上
name.set(o,"小明");
Field age = aClass.getDeclaredField("age");
age.setAccessible(true);
age.set(o,20);
Field gender = aClass.getDeclaredField("gender");
gender.setAccessible(true);
gender.set(o,'男');
System.out.println(o);//name='小明 age=20 gender=男
//调用方法和属性值得重新赋值
Method setGender = aClass.getDeclaredMethod("setGender", char.class);//公有方法不用关闭校验
//把方法与对象关联上
setGender.invoke(o,'女');
System.out.println(o);//name='小明 age=20 gender=女
//调用私有的sleep方法
Method sleep = aClass.getDeclaredMethod("sleep", String.class);
sleep.setAccessible(true);
sleep.invoke(o,"床");//小明喜欢躺在床睡觉
//调用静态的方法
Method sum = aClass.getDeclaredMethod("sum", int.class, int.class);
Object invoke = sum.invoke(null, 2, 3);
System.out.println(invoke);//5
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchFieldException |
NoSuchMethodException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
注意:在访问属性和调用方法时,私有的一定要关闭校验setAccessible(true)
获取单个属性:描述类对象.getDeclaredField(String 属性名)
属性与对象关联:调用set(object obj,Object value)
获取单个方法:描述类对象.getDeclaredMethod(String 方法名,参数类型.class,.......)
方法与对象关联:invoke(Object obj , Object ....args)