Java基础知识总结(八)——反射

时间:2022-06-22 20:03:56

心得:每个对象都对应自己类的Class对象,这个Class对象是这个类的类新信息的对外入口,通过这个Class对象我们可以从方法区知道这个类有哪些域,构造器,方法,结合具体的对象可以获得域在运行时的值,还可以基于Method调用(查找方法表调用?)。反射的意义在于运行时的扩展能力。

PS:
(1)在HotSpot(JDK 1.7)中Class对象是放在方法区中的。
(2)自己动手写一个工具输入类名,将类的完整信息输出出来,结合类图,整个结构就清晰了。

1. 获取Class对象

Class对象是类型信息的入口

获取Class对象的三个方法:
(1)obj.getClass();
(2)Object.class;
(3)Class.forName(String);

2. 反射的主要功能

2.1 获取类信息

三个大块
(1)Field;
(2)Method;
(3)Constructor;

核心结构类图:
Java基础知识总结(八)——反射
(1)Member:修饰符,名称,是否为编译器生成等;
(2)AccessibleObject:获取修改访问控制权限等;
(3)Executale:可执行的(Constructor,Method的抽象父类),类型参数,参数列表,异常列表,注解,是否为可变参数列表等;
(4)Method:返回类型,是否为默认方法(JDK1.8新方法),是否为桥方法,注解,调用(invoke);
(5)Constructor:方法的大部分功能(没有getReturnType方法),创建新对象(newInstance);
(6)Class:获取方法,域,构造器,实现接口,getXXXs获取包括父类公有方法,域,getDeclaredXXXs包括private,protected等,创建对象(newInstance);

2.2 反射创建对象

(1)通过Class对象(默认构造器创建):

class.newInstance();

(2)通过构造器创建:

try {
Constructor<A> constructor = aClass.getConstructor(int.class);
constructor.newInstance(1);
} catch (NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}

(3)反射创建数组:
这时Arrays中的泛型方法,很经典,注意数组的反射创建方法在Array类中。

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}

2.3 分析对象

主要是Field.get(obj),注意Accessible的修改和返回类型:

try {
A a = new A();
Field intField = aClass.getField("publicSuperField");
Field stringField = aClass.getDeclaredField("privateStringField");
stringField.setAccessible(true); //读写私有域
Integer intaa = (Integer)intField.get(a);//返回值自动装箱成Integer
int inta = intField.getInt(a);
intField.set(a, 1);
String intStr = (String)stringField.get(a);
stringField.set(a, "123");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}

2.4 反射调用方法

(1)一样有访问权限的问题,注意修改;
(2)返回值同样是Object;
(3)很多人吐槽反射效率低,但我觉得还是要结合具体需求,如果反射可以便于开发,性能开销相比业务开销又微不足道,该使用就用。

反射调用异常:InvocationTargetException

调用目标方法/构造器中发生异常会导致该异常,通过getTargetException获取实际的异常。

2.5 反射读取泛型信息

泛型信息几个重要类:
Class类:具体类型:
TypeVariable接口:类型变量,形如T extends Comparable<? super T>
WildcardType接口:通配符,形如? super T
ParameterizedType接口:泛型类或泛型接口,形如;
GenericArrayType接口:泛型数组;

Java基础知识总结(八)——反射

这也采用了面向接口的思想:
(1)获取方法参数类型,域类型,接口列表,方法返回类型,都有得到擦除之后和带有泛型信息两个版本的方法可以用(比如field.getGenericType()和field.getType(),parameter.getType()和parameter.getParameterizedType());
(2)返回的Type可能有不同的实现,比如返回方法的参数类型: public static <T> void gMethod(T t, Object o1, Comparable<? super T> o2),第一个是TypeVariable,第二个是Class,第三个是ParameterizedType;

3. 判断对象类型

3.1 几种基本方法

(1)instanceof关键字:objectref instanceof 类/接口名(的符号引用)
(2)isInstance方法:claz.isInstance(obj)
(3)通过“==”比较Class对象是否是同一实例;

前两个只要对象是类或接口的派生(或者直接是类的实例)或实现就返回true,对象引用为null时都返回false

3.2 instanceof的实现原理

(1)从编译说起:

(2)运行时,指令在执行引擎中的行为:
instanceof指令和checkcast指令很相似,但是区别在于

4. 动态代理

实现原理:反射+InvocationHandler+生成字节码;

动态代理的意义在于在具体原始类和接口还未知的时候就可以定义代理类的代理行为。这就是动态的体现,相对于我们编写代理类时需要实现被代理类的接口。

一个动态代理的构建和使用包括3个部分:
(1)方法调度器(核心):InvocationHandler,核心方法public Object invoke(Object proxy, Method method, Object[] args) throws Throwable,在编写时不要涉及将被代理的类型,当我们在调用;
(2)一组接口:在具体使用时,传给Proxy.newProxyInstance(类加载器, 要实现的接口, 方法调度器);
(3)实际的代理类实例

Interface proxy = (Interface)Proxy.newProxyInstance(Interface.class.getClassLoader(),
new Class<?>[]{Interface.class}, new InvocationHandlerT(itf));

这个代理类(注意是类)是运行时创建的,设置System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");可以在磁盘上保存这个代理类的class文件:
(1)这个代理类实现我们传入的接口类型,只能传入接口的类型,因为生成的代理类继承了Proxy(包含一个InvocationHandler)。
(2)通过反射获得包括接口方法在类的必要方法,通过super.h.invoke(this, m3, (Object[])null);调用;

public final class $Proxy0 extends Proxy implements Interface {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

public $Proxy0(InvocationHandler var1) throws {
super(var1);
}

public final void f() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
//...省略toString,hashCode,equals方法
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.yjh.reflect.InvocationHandlerTest$Interface").getMethod("f", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}