1. Java反射机制概述
- Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
- 动态语言
是一种在运行的时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。 主要动态语言:Object-C、C#、javaScript、PHP、python
- 静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。 java不是动态语言,但Java可以称之为”准动态语言“。即Java有一定的动态性,我们可以利用反射的机制、字节码操作获得类似动态语言的特性。 Java的动态性让编程的时候更灵活!
- 加载完类之后,在堆内存的方法区中就产生了一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
正常方式: 引入需要的包类名称->通过new实例化->取得实例化对象
反射方式:实例化对象->getClass()方法->得到完整的”包类”名称
Java反射机制研究与应用
Java反射机制提供的功能
- 在运行时判断任意一个对象所属类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
-
反射相关主要API
java.lang.Class:代表一个类
- java.lang.reflect.Method: 代表类的方法
- java.lang.reflect.Field: 代表类的成员变量
- java.lang.reflect.Constructor: 代表类的构造器
2. 理解Class类并获取Class实列
2.1 Class类简述
- 在 Object 类中定义了以下的方法,此方法将被所有子类继承
public final Class getClass()
- 以上的方法返回值的类型是一个 Class 类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即可以通过对象反射求出类的名称。
- 对象使用反射后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含了特定某个结构( class/interface/enum/annotation/primitive type/void/[])的有关信息。
- 程序经过javac.exe命令以后,会生成一个或者多个字节码文件(.class)结尾,接着我们使用java.exe命令对某个字节码文件进行解释运行,相当于某个字节码文件加载到内存中。此过程就成为类的加载。加载到内存中的类,就称为运行时类,此运行时类就作为Class的一个实例。
- 换句话说,Class的实列就对应着一个运行时的类
- 加载到内存中的运行时类,会缓存一定的时间,在此时间内,可以通过不同方式来获取此运行时的类。
2.3 获取Class的实列方式
@Test
public void test3() throws ClassNotFoundException {
// 方式一:调用运行时类的属性:.class
Class clazz = Person.class;
// 方式二:通过运行时类的对象,调用getClass()
Person person = new Person();
Class clazz1 = person.getClass();
System.out.println(clazz1);
// 方式三:调用Class的静态方法:forName(String classPath)
Class clazz2 = Class.forName("com.ait.reflection.Person");
System.out.println(clazz2);
// 方式四:使用类加载器:ClassLoader 需要类全路径
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz3 = classLoader.loadClass("com.ait.reflection.Person");
}
创建对象的几种方式
- new + 构造器
- 要创建Xxx类的对象,可以考虑:Xxx、Xxxs、XxxFactory、XxxBuilder类中查看是否有静态方法的存在。可以调用其静态方法,创建Xxx对象。
- 通过反射
2.4 反射案例
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
public int age;
private Person(String name) {
this.name = name;
}
public void show() {
System.out.println("你好,我是一个人");
}
private String showNation(String nation) {
System.out.println("我的国籍是:" + nation);
return nation;
}
}
@Test
public void test1(){
// 1.创建Person类的对象
Person p1 = new Person("Tom", 13);
// 2.通过对象调用内部的属性、方法
p1.age=10;
System.out.println(p1.toString());
p1.show();
// 在Person类外部,不可通过Person类的对象调用私有结构
// 比如私有的 name、showNation(),以及私有的构造器
}
@Test
public void test2() throws Exception {
Class clazz = Person.class;
// 1.通过反射,创建Person类的对象
Constructor cons = clazz.getConstructor(String.class,int.class);
Object obj = cons.newInstance("Tom", 13);
System.out.println(obj.toString());
// 2.通过反射,调用对象指定的属性方法
// 调用属性
Field age = clazz.getDeclaredField("age");
age.set(obj,10);
System.out.println(obj.toString());
// 调用方法
Method show = clazz.getDeclaredMethod("show");
show.invoke(obj);
// 3.通过反射,可以调用Person类的私有结构的。比如私有构造器,方法、属性
// 私有构造器
Constructor cons1 = clazz.getDeclaredConstructor(String.class);
cons1.setAccessible(true);
// 调用 newInstance() 创建对应的运行时类对象,实际上是调用类的无参构造器
Person p1 = (Person) cons1.newInstance("jerry");
System.out.println(p1);
// 调用私有的属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1,"王五");
System.out.println(p1);
// 调用私有的方法
Method showNation = clazz.getDeclaredMethod("showNation", String.class);
showNation.setAccessible(true);
// 相当于p1.showNation
showNation.invoke(p1,"中国");
}
2.5 哪些类型可以有Class对象
- class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
- interface:接口
- []:数组
- enum: 枚举
- annotation: 注解@interface
- primitve Type: 基本数据类型
void:无返回值
@Test
public void test3(){
Class<Object> c1 = Object.class;
Class<Comparable> c2 = Comparable.class;
Class<String[]> c3 = String[].class;
Class<int[][]> c4 = int[][].class;
Class<ElementType> c5 = ElementType.class;
Class<Override> c6 = Override.class;
Class<Integer> c7 = int.class;
Class<Void> c8 = void.class;
Class<Class> c9 = Class.class;
int[] i1 = new int[10];
int[] i2 = new int[100];
Class<? extends int[]> c10 = i1.getClass();
Class<? extends int[]> c11 = i2.getClass();
// 只要数组的元素类型与维度一样,就是同一个Class
System.out.println(c10 == c11);//true
}
3. 类的加载与ClassLoad的理解(了解)
3.1 类加载过程
- 加载:将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的 java.lang.Class 对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个 Class 对象。这个加载的过程需要类加载器参与。
- 链接:将 Java 类的二进制代码合并到JVM的运行状态之中的过程。
- 验证:确保加载的类信息符合JVM规范,例如:以 cafe 开头,没有安全方面的问题。
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
- 初始化:
- 执行类构造器
() 方法的过程。类构造器 () 方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。 - 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 虚拟机会保证一个类的
() 方法在多线程环境中被正确加锁和同步。 ```java public class ClassLoadingTest{ public static void main (String [] args){
} }System.out.println(test.m);
class test{
static {
m = 300;
}
static int m = 100;
}
//第一步:加载
//第二步:链接结束后m=0
//第三步:初始化结束后,m的值由
<a name="Z630C"></a>
## 3.2 Java类编译、运行的执行的流程
<br /> <br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/12426545/1656781180787-6fa24f41-3d5b-4233-b18c-550dca66e993.png#clientId=u31b0c56e-8ee5-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=278&id=FLodz&margin=%5Bobject%20Object%5D&name=image.png&originHeight=556&originWidth=1614&originalType=binary&ratio=1&rotation=0&showTitle=false&size=357172&status=done&style=none&taskId=u7448523b-6517-400b-82eb-05e1b640039&title=&width=807)
<a name="ykssV"></a>
## 3.3 类加载器作用
- 将 `class `文件字节码内容加载到内存中,并将这些静态数据`转换成方法区的运行时数据结构`,然后在堆中生成一个代表这个类的`java.lang.Class` 对象,作为方法区中类数据的访问入口。
- 类缓存:标准的 JavaSE 类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些 Class 对象
<a name="Tk3wE"></a>
## 3.4 ClassLoad 简介
略,具体可百度双亲委派,JVM知识<br />![](https://cdn.nlark.com/yuque/0/2022/webp/12426545/1656781513079-b0d05308-dada-46a0-9cf9-ceee8d047ace.webp#clientId=u31b0c56e-8ee5-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=T29kl&margin=%5Bobject%20Object%5D&originHeight=263&originWidth=773&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=uf2bf2327-cb8e-408d-96a2-a15a7777526&title=)
<a name="Wvq22"></a>
# 4. 创建运行时的类对象
```java
@Test
public void test1() throws Exception {
//方式一
Class<Person> clazz1 = Person.class;
//方式二
Class<Person> clazz2 = (Class<Person>) Class.forName("cn.bruce.java.Person");
Person person1 = clazz1.newInstance();
Person person2 = clazz2.newInstance();
System.out.println(person1);
System.out.println(person2);
}
newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。
要想此方法正常的创建运行时类的对象,要求:
- 运行时类必须提供空参的构造器
- 空参的构造器的访问权限得够。通常,设置为 public。
在 javabean 中要求提供一个 public 的空参构造器。原因:
- 便于通过反射,创建运行时类的对象
便于子类继承此运行时类时,默认调用 super() 时,保证父类此构造器
5. 获取运行时类的完整结构
public class Creature<T> implements Serializable {
private char gender;
public double weight;
private void breath(){
System.out.println("生物呼吸");
}
public void eat(){
System.out.println("生物吃东西");
}
}
```java @MyAnnotaion(“Hi”) public class Person1 extends Creature
implements Comparable , MyInterface { private String name; int age; public int id;
public Person1() {
}
@MyAnnotaion(value = “constructor”) private Person1(String name) {
this.name = name;
}
public Person1(String name, Integer age) {
this.name = name;
this.age = age;
}
@MyAnnotaion() private String show(String nation) {
System.out.println("我的国籍是: " + nation);
return nation;
}
public String display(String instersts) {
return instersts;
}
@Override public void info() {
System.out.println("我是一个人");
}
@Override public int compareTo(String o) {
return 0;
} }
```java
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotaion {
String value() default "hello";
}
5.1 获取运行时类中的属性
@Test
void contextLoads() {
Class clazz = Person1.class;
// 获取属性结构
// getFields() 获取当前运行时类及其父类中声明为public访问权限的属性
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("******************************");
// getDeclaredFields() 获取当前运行时类中声明的所有属性。 不包含父类中的属性
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
}
//权限修饰符 数据类型 变量名
@Test
public void test2() throws ClassNotFoundException {
Class clazz = Person1.class;
Field[] declaredFields = clazz.getDeclaredFields();
for (Field f :
declaredFields) {
//1.权限修饰符
int modifiers = f.getModifiers();
System.out.print(Modifier.toString(modifiers)+"\t");
//2.数据类型
Class<?> type = f.getType();
System.out.print(type.getName()+"\t");
//3.变量名
String fName = f.getName();
System.out.print(fName);
System.out.println();
}
}
5.2 获取运行时类的方法
@Test
void contextLoads1() {
Class clazz = Person1.class;
// getMethods() 获取当前运行时类及其父类中声明为public权限的方法
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println(method);
}
// getDeclaredMethods():获取当前运行时类的所有方法,不包含父类中的方法
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod);
}
}
@Test
public void test2() throws ClassNotFoundException {
Class clazz = Person1.class;
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method m : declaredMethods) {
//1.获取方法声明的注解
Annotation[] annos = m.getAnnotations();
for (Annotation a :
annos) {
System.out.println(a);
}
//2.权限修饰符
System.out.print(Modifier.toString(m.getModifiers()) + "\t");
//3.返回值类型
System.out.print(m.getReturnType().getName() + "\t");
//4.方法名
System.out.print(m.getName());
System.out.print("(");
//5.形参列表
Class<?>[] parameterTypes = m.getParameterTypes();
// 判断是否有形参
if (!(parameterTypes == null && parameterTypes.length == 0)) {
for (int i = 0; i < parameterTypes.length; i++) {
if (i == parameterTypes.length - 1) {
System.out.print(parameterTypes[i].getName() + " args_" + i);
break;
}
System.out.print(parameterTypes[i].getName() + "args_" + i + ",");
}
}
System.out.print(")");
//6.抛出的异常
Class<?>[] exceptionTypes = m.getExceptionTypes();
if (exceptionTypes.length > 0) {
System.out.print("throws ");
for (int i = 0; i < exceptionTypes.length; i++) {
if (i == exceptionTypes.length - 1) {
System.out.print(exceptionTypes[i].getName());
break;
}
System.out.print(exceptionTypes[i].getName() + ",");
}
System.out.println();
}
}
}
5.3 获取运行时类构造器
@Test
void contextLoads3() {
Class clazz = Person1.class;
// getConstructors(): 获取当前运行时类声明为 public 的构造器
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
// getDeclaredConstructors(): 获取当前运行时类的全部构造器
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
}
5.4 获取运行时的父类
@Test
void contextLoads4() {
Class clazz = Person1.class;
// 获取运行时的父类
Class superclass = clazz.getSuperclass();
System.out.println(superclass);
// 获取运行时带泛型的父类
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
// 获取运行时类,父类的泛型
Type genericSuperclass1 = clazz.getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass1;
// 获取泛型类型
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
5.5 获取运行时类的接口,注解,所在包
@Test
void contextLoads5() {
Class clazz = Person1.class;
// 获取运行时类的实现接口
Class[] interfaces = clazz.getInterfaces();
for (Class anInterface : interfaces) {
System.out.println(anInterface);
}
// 获取运行时类的父类实现的实现接口
Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
for (Class aClass : interfaces1) {
System.out.println(aClass);
}
// 获取运行时类所在的包
Package aPackage = clazz.getPackage();
System.out.println(aPackage);
// 获取运行时类声明的注解
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
}