反射是Java中非常特别的机制,反射可以绕过泛型检查、破坏类的封装性,但使用反射的好处是巨大的:反射可以在程序运行时加载类并获取类中的所有信息,实现对程序的动态控制。
反射机制的核心 Class<T>类
获取class对象的3种方式:
Class cls = ClassName.class; // 通过 类名 + .class
cls = instance.getClass(); // 通过实例对象调用getClass()
cls = Class.forName("java.lang.String") // 通过类名的字符串形式获得
注意:
内置了8+1个基本数据类型的字节码。
同类型、同维数的数组共享一份字节码,父类是Object。数组的类型名:[L数组元素类型名。
常用方法:
static Class<?> forName(String className) // 通过类名获取class对象
T newInstance() // 使用默认的无参构造器创建一个新实例。
String getName() // 获取完整名称
String getSimpleName()
ClassLoader getClassLoader() // 获取加载此类的类加载器
Field getField(String name) // 获得公共成员字段
Field getDeclaredField(String name) // 获得成员字段,可以是private的
Field[] getFields() // 获得所有员字段
// 根据参数列表获取public的构造方法
Constructor<T> getConstructor(Class<?>... parameterTypes)
Constructor<?>[] getDeclaredConstructors() // 获取所有构造方法,包括private的
// 根据方法名和参数列表获取public方法
Method getMethod(String name, Class<?>... parameterTypes)
Method[] getDeclaredMethods() // 获取所有方法,包括private的
几个反射类
Field 代表了成员变量
Class getType() // 获取字段的类型
Method 代表方法
Class[] getParameterType()// 获取参数列表
Class getRuturnType()// 获取返回类型
Object invoke(Object o, Object...args)// 调用Method对象代表的方法,无论实际方法的返回类型是什么,invoke都作为Object类型返回
Constructor 代表了构造方法
Class[] getParameterType()// 获取参数列表
以上3个类都使用的且意义也相同的方法:
String getName()
int getModifiers() // 获取修饰符,public修饰符 对应 整形的静态常量PUBLIC
boolean isAccessible() // 判断是否可访问
void setAccessible(boolean flag) // 设置访问限制
类加载器
类加载器负责将class文件加载到JVM中,同时创建相应的Class对象。类加载器本身也是对象,也需要其他类加载器,除了BootStrap。
JVM判断Java类是否相同,要看两点:1、类名是否相同;2、是否被同一个类加载器加载。只有类名相同并且被同一个类加载器加载才会被JVM认为是同一个类。
3个内置的类加载器:
引导类加载器 BootStrap:根加载器,内嵌在虚拟机中,无需其他类加载器加载,加载范围:JRE/lib/rt.jar。
扩展类加载器 ExtClassLoader:被BootStrap加载,加载范围:JRE/lib/ext/*.jar。
系统类加载器 AppClassLoader:被ExtClassLoader加载,加载范围:ClassPath指定的目录或jar文件
类加载器的委托机制:
某一加载器若有父加载器,则将加载任务委托给父加载器,直至将加载任务传递到根加载器;
从根加载器开始,查找自己管辖范围的目录,若已存在需要加载的class文件则加载,否则将加载任务退回子加载器加载;
若从根加载器到发起加载任务的加载器都找不到class文件,则报异常,发起者不会将加载任务委托给子加载器。
JVM判断Java类是否相同,要看两点:1、类名是否相同;2、是否被同一个类加载器加载。只有类名相同和被同一个类加载器加载才会被JVM认为是同一个类。
面试题:可不可以自己编写一个java.lang.System类?
可以,但通常加载不到自己编写的System类,因为按类加载器的委托机制,加载任务传递至根加载器后,根加载器会发现系统默认的System类并加载。若想加载自己的System类可以编写自己的类加载器。
自定义类加载器
必须继承抽象类ClassLoader,覆盖findClass()
内省 IntroSpector
内省是对类的内部事务进行处理的方式。为了高效地使用内省,对需要进行内省操作的类约定了一些规则,符合这些规则的类称为Bean类。Java提供了一套工具对Bean类进行操作,这套工具位于java.beans包中。
使用PropertyDescriptor类操作Bean类中的某个属性:
Person person = new Person();
String propertyName = "name";
Class classType = p.getClass();
PropertyDescriptor pd = // 获得属性描述器
new PropertyDescriptor(propertyName, classType);
Method setProp = pd.getWriterMethod(); // 通过属性描述器获得对属性操作的方法
setProp.invoke(p, "Jack"); // 通过方法操作属性
Method getProp = pd.getReaderMethod(); // 通过属性描述器获得对属性操作的方法
Object retVal = getProp.invoke(p); // 通过方法操作属性
通过BeanInfo类获得Bean类的多个属性:
BeanInfo bi = // 获取Bean类的信息(把Person类当做Bean类)
IntroSpector.getBeanInfo(Person.class);
PropertyDescriptor[] pds = // 同过BeanInfo类获取多个属性描述器
bi.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) { // 遍历属性描述器
// 操作属性
}
代理类 java.lang.reflect.Proxy
代理是一种设计模式,它的原理是:创建一个与委托类有着相同接口的代理类,使用者并不直接访问委托类,而是通过访问代理类来实现对委托类的访问。我们通过改变代理类,可以在不修改委托类的情况为其添加不同功能。
Proxy类两个常用的静态方法:
Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) // 获取代理类的字节码,需要提供类加载器和接口列表
Object newProxyInstance(
ClassLoader loader, // 创建一个代理类的实例
Class<?>[] interfaces,
InvocationHandler h)
InvocationHandler接口
此接口定义了invoke()方法,通过覆盖此方法实现对委托类的功能增强。
Object invoke(Object proxy, Method method, Object[] args)
一个小范例:
List list = new ArrayList();
List proxy = (List) Proxy.newProxyInstance( // 创建List类的代理类实例
List.getClass().getClassLoader(),
List.getClass().getInerfaces(),
new InvocationHandler() { // InvocationHandler的匿名内部类
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// 此处可编写增强功能的代码
Object retVal = method.invoke(list, args); // 调用委托类的方法
// 此处可编写增强功能的代码
return retVal;
}
});