Java反射
题外话
- 好久没有更新了,最近忙着准备实习的面试、笔试,因此在博客里面记录一些自己学到的知识。
概念
- JAVA反射机制是指在运行状态中
- 对于任意一个类,都能够知道这个类的所有属性和方法
- 对于任意一个对象,都能够知道这个对象的所有属性和方法
- 这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制
- 反射就是把java类中的各种成分映射成一个个的Java对象
例如:一个类有:成员变量(Field类型对象)、方法(Method类型对象)、构造方法(Constructor对象)、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
反射是java提供的一个重要功能,可以在运行时检查类、接口、方法和变量等信息,无需知道类的名字,方法名等。还可以在运行时实例化新对象,调用方法以及设置和获取变量值。
如图是类的正常加载过程:反射的原理在与class对象。
熟悉一下加载的时候:Class对象的由来是将class文件读入内存,并为之创建一个Class对象。
静态编译和动态编译
反射——动态编译;多态——动态绑定
- 静态编译:在编译时确定类型,绑定对象
- 动态编译:在运行时确定类型,绑定对象
编译时多态:主要是方法的重载,通过参数列表的不同来区分不同的方法。
运行时多态:也叫作动态绑定,一般是指在执行期间(非编译期间)判断引用对象的实际类型,根据实际类型判断并调用相应的属性和方法。主要用于继承父类和实现接口时,父类引用指向子类对象。
面向对象中的多态就是利用运行时多态,编译时确定引用类型(决定使用哪个方法),运行时确定实际使用的对象方法是谁。
反射优缺点
- 优点:运行期间类型判断,可以动态加载类,提高代码灵活度
- 缺点:
- 性能瓶颈:反射相当于一系列解释操作,通知JVM要做的事情,性能比直接的ajva代码要慢很多
- http://791202.com/2020/04/28/java/901/
- 主要是因为编译器没法对反射相关的代码做优化。
java反射之所以慢,根本原因是编译器没法对反射相关的代码做优化。
我们都知道 Java 代码是需要编译才能在虚拟机里运行的,但其实 Java 的编译期是一段不确定的操作过程。因为它可能是一个前端编译器(如 Javac)把 *.java 文件编译成 *.class 文件的过程;也可能是程序运行期的即时编译器(JIT 编译器,Just In Time Compiler)把字节码文件编译成机器码的过程;还可能是静态提前编译器(AOT 编译器,Ahead Of Time Compiler)直接把 *.java 文件编译成本地机器码的过程。
其中即时编译器(JIT)在运行期的优化过程对于程序运行来说更重要(比如对热点代码的优化,直接保存下来那些热点的机器码,不用再翻译了),Java虚拟机在编译阶段的代码优化就在这里进行,由于反射涉及动态解析的类型,因此无法执行某些Java虚拟机优化。因此,反射操作的性能要比非反射操作慢,因此应该避免在对性能敏感的应用程序中频繁使用Java反射来创建对象。
- 安全问题:让我们可以动态操作改变类的属性的同时也增加了类的安全隐患。
反射的应用场景
- 反射是框架设计的灵魂
- 在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。
举例
- 我们在使用 JDBC 连接数据库时使用
Class.forName()
通过反射加载数据库的驱动程序; - Spring 框架的 IOC(动态加载管理 Bean)创建对象以及 AOP(动态代理)功能都和反射有联系;
- 动态配置实例的属性;
获取Class对象的四种方式
Class类对象将一个类的方法、变量等信息告诉运行时的程序。
1. 通过类名获取
Class<?> alunbarClass = 类名.class;
这种方法会遍历包下面的类来获取Class对象,通过此方法获取Class的对象不会进行初始化(不会执行static)
2. 通过类的路径获取:Class.forName()
Class alunbarClass1 = Class.forName("cn.example.TargetName");
Class.forName(className)方法,内部实际调用的是一个native方法 forName0(className, true, ClassLoader.getClassLoader(caller), caller);
第2个boolean参数表示类是否需要初始化,Class.forName(className)默认是需要初始化。
一旦初始化,就会触发目标对象的 static块代码执行,static参数也会被再次初始化。
3. 通过类的实例获取
Employee e = new Employee();
Class alunbarClass2 = e.getClass();
4. 通过类加载器和类的路径获取:xxxClassLoader.loadClass()
Class clazz = ClassLoader.LoadClass("cn.javaguide.TargetObject");
通过类加载器获取Class对象不会进行初始化,意味着不进行包括初始化等一些列步骤,静态块和静态对象不会得到执行
总结
- 通过类名.class和通过类加载器+类路径来获取,不会初始化(静态代码块)。
- 通过类路径+class.forName和通过对象.getClass获取会初始化。
使用示例
构造方法
- 有declare的是可以私有的,但是没有declare不行
public Constructor getConstructor(Class... parameterTypes);//获取单个的"公有的"构造方法:
public Constructor getDeclaredConstructor(Class... parameterTypes);//获取"某个构造方法"可以是私有的,或受保护、默认、公有;
Constructor con = clazz.getConstructor(null);
//1>、因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型
//2>、返回的是描述这个无参构造函数的类对象。
System.out.println("con = " + con);
//调用构造方法
Object obj = con.newInstance();
获取变量
* 获取成员变量并调用:
*
* 1.批量的
* 1).Field[] getFields():获取所有的"公有字段"
* 2).Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有;
* 2.获取单个的:
* 1).public Field getField(String fieldName):获取某个"公有的"字段;
* 2).public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)
*
* 设置字段的值:
* Field --> public void set(Object obj,Object value):
* 参数说明:
* 1.obj:要设置的字段所在的对象;
* 2.value:要为字段设置的值;
//获取一个对象
Object obj = stuClass.getConstructor().newInstance();//产生Student对象--》Student stu = new Student();
//为字段设置值
f.set(obj, "刘德华");//为Student对象中的name属性赋值--》stu.name = "刘德华"
//验证
Student stu = (Student)obj;
System.out.println("验证姓名:" + stu.name);
成员方法
/*
* 获取成员方法并调用:
*
* 1.批量的:
* public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类)
* public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
* 2.获取单个的:
* public Method getMethod(String name,Class<?>... parameterTypes):
* 参数:
* name : 方法名;
* Class ... : 形参的Class类型对象
* public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
*
* 调用方法:
* Method --> public Object invoke(Object obj,Object... args):
* 参数说明:
* obj : 要调用方法的对象;
* args:调用方式时所传递的实参;
):
反射Main方法
综合示例
package cn.javaguide;
public class TargetObject {
private String value;
public TargetObject() {
value = "JavaGuide";
}
public void publicMethod(String s) {
System.out.println("I love " + s);
}
private void privateMethod() {
System.out.println("value is " + value);
}
}
使用反射操作
package cn.javaguide;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
/**
* 获取TargetObject类的Class对象并且创建TargetObject类实例
*/
Class<?> tagetClass = Class.forName("cn.javaguide.TargetObject");
TargetObject targetObject = (TargetObject) tagetClass.newInstance();
/**
* 获取所有类中所有定义的方法
*/
Method[] methods = tagetClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
/**
* 获取指定方法并调用
*/
Method publicMethod = tagetClass.getDeclaredMethod("publicMethod",
String.class);
publicMethod.invoke(targetObject, "JavaGuide");
/**
* 获取指定参数并对参数进行修改
*/
Field field = tagetClass.getDeclaredField("value");
//为了对类中的参数进行修改我们取消安全检查
field.setAccessible(true);
field.set(targetObject, "JavaGuide");
/**
* 调用 private 方法
*/
Method privateMethod = tagetClass.getDeclaredMethod("privateMethod");
//为了调用private方法我们取消安全检查
privateMethod.setAccessible(true);
privateMethod.invoke(targetObject);
}
}
反射与泛型
- 可以获取方法返回值、方法参数、变量的泛型类型
- 先getGenericReturnType()获取Type类型的参数,然后强转为ParameterizedType,然后getActualTypeArguments()
获取泛型信息
很多人认为java类在编译的时候会把泛型信息给擦除掉,所以在运行时是无法获取到泛型信息的。其实在某些情况下,还是可以通过反射在运行时获取到泛型信息的。获取到
java.lang.reflect.Method
对象,就有可能获取到某个方法的泛型返回信息。泛型方法返回类型
下面的类中定义了一个返回值中有泛型的方法:
public class MyClass {
protected List<String> stringList = ...;
public List<String> getStringList(){
return this.stringList;
}
}
下面的代码使用反射检测getStringList()方法返回的是
List<String>
而不是List
Method method = MyClass.class.getMethod("getStringList", null);
Type returnType = method.getGenericReturnType();
if(returnType instanceof ParameterizedType){
ParameterizedType type = (ParameterizedType) returnType;
Type[] typeArguments = type.getActualTypeArguments();
for(Type typeArgument : typeArguments){
Class typeArgClass = (Class) typeArgument;
System.out.println("typeArgClass = " + typeArgClass);
}
}
上面这段代码会打印:
typeArgClass = java.lang.String
泛型方法参数类型
下面的类定义了一个有泛型参数的方法setStringList():
public class MyClass {
protected List<String> stringList = ...;
public void setStringList(List<String> list){
this.stringList = list;
}
}
Method类提供了getGenericParameterTypes()方法获取方法的泛型参数。
method = Myclass.class.getMethod("setStringList", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for(Type genericParameterType : genericParameterTypes){
if(genericParameterType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericParameterType;
Type[] parameterArgTypes = aType.getActualTypeArguments();
for(Type parameterArgType : parameterArgTypes){
Class parameterArgClass = (Class) parameterArgType;
System.out.println("parameterArgClass = " + parameterArgClass);
}
}
}
上面的代码会打印出
parameterArgType = java.lang.String
泛型变量类型
通过反射也可以获取到类的成员泛型变量信息——静态变量或实例变量。下面的类定义了一个泛型变量:
public class MyClass {
public List<String> stringList = ...;
}
通过反射的Filed对象获取到泛型变量的类型信息:
Field field = MyClass.class.getField("stringList");
Type genericFieldType = field.getGenericType();
if(genericFieldType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericFieldType;
Type[] fieldArgTypes = aType.getActualTypeArguments();
for(Type fieldArgType : fieldArgTypes){
Class fieldArgClass = (Class) fieldArgType;
System.out.println("fieldArgClass = " + fieldArgClass);
}
}
Field对象提供了getGenericType()方法获取到泛型变量。 上面的代码会打印出:
fieldArgClass = java.lang.String
动态代理
(16条消息) Java设计模式-代理模式之动态代理(附源码分析)
静态代理
- 为一个对象提供一个代理,通过代理对象来访问
- 便于信息隐藏
- 但是会使得系统内的类规模增大,不易维护,导致结构臃肿
动态代理概述
- 在运行时刻,动态地创建出一个实现了多个接口的代理类。每个代理类对象(Proxy.newProxyInstance,返回的对象可以强转为Subject类!)都会关联一个调用处理器InvocationHandler接口的实现。
- 当使用者调用了代理对象所代理的接口中的方法时,这个调用的信息会被传递给InvocationHandler的invoke方法。在invoke方法的参数中可以获取到代理对象、方法对应的method对象和调用的实际参数。
- Invoke方法的返回值被返回给使用者。
这种做法实际上相当于对方法实行了拦截。
- Proxy类为JDK中的,我们不能像静态代理一样把代码放到proxy里面,而是需要一个InvocationHandler。
- InvocationHandler的工作就是响应代理的任何调用。
动态代理实现步骤
- 通过实现InvocationHandler创建自己的调用处理器类,覆盖invoke方法(三个参数为代理类实例(这个代理类由getProxyClass实现,然后在newProxyClass中生成实例返回)、要调用的Methon对象、要调用的方法参数),通过构造函数传入被代理对象
- 在客户端
- 创建一个需要被代理的对象
Subject realSubject = new RealSubject();
- 创建调用处理器类的实例
InvocationHandler handler = new DynamicProxy(realSubject);
,将realSubject作为参数传入 - 创建代理对象
Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
- 注:这个代理对象再也不用像静态代理那样实现Subject接口了,可以动态获得RealSubject中的方法
- 调用方法
subject.hello("world");
在Proxy.newProxyInstance方法中,
会首先获得与指定类加载器和一组接口相关的代理类型对象(Class类型)
Class cl = getProxyClass(loader, interfaces);
然后通过这个cl对象获得对应构造函数(反射),接着利用构造函数+反射获得实例
// 通过反射获取构造函数对象并生成代理类实例
try {
Constructor cons = cl.getConstructor(constructorParams);
return (Object) cons.newInstance(new Object[] { h });
} catch (NoSuchMethodException e) { throw new InternalError(e.toString());
} catch (IllegalAccessException e) { throw new InternalError(e.toString());
} catch (InstantiationException e) { throw new InternalError(e.toString());
} catch (InvocationTargetException e) { throw new InternalError(e.toString());
}
动态代理实现实例
Subject
package ProxyMode;
/*
* 抽象接口,对应类图中的Subject
*
*/
public interface Subject {
public void SujectShow();
}
RealSubject
package ProxyMode;
public class RealSubject implements Subject{
@Override
public void SujectShow() {
// TODO Auto-generated method stub
System.out.println("杀人是我指使的,我是幕后黑手!By---"+getClass());
}
}
建立InvocationHandler用来响应代理的任何调用
package ProxyMode;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ProxyHandler implements InvocationHandler {
private Object proxied;
public ProxyHandler( Object proxied )
{
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("准备工作之前:");
//转调具体目标对象的方法
Object object= method.invoke( proxied, args);
System.out.println("工作已经做完了!");
return object;
}
}
动态代理类测试,这个代理类中再也不用实现Subject接口,可以动态的获得RealSubject接口中的方
package ProxyMode;
import java.lang.reflect.Proxy;
public class DynamicProxy {
public static void main( String args[] )
{
RealSubject real = new RealSubject();
Subject proxySubject = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(),
new Class[]{Subject.class},
new ProxyHandler(real));
proxySubject.SujectShow();;
}
}
测试结果
准备工作之前:
杀人是我指使的,我是幕后黑手!By---class ProxyMode.RealSubject
工作已经做完了!
Proxy和InvocationHandler重要部分源码分析
java.lang.reflect.Proxy
- 这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
清单 1. Proxy 的静态方法
// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器,比如上面代码中的ProxyHandler
static InvocationHandler getInvocationHandler(Object proxy)
// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces)
// 方法 3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl)
// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces,
InvocationHandler h)
newPRoxyInstance
下面重点看看newPRoxyInstance方法:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {
// 检查 h 不为 空,否则抛异常
if (h == null) {
throw new NullPointerException();
}
// 获得与制定类装载器和一组接口相关的代理类类型对象
Class cl = getProxyClass(loader, interfaces);
// 通过反射获取构造函数对象并生成代理类实例
try {
Constructor cons = cl.getConstructor(constructorParams);
return (Object) cons.newInstance(new Object[] { h });
} catch (NoSuchMethodException e) { throw new InternalError(e.toString());
} catch (IllegalAccessException e) { throw new InternalError(e.toString());
} catch (InstantiationException e) { throw new InternalError(e.toString());
} catch (InvocationTargetException e) { throw new InternalError(e.toString());
}
}
看这个方法的三个参数
- loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
- interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
- h: 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
从上面JDK源码中可以看出getProxyClass方法才是newProxyInstance方法中最重要的,该方法负责为一组接口动态地生成代理类类型对象。下面开始解析proxy中的getProxyClass方法
getProxyClass——获取需要生成的代理类
该方法总共可以分为四个步骤:
1. 安全检查
- 对这组接口进行一定程度的安全检查,包括检查接口类对象是否对类装载器可见并且与类装载器所能识别的接口类对象是完全相同的,还会检查确保是 interface 类型而不是 class 类型。
这个步骤通过一个循环来完成,检查通过后将会得到一个包含所有接口名称的字符串数组,记为 String[] interfaceNames
for (int i = 0; i < interfaces.length; i++) {
// 验证类加载程 序 解 析 该接口到同一类对象的名称。
String interfaceName = interfaces[i].getName();
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(interfaceName, false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != interfaces[i]) {
throw new IllegalArgumentException(
interfaces[i] + " is not visible from class loader");
}
// 验证类对象真正代表一个接口
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
//验证这个接口是不是重复的
if (interfaceSet.contains(interfaceClass)) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
interfaceSet.add(interfaceClass); //interfaceset是一个hashset集合
interfaceNames[i] = interfaceName;
}
2. 查询类装载器对应缓存表
- 从 loaderToCache 映射表中获取以类装载器对象为关键字所对应的缓存表,如果不存在就创建一个新的缓存表并更新到 loaderToCache。
缓存表是一个 HashMap 实例,正常情况下它将存放键值对(接口名字列表,动态生成的代理类的类对象引用)。当代理类正在被创建时它会临时保存(接口名字列表,pendingGenerationMarker)。标记 pendingGenerationMarke 的作用是通知后续的同类请求(接口数组相同且组内接口排列顺序也相同)代理类正在被创建,请保持等待直至创建完成。
synchronized (cache) {
do {
// 以接口名字列表作为关键字获得对应 cache 值
Object value = cache.get(key);
if (value instanceof Reference) {
proxyClass = (Class) ((Reference) value).get();
}
if (proxyClass != null) {
// 如果已经创建,直接返回,这里非常重要,如果已经创建过代理类,那么不再创建
return proxyClass;
} else if (value == pendingGenerationMarker) {
// 代理类正在被创建,保持等待
try {
cache.wait();
} catch (InterruptedException e) {
}
// 等待被唤醒,继续循环并通过二次检查以确保创建完成,否则重新等待
continue;
} else {
// 标记代理类正在被创建
cache.put(key, pendingGenerationMarker);
// break 跳出循环已进入创建过程
break;
} while (true);
}
3. 动态创建代理类
- 首先是确定代理类所在的包,其原则如前所述,如果都为 public 接口,则包名为空字符串表示顶层包;如果所有非 public 接口都在同一个包,则包名与这些接口的包名相同;如果有多个非 public 接口且不同包,则抛异常终止代理类的生成。确定了包后,就开始生成代理类的类名,同样如前所述按格式“$ProxyN”生成。
// 动态地生成代 理类的字节码数组
byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces);
try {
// 动态地定义新生成的代理类
proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0,
proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
// 把生成的代理类的类对象记录进 proxyClasses 表
proxyClasses.put(proxyClass, null);
4. 更新缓存表
结尾部分,更新缓存表根据结果更新缓存表,如果成功则将代理类的类对象引用更新进缓存表,否则清楚缓存表中对应关键值,最后唤醒所有可能的正在等待的线程。
synchronized (cache) {
if (proxyClass != null) {
cache.put(key, new WeakReference<Class<?>>(proxyClass));
} else {
cache.remove(key);
}
cache.notifyAll();
}
java.lang.reflect.InvocationHandler
- 这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。
- InvocationHandler 的核心方法,我们最关心的是Invoke方法为什么会被调用,见下面分析:
// 该方法负责集中处理动态代理类上的所 有方法调用。
//第一个参数既是代理类实例,
//第二个参数是被调用的方法对象
// 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
Object invoke(Object proxy, Method method, Object[] args)
- 每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象(参见 newProxyInstance 的第三个参数)。
- 我们在Handler中调用的method.invoke方法中并没有显示的调用invoke方法,只是在newProxyInstance中应用了一个handler对象,有了上面关于newProxyInstance的源码分析,我们知道了 newproxyinstance生成了一个
$Proxy0
类代理。当调用Subjectshow()方法时,其实调用的$Proxy0
的SubjectShow()方法,从而调用父类Proxy中传进来第三个参数(h)的Invoke方法。
//这个方法是 Proxy源码中的
protected Proxy(InvocationHandler h) {
this.h = h;
}
来看NewProxyInstance方法生成的$Proxy0代理类的源码
public final class $Proxy0 extends Proxy implements Subject {
private static Method m1;
private static Method m0;
private static Method m3;
private static Method m2;
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals",
new Class[] { Class.forName("java.lang.Object") });
m0 = Class.forName("java.lang.Object").getMethod("hashCode",
new Class[0]);
m3 = Class.forName("***.RealSubject").getMethod("request",
new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString",
new Class[0]);
} catch (NoSuchMethodException nosuchmethodexception) {
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
} catch (ClassNotFoundException classnotfoundexception) {
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
} //static
public $Proxy0(InvocationHandler invocationhandler) {
super(invocationhandler);
}
@Override
public final boolean equals(Object obj) {
try {
return ((Boolean) super.h.invoke(this, m1, new Object[] { obj })) .booleanValue();
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
@Override
public final int hashCode() {
try {
return ((Integer) super.h.invoke(this, m0, null)).intValue();
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void SubjectShow() {
try {
super.h.invoke(this, m3, null); //就是这个地方 调用h.invoke()
return;
} catch (Error e) {
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
@Override
public final String toString() {
try {
return (String) super.h.invoke(this, m2, null);
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}
- 会通过
super.h.invoke(this,m3,null)
来调用invoke方法
- invoke方法内部通过
m3.invoke(代理对象,参数)
来调用
其他知识点
基本数据类型也有Class对象
为什么Class类没有公共构造方法?
Class
没有公共构造方法。Class
对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass
方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。
对于某一个类,有几个Class对象?
只有一个,单例
通过反射越过泛型检查
泛型用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的
import java.util.ArrayList;
/*
* 通过反射越过泛型检查
*
* 例如:有一个String泛型的集合,怎样能向这个集合中添加一个Integer类型的值?
*/
public class Demo {
public static void main(String[] args) throws Exception{
ArrayList<String> strList = new ArrayList<>();
strList.add("aaa");
strList.add("bbb");
// strList.add(100);
//获取ArrayList的Class对象,反向的调用add()方法,添加数据
Class listClass = strList.getClass(); //得到 strList 对象的字节码 对象
//获取add()方法
Method m = listClass.getMethod("add", Object.class);
//调用add()方法
m.invoke(strList, 100);
//遍历集合
for(Object obj : strList){
System.out.println(obj);
}
}
}
控制台输出:
aaa
bbb
100
ref
- Java反射使用总结
- Reflection:Java 反射机制的应用场景
- Java 基础之—反射(非常重要)
- Java设计模式-代理模式之动态代理(附源码分析)_java动态代理源码分析
- JavaGuide (gitee.io)