Java反射机制
前言
本文参考了https://blog.csdn.net/sinat_38259539/article/details/71799078
一、反射的概述
反射是框架设计的灵魂
(使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码))
就像人照镜子可以看到人的整体,五官等结构,通过反射我们也可以看到类的完整结构,所以说Class对象就像是一面镜子一样。
反射的定义
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
比如之前在没有使用反射的时候,我们这里有一个类,如果我们想要调用里面的属性,方法等,我们要先创建一个对象,才可以调用里面的属性等。类似的,我们如果使用反射,想要使用一个类的成员变量,方法等,我们就要先获取一个Class对象,这个Class对象里面有类的成员变量,方法等信息,这样我们才可以进行相关的操作
java.lang.Class:代表整个字节码,代表一个类型,代表整个类。
java.lang.reflect.Method:代表字节码中的方法字节码。代表类中的方法。
java.lang.reflect.Constructor:代表字节码中的构造方法字节码。代表类中的构造方法
java.lang.reflect.Field:代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。
java.lang.Class:
我们可以把下面这些代码看成一个整体,属于Class里面的内容
public class User{
// Field
int no;
// Constructor
public User(){
}
public User(int no){
this.no = no;
}
// Method
public void setNo(int no){
this.no = no;
}
public int getNo(){
return no;
}
}
初步了解反射以后,在使用它之前,我们来想想反射有什么用,以及我们为什么要使用反射
反射的作用
通过java语言中的反射机制可以操作字节码文件。
通过反射机制可以操作代码片段。(class文件。)
java的反射机制就是增加程序的灵活性,避免将程序写死到代码里
例如: 实例化一个 person()对象, 不使用反射, ,就要调用构造器来实例化对象,new person(); 如果有一天,我们想要实例化其他对象, 那么必须修改源代码,并重新编译 。
使用反射: class.forName(“person”).newInstance(); 而且这个类描述可以写到配置文件中,如 **.xml, 这样如果想实例化其他类,只要修改配置文件的"类描述"就可以了,不需要重新修改代码并编译。
反射在框架用的很多,我们可以通过外部文件的配置,在不修改源代码的情况下,来控制程序,符合OCP开闭原则
反射的缺点是影响效率
反射的应用场合
有的时候,比如说我们想要创建一个对象, 但是
在编译时根本无法知道该对象或类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息.
在运行阶段使用,不能写死;工厂模式,动态生成对象;框架底层;运行过程中修改jar包中的一些内容(由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。)
二、Java反射机制
反射相关的类在java.lang.reflect这个包中
反射机制原理示意图
Java Reflection
反射机制可以完成下面的功能:
下面是反射相关的主要类
接下来,根据上面提供的和反射相关的类,进行相应的测试
package com.zyh.java;
import java.io.File;
import java.io.FileReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Properties;
/**反射的引入
* @author zengyihong
* @create 2022--02--27 19:17
*/
public class ReflectionQuestion {
public static void main(String[] args) throws Exception {
/**
* 根据配置文件 re.properties指定信息
* 创建Cat对象,并调用hi方法
*/
//传统方式 new对象-->调用方法
// Cat cat=new Cat();
// cat.hi();
//尝试做一下,理解反射
//1.Properties可以读取配置文件
Properties properties=new Properties();
File file=new File("day11/src/re.properties");
FileReader reader=new FileReader(file);
//把文件加载进来
properties.load(reader);
String classfullpath = properties.getProperty("classfullpath");
String methodName = properties.getProperty("method");
System.out.println("classfullpath="+classfullpath);
System.out.println("method="+methodName);
//2.创建对象 传统的方法,解决不了
//3.使用反射机制解决
System.out.println("---------------利用反射机制创建对象和调用方法-------------");
//(1)加载类,返回Class类型的对象
//参数的路径要写类的全类名,包名也得加上
Class aClass = Class.forName(classfullpath);
//(2)通过aclass得到我们加载的类 com.zyh.java.Cat的对象实例
Object instance = aClass.newInstance();
//运行类型
System.out.println("运行类型"+instance.getClass());
//(3)通过aclass得到我们加载的类 com.zyh.java.Cat 的methodName 的方法对象
//在反射中,可以把方法视为对象(万物皆对象)
Method method1 = aClass.getMethod(methodName);
//(4)通过method1 调用方法:即通过方法对象来调用方法
//传统方法:对象.方法() 反射:方法.invoke(对象)
method1.invoke(instance);
//getField不能得到私有的属性
Field nameField = aClass.getField("age");
System.out.println(nameField.get(instance));
//getConstructor 代表类的构造方法
Constructor constructor = aClass.getConstructor();
//public com.zyh.java.Cat()
System.out.println(constructor);
Constructor constructor1 = aClass.getConstructor(String.class);
//public com.zyh.java.Cat(java.lang.String)
System.out.println(constructor1);
}
}
class Cat{
private String name="招财猫";
public int age=10;
public Cat() {
}
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
public Cat(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void hi(){
System.out.println("你好:我是"+name);
}
}
通过不同阶段,来获取对象实例
反射使用步骤
反射 API 用来生成 JVM 中的类、接口或则对象的信息。
- Class 类:反射的核心类,可以获取类的属性,方法等信息。
- Field 类:Java.lang.reflec 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性 值。
- Method 类: Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或 者执行方法。
- Constructor 类: Java.lang.reflec 包中的类,表示类的构造方法。
- 获取想要操作的类的 Class 对象是反射的核心,通过 Class 对象我们可以任意调用类的方法。
- 调用 Class 类中的方法,既就是反射的使用阶段。
- 使用反射 API 来操作这些信息。
三、 Class类
基本介绍
package com.hspedu.reflection.class_;
import com.hspedu.Cat;
import java.util.ArrayList;
/**
* 对 Class 类特点的梳理
**/
public class Class01 {
public static void main(String[] args) throws ClassNotFoundException {
//看看 Class 类图
//1. Class 也是类,因此也继承 Object 类
//Class
//2. Class 类对象不是 new 出来的,而是系统创建的
//(1) 传统 new 对象
/* ClassLoader 类
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
*/
//Cat cat = new Cat();
/*
ClassLoader 类, 仍然是通过 ClassLoader 类加载 Cat 类的 Class 对象
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
*/
Class cls1 = Class.forName("com.zyh.java.Cat");
//3. 对于某个类的 Class 类对象,在内存中只有一份,因为类只加载一次
Class cls2 = Class.forName("com.zyh.java.Cat");
System.out.println(cls1.hashCode());
System.out.println(cls2.hashCode());
Class cls3 = Class.forName("com.zyh.java.Dog");
System.out.println(cls3.hashCode());
}
}
常用方法
package com.zyh.java;
import java.lang.reflect.Field;
/**
* @author zengyihong
* @create 2022--02--28 13:36
*/
public class ClassMethod {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//类的全路径
String fullpath="com.zyh.java.Car";
//通过Class.forName获得Class对象
Class carClass=Class.forName(fullpath);
//com.zyh.java.Car
System.out.println(carClass.getName());
//获取该对象的实例
Object instance = carClass.newInstance();
//看出运行类型为 class com.zyh.java.Car
System.out.println(instance.getClass());
//getName返回这个对象的实体 class com.zyh.java.Car
System.out.println(instance.getClass());
//得到包名 package com.zyh.java
System.out.println(carClass.getPackage());
//返回所有字段
Field[] fields = carClass.getFields();
for (Field f:fields){
System.out.println(f.getName());
}
//通过反射得到属性brand
Field brand = carClass.getField("brand");
System.out.println(brand.getName());
//通过反射获取属性值
//宝马
Object o = brand.get(instance);
System.out.println(o);
//通过反射来修改属性值
brand.set(instance,"奔驰");
//奔驰
System.out.println(brand.get(instance));
}
}
获取Class类对象
package com.zyh.java;
/**
* 获取Class对象的方式
*
* @author zengyihong
* @create 2022--02--28 8:41
*/
public class GetClass_ {
public static void main(String[] args) throws ClassNotFoundException {
//方式1:Class.forName
//这一步通常是通过配置文件读取
String path= "com.zyh.java.Car";
Class<?> aClass = Class.forName(path);
System.out.println(aClass);
//方式2:类名.class 多用于参数传递
Class catClass = Car.class;
System.out.println(catClass);
//方式3:对象名.getClass() 应用场景:有对象实例
Car car=new Car();
Class aClass1 = car.getClass();
System.out.println(aClass1);
//方式4:通过类加载器(4)来获取Class类对象
//先得到类加载器car
ClassLoader classLoader = car.getClass().getClassLoader();
//通过类加载器得到Class对象 把类的全路径写上去
Class<?> aClass2 = classLoader.loadClass(path);
System.out.println(aClass2);
Class<Integer> integerClass = int.class;
System.out.println(integerClass);
Class<Integer> type = Integer.TYPE;
System.out.println(type);
System.out.println(type==integerClass ) ;//true
}
}
对象实例化的方式
Class 对象的 newInstance()
1. 使用 Class 对象的 newInstance()方法来创建该 Class 对象对应类的实例,但是这种方法要求
该 Class 对象对应的类有默认的空构造器。 否则,编译不报错,运行报错
调用 Constructor 对象的 newInstance()
2. 先使用 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance()
方法来创建 Class 对象对应类的实例,通过这种方法可以选定构造方法创建实例。
//获取 Person 类的 Class 对象
Class clazz=Class.forName("reflection.Person");
//使用.newInstane 方法创建对象
Person p=(Person) clazz.newInstance();
//获取构造方法并创建对象
Constructor c=clazz.getDeclaredConstructor(String.class,String.class,int.class);
//创建对象并设置属性
Person p1=(Person) c.newInstance("李四","男",20);
通过反射获取类的相关信息
第一组: java.lang.Class 类
package com.zyh.java;
import java.lang.reflect.Field;
/**
* @author zengyihong
* @create 2022--02--28 14:37
*/
public class ClassMethod1 {
public static void main(String[] args) throws Exception{
Class aClass=Class.forName("com.zyh.java.Person");
//获取全类名
System.out.println(aClass.getName());
//获取简单名字
System.out.println(aClass.getSimpleName());
//获取所有public修饰的属性,包括本类以及父类的
Field[] fields = aClass.getFields();
for (Field f :fields) {
System.out.println("本类以及父类的属性:"+f.getName());
}
//获取本类所有属性,包括public和非public
Field[] declaredFields = aClass.getDeclaredFields();
for(Field f:declaredFields){
System.out.println("所有的属性包括public和非public的:"+f.getName());
}
}
}
class A {
public String hobby;
}
class Person extends A{
public String name;
protected int age;
String job;
private double sal;
//方法
public void m1() {
}
protected void m2() {
}
void m3() {
}
private void m4() {
}
public Person() {
}
public Person(String name, int age, String job, double sal) {
this.name = name;
this.age = age;
this.job = job;
this.sal = sal;
}
}
第二组: java.lang.reflect.Field 类
第三组: java.lang.reflect.Method 类
第四组: java.lang.reflect.Constructor 类
四、类加载
基本说明
静态成员的初始化和类加载有关
编译的时候,很明显会报错,这就体现了静态加载,不过我们输入的值也有可能是1,2,或者其他值,不一定会输入1,也就是说在运行的时候,可能没有用到Dog这个类,但是它在编译的时候就会加载这个类,不管我们有没有使用
这样子编译通过了,我们使用了反射,反射是动态加载,如果我们没有输入2,程序没有执行到那里,就不会真正加载Person类,所以虽然程序中此时没有Person类,也不会报错
动态加载就类似于延迟加载
类加载时机
类加载过程图
验证:主要是对我们的文件进行安全的校验,比如说文件格式是否正确,元数据验证是否正确,符号引用是否正确,字节码是否正确
准备:对静态变量进行分配内存并且完成默认初始化
解析:虚拟机会把常量池的符号引用替换成直接引用
初始化:真正执行类中定义的Java代码
加载阶段和准备阶段是由JVM来控制的,程序员是无法控制的,初始化,程序员可以控制,比如说A类有静态代码块,我们在里面写什么,完全是由我们来控制的
类加载各个阶段完成的任务
加载阶段
连接阶段—验证
连接阶段—准备
package com.hspedu.reflection.classload_;
/**
* 我们说明一个类加载的链接阶段-准备
*/
public class ClassLoad02 {
public static void main(String[] args) {
}
}
class A {
//属性-成员变量-字段
// 分析类加载的链接阶段-准备 属性是如何处理
//1. n1 是实例属性, 不是静态变量,因此在准备阶段,是不会分配内存
//2. n2 是静态变量,分配内存 n2 是默认初始化 0 ,而不是 20
//3. n3 是 static final 是常量, 他和静态变量不一样, 因为一旦赋值就不变 n3 = 30
public int n1 = 10;
public static int n2 = 20;
public static final int n3 = 30;
}
连接阶段-解析
在编译的时候,A类和B类还没有真正加载到内存,还没有分配真正空间,这个时候是以符号的方式,记录的是相对的引用
假设这里有符号,1和2,这就说1引用了2
一旦到了类加载,就会有A类的Class对象和B类的Class对象
因为原来编译的时候,他们是符号引用,他们还没有真正加载到内存,没有实际的内存地址,只能按符号的方式来记录
比如说现在有两个人,他们之间的距离刚开始是以一种相对的方式来记录,如果放在地球上,为了更精确记录他们之间的关系,用经度和纬度来记录他们之间的关系
现在再回到刚刚讲的A类和B类
当他们加载到内存中,A类和B类都有真正的内存地址,它们之间就通过这个内存地址来实现相互之间的引用,这就是符号引用替换成直接引用
Initialization(初始化)
package com.hspedu.reflection.classload_;
// 演示类加载-初始化阶段
public class ClassLoad03 {
public static void main(String[] args) throws ClassNotFoundException {
//1. 加载 B 类,并生成 B 的 class 对象
//2. 链接 num = 0
//3. 初始化阶段
//依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并合并
/*
clinit() {
System.out.println("B 静态代码块被执行");
//num = 300;
num = 100;
}
//合并: num = 100
*/
//new B();//类加载
//System.out.println(B.num);//100, 如果直接使用类的静态属性,也会导致类的加载
//看看加载类的时候,是有同步机制控制
/*
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//正因为有这个机制,才能保证某个类在内存中, 只有一份 Class 对象
synchronized (getClassLoadingLock(name)) {
//....
}
}
*/
B b = new B();
}
}
class B {
static {
System.out.println("B 静态代码块被执行");
num = 300;
}
static int num = 100;
public B() {//构造器
System.out.println("B() 构造器被执行");
}
}
五、总结
动态语言
动态语言,是指程序在运行时可以改变其结构:新的函数可以引进,已有的函数可以被删除等结
构上的变化。比如常见的 JavaScript 就是动态语言,除此之外 Ruby,Python 等也属于动态语言,
而 C、C++则不属于动态语言。从反射角度说 JAVA 属于半动态语言。
反射机制概念 (运行状态中知道类所有的属性和方法)
在 Java 中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;
并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方
法的功能成为 Java 语言的反射机制。
反射的应用场合
编译时类型和运行时类型
在 Java 程序中许多对象在运行是都会出现两种类型:编译时类型和运行时类型。 编译时的类型由声明对象时实用的类型来决定,运行时的类型由实际赋值给对象的类型决定 。如:
Person p=new Student();
其中编译时类型为 Person,运行时类型为 Student。的编译时类型无法获取具体方法
程序在运行时还可能接收到外部传入的对象,该对象的编译时类型为 Object,但是程序有需要调用
该对象的运行时类型的方法。为了解决这些问题,程序需要在运行时发现对象和类的真实信息。
然而,如果编译时根本无法预知该对象和类属于哪些类,程序只能依靠运行时信息来发现该对象
和类的真实信息,此时就必须使用到反射了。
- 点赞
- 收藏
- 关注作者
评论(0)