经常听到java 反射,自己也看过一些文章和视频,但总是感觉没有很好地理解,所以结合了自己觉得比较好的文章(主要是我能理解的),自己又总结了一下,作为记录。
java反射
前言
我们都知道,JVM加载的是.class文件。其实,类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
同时,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器会在程序首次主动使用该类时会生成错误报告(LinkageError错误),如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。
举例Object o=new Object();
例如要运行一段代码 Object o=new Object();
其运行过程:
首先JVM会启动,你的代码会编译成一个.class文件,然后被类加载器加载进jvm的内存中,你的类Object加载到方法区中,创建了Object类的class对象到堆中,注意这个不是new出来的对象,而是类的类型对象,每个类只有一个class对象,作为方法区类的数据结构的接口。jvm创建对象前,会先检查类是否加载,寻找类对应的class对象,若加载好,则为你的对象分配内存,初始化也就是代码:new Object()。
上面的流程就是你自己写好的代码扔给jvm去跑,跑完就over了,jvm关闭,你的程序也停止了。
理解了上面的过程,再理解反射就比较容易了。
一、反射的概述
Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。
反射提供的主要功能:
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
- 在运行时调用任意一个对象的方法;
- 生成动态代理.(下篇再讲)
重点:是运行时而不是编译时
二、获取class文件对象的方式
反射是对一个类进行解剖,要想解剖一个类,必须先要获取到该类的字节码文件对象。
而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。
再强调一下, 咱们写的代码是存储在后缀名.java文件中的,它会被编译成.class文件,jvm真正执行的是.class文件。Java是面向对象的语言,一切皆为对象,所以java认为编译后的.class文件也是对象,给抽象成了一个类,这个类就是Class。
通过文档了解到,使用forName(String className)方法就可以得到想要反射的类。
获取class文件对象的有三种方式,具体如下:
(1)使用Class类的forName()静态方法
Class perClass = Class.forName("com.diligentkong.bean.Person");
System.out.println(perClass);
输出:class com.diligentkong.bean.Person
(2)直接获取某一个对象的class
Person person = new Person(); // new一个Person对象
Class perClass = person.getClass();// 获取Class对象
System.out.println(perClass);
//结果输出:class com.diligentkong.bean.Person
(3)调用某个对象的getClass()方法
Class perClass = Person.class;
System.out.println(perClass);
输出:class com.diligentkong.bean.Person
三、反射的基本运用
1.获取构造器信息
通过Class类的getConstructor方法得到Constructor类的一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例:
Class perClass = Class.forName("com.diligentkong.bean.Person");
Constructor constructor = perClass.getConstructor(String.class,int.class);//获取有参构造函数
可以根据传入的参数来调用对应的构造函数。
2.通过反射来生成对象
1 .使用Class对象的newInstance()方法来创建Class对象对应类的实例。
Class perClass = Class.forName("com.diligentkong.bean.Person");
//调用 Person 的无参数构造方法,创建此Class对象所表示的类的一个新实例
Person person = (Person) perClass.newInstance();
System.out.println(person);
输出:Person{name='null', age=0}
2.先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。
Class perClass = Class.forName("com.diligentkong.bean.Person");
Constructor constructor = perClass.getConstructor(String.class,int.class);//获取有参构造函数
Person p = (Person) constructor.newInstance("kong",22);
System.out.println(p); //Person{name='kong', age=22}
3.获取方法
获取某个Class对象的方法集合,主要有以下几个方法:
1.获取所有的:
-
public Method[] getMethods():获取所有"公有(public)方法";(包含了父类的方法也包含Object类)
-
public Method[] getDeclaredMethods():方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
2.获取单个的: 方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。
-
public Method getMethod(String name,Class<?>... parameterTypes):
- public Method getDeclaredMethod(String name,Class<?>… parameterTypes)
参数:
name : 方法名;
Class … : 形参的Class类型对象
调用方法:
public Object invoke(Object obj,Object… args):
参数说明:
obj : 要调用方法的对象;
args:调用方式时所传递的实参;
实例代码:
public class Person {
private String name;
private int age;
public void run(){
System.out.println("I can run ....");
}
protected void eat(int num){
System.out.println("我今天吃了"+num+"顿大餐,哈哈哈!");
}
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) throws Exception {
//获取Class对象
Class perClass = Class.forName("com.diligentkong.bean.Person");
Constructor constructor = perClass.getConstructor(String.class,int.class);//获取有参构造函数
Person p = (Person) constructor.newInstance("kong",22);
System.out.println(p); //Person{name='kong', age=22}
// 获取所有公有方法
System.out.println("------------------获取所有“公有”方法-------------");
Method[] methodArray = perClass.getMethods();
for (Method m: methodArray){
System.out.println(m);
}
System.out.println("----------------获取所有的方法,包括私有的---------");
Method[] declaredMethods = perClass.getDeclaredMethods();
for (Method m : declaredMethods){
m.setAccessible(true); //解除私有限定
System.out.println(m);
}
System.out.println("----------------获取公有的run()方法-------------------");
Method m = perClass.getMethod("run");
m.invoke(p);
System.out.println(m);
System.out.println("-------------------获取私有的eat()方法----------------------------");
Method promethod = perClass.getDeclaredMethod("eat",int.class);
promethod.setAccessible(true);
promethod.invoke(p,3);
}
结果:
4.获取类的成员变量信息
getFiled:访问公有的成员变量
getDeclaredField:所有已声明的成员变量,但不能得到其父类的成员变量
示例代码:
在Person中加入如下字段:
public int i;
public int j;
Class perClass = Class.forName("com.diligentkong.bean.Person");
Constructor constructor = perClass.getConstructor(String.class,int.class);//获取有参构造函数
Field field = perClass.getDeclaredField("age");
System.out.println("-------------访问所有的属性,包括public属性-------");
Field[] fields = perClass.getDeclaredFields();
for (Field f: fields){
System.out.println(f.getName());
}
System.out.println("--------------获取public属性--------------");
Field[] privateFields = perClass.getFields();
for (Field f: privateFields){
System.out.println(f.getName());
}
System.out.println("--------------获取获取特定的属性:age--------------");
Field ff = perClass.getDeclaredField("age");
System.out.println(ff);
System.out.println("----------------获取特定的属性: i------------");
Field ff2= perClass.getField("i");
System.out.println(ff2);
System.out.println("---------------------");
//实例化这个类,赋值给p1
Person p1 = (Person) perClass.newInstance();
//解除私有限定
field.setAccessible(true);
// 给p1对象的age属性赋值20
field.set(p1,20);
//获取属性age
System.out.println(""+field.get(p1));
}
结果:
5.通过反射越过泛型检查
例如:ArrayList的一个对象,在这个集合中添加一个字符串数据,
// 泛型只在编译期有效,在运行期会被擦出掉
ArrayList<Integer> list = new ArrayList<>();
list.add(100);
list.add(200);
Class clazz = Class.forName("java.util.ArrayList");
Method m = clazz.getMethod("add",Object.class);
m.invoke(list,"aaa");
System.out.println(list); //[100, 200, aaa]
好了,暂时总结这些,有新的认识再去补充。
参考的博文:
https://www.zhihu.com/question/24304289
https://www.sczyh30.com/posts/Java/java-reflection-1/