1. 概述
反射就是把java类中的各种成分映射成相应的java类。
1.1 类的加载概述
当程序要使用某个类的时候,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三步来实现对这个类进行初始化
1.1.1 加载
就是指将class文件读入内存,并为之创建一个Class对象,任何类被使用的时候,系统都会建立一个Class对象
1.1.2 连接
连接分为三步:
1. 验证:是否有正确的内部结构,并和其他类协调一致
2. 准备:负责为类的静态成员分配内存,并设置默认初始化值
3. 解析:将类的二进制数据中的符号应用替换为直接引用
1.1.3 初始化
就是初始化步骤
1.2 加载时机
1. 创建类的实例
2. 访问类的静态变量,或者为静态变量赋值
3. 调用静态类的方法
4. 使用反射方式强制创建某个类或者接口对应的java.lang.Class对象
5. 初始化某个类的子类
6. 直接使用java.exe命令来运行某个主类
1.3 类加载器的概述和分类
1.3.1 类加载器的概述
负责将.class文件加载到内存中,并为之生成对应的class对象。
1.3.2 类加器的分类
1. 根类加载器:Bootstrap ClassLoader
2. 扩展类加载器:Extension ClassLoader
3. 系统类加载器:System ClassLoader
1.3.3 类加载器的作用
根类加载器Bootstrap ClassLoader,也被称为引导类加载器,负责java核心类的加载,比如:System、String等。在JDK中JRE的lib目录下rt.jar文件中
扩展类加载器Extension ClassLoader,负责JRE的扩展目录中jar包的加载,在JDK中JRE的lib目录下ext目录
系统类加载器System ClassLoader,负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径。
2. 反射的基石——Class
Class是从JDK1.2开始引入的特性,Class类代表了Java类,Java类用于描述一类事物的共性,该类事物决定了其所具有的属性,至于属性值,则由具体的实现类来确定,不同的实例对象有不同的属性值。
Java程序中用Class来描述各个类,Class描述了类的名字,访问属性,类的包名,字段名称列表,方法名称的列表等等。其实例对应各个类在内存中的字节码,一个类被加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象具有相同的类型,这个类型就是CLass,即
在java程序中各个java类都同属于一类事物,描述这类事物的java类名就是Class
2.1. 获取Class类型的三种方式
2.1.1 方式一
类名.class
例如:System.class
2.1.2 方式二
对象.getClass()
例如:new Date().getClass()
2.1.3 方式三
Class.forName(“类名”)
例如:CLass.forName(“java.util.Date”);
注意:
1. 该方式在实际中被大量运用于一些框架的反射中。
2. 该方法有两种加载方式,第一种,内存中已存在该字节码,直接返回对应的字节码的内存地址,第二种,虚拟机中不存在该字节码,首先类加载器会先加载该类的字节码到内存中缓存,同时方法返回该字节码对应的内存地址.
3. 同一个类对象在内存中只会存在一个字节码。
例如:
String str1 = "acc";
String str2 = new String();
Class c1 = str1.getClass();
Class c2 = str2.getClass();
Class c3= Class.forName("java.lang.String");
System.out.println(c1 == c2); // true
System.out.println(c1 == c3); // true
2.2. 九个预定义CLass实例对象
八个基本数据类型+void,他们都有对应的Class对象。
例如:int.class void.class
2.3. 判断Class字节码类型
注意:基本数据类型的包装类都有一个TYPE常量,该常量表示其对应的基本数据类型的字节码
例如:int.class == Integer.TYPE
2.3.1 isPrimitive——判断基本数据类型
在java中,可以通过isPrimitive()来判断字节码是否为基本数据类型
例如:int.class.isPrimitive();
2.3.2 isArray——判断数组
int[].class.isArray();
3. 反射
3.1 构造方法的反射——Constructor
Constructor类代表了某个类中的构造方法
3.1.1 常用API
1. 得到某个类的构造方法集合——getConstructors
Constructor c[] = Class.forName("java.lang.String").getConstructors();
注意:对于数组的写法,中括号最好是写在类的后面。例如:Constructor[] c = xxx
2. 得到指定参数的构造方法——getConstructor
Constructor c11 = Class.forName("java.lang.String").getConstructor(StringBuffer.class);
3. 创建实例对象——newInstance
该方法是使用该类无参的构造函数创建对象,如果一个类不存在无参的构造函数,就不能使用它创建了。
Constructor c11 = Class.forName("java.lang.String").getConstructor(StringBuffer.class);
String str = (String)c11.newInstance(new StringBuffer("abc"));
System.out.println(str.charAt(1));
4. 创建实例对象—— newInstance(Object … obj)
该方法是使用该类的有参数的构造函数来创建对象的。
Class clazz = Class.forName("com.bjc.Person");
Constructor c = clazz.getConstructor(String.class,int.class);
Person p = (Person)c.newInstance("张三",21);
3.2 成员变量的反射——Field
Field类代表了某个类中的一个成员变量
3.2.1 常用API
1. 获取所有的成员变量——getFields
ReflectPoint rp = new ReflectPoint(3, 5);
Field[] fields = rp.getClass().getFields();
2. 获取某一个非private类型的成员变量——getField
ReflectPoint rp = new ReflectPoint(3, 5);
Field fieldY = rp.getClass().getField("y");
3. 获取某个类型的Field的值——get
ReflectPoint rp = new ReflectPoint(3, 5);
Field fieldY = rp.getClass().getField("y");
Object object = fieldY.get(rp);
System.out.println(object);
注意:get的参数为一个实例对象
4. 获取所有私有的成员变量——getDeclaredFields
rp.getClass().getDeclaredFields();
5. 获取指定的私有成员变量——getDeclaredField
rp.getClass().getDeclaredField("y");
6. 设置私有变量可以被访问——setAccessible
Field declaredField = rp.getClass().getDeclaredField("y");
declaredField.setAccessible(true);
注意:如果要访问私有成员,需要设置setAccessible为true,否则会报错。
7. 设置成员变量值——set
Field declaredField = rp.getClass().getDeclaredField("y");
declaredField.set(rp,32);
8. 获取成员变量的类型——getType
Class type = fieldY.getType();
3.2.2 例子
1. 将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的“b”改成“a”
public static void change(Object obj) throws Exception {
Field[] fields = obj.getClass().getFields();
for(Field f : fields){
if(f.getType() == String.class){ // 注意:字节码只有一份,所以这里用双等号语意更明确
String strVal = (String)f.get(obj);
String newStrVal = strVal.replaceAll("a", "b");
f.set(obj,newStrVal); // 将新值设置到obj对象中
}
}
}
3.3 成员方法的反射——Method
Method类代表某个类中的一个成员方法
3.3.1 常用API
1. 得到类中的所有方法——getMethods
Method[] methods = Class.forName("java.lang.String").getMethods();
2. 得到类中的某一个方法——getMethod
2.1 获取有参方法
Class.forName("java.lang.String").getMethod("charAt", int.class);
2.2 获取无参方法
Class.forName("com.bjc.Person").getMethod("eat");
3. 执行方法——invoke
3.1 执行有参方法
Method method = Class.forName("java.lang.String").getMethod("charAt", int.class);
String str = "abc";
method.invoke(str, 1);
又如:
Class clazz = Class.forName("com.bjc.Person");
Constructor c = clazz.getConstructor(String.class,int.class);
Person p = (Person)c.newInstance("张三",22);
Method m = Class.forName("com.bjc.Person").getMethod("eat",int.class);
m.invoke(p,23);
该执行逻辑为,调用某个对象的某个方法,第一个参数为对象,第二个开始的为参数列表
3.2 执行无参方法
Class clazz = Class.forName("com.bjc.Person");
Constructor c = clazz.getConstructor(String.class,int.class);
Person p = (Person)c.newInstance("张三",22);
Method m = Class.forName("com.bjc.Person").getMethod("eat");
m.invoke(p);
注意:
1. 如果传递给Method对象的invoke()方法的第一个参数为Null,这表示该method对象对应的是一个静态方法。
例如:method.invoke(null, 1);
2. jdk1.4与jdk1.5invoke的区别是。
在1.5
public Object invoke(Object obj,Object ... args)
在1.4
public Object invoke(Object obj,Object[] args)
即,按1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,我们也可以这样使用
Method method = Class.forName("java.lang.String").getMethod("charAt", int.class);
String str = "abc";
method.invoke(str, new Oject[]{1});
3.3.2 对接收数组参数的成员方法的反射
我们知道,在jdk1.4中,数组中的每一个元素都对应一个参数,但是在jdk1.5中是按照一个整体来处理的,如果我们一个数组作为参数传递给invoke的时候,javac会按照1.4还是按照1.5来处理了?jdk1.5肯定是要兼容1.4的所以,所以javac会按照1.4的语法进行处理,即将数组打散成为若干个单独的参数,所以,数组类型的参数,不能直接使用数组(即下面的处理方式会出现参数类型不匹配的问题method.invoke(null,new String[]{“abc”,”bcd”}))。
那么,我们需要怎么处理数组参数了
两种方式
1. 方式一:
method.invoke(null,new Object[]{new String[]{"abc","bcd"}})
2. 方式二:
method.invoke(null,(Oject)new String[]{"ac","cd"})
3.4 数组的反射
1. 具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
2. 代表数组的Class实例对象的getSupperClass()方法返回的父类为Object类对应的Class。
3. 基本类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用。
4. 非基本类型的一维数组,既可以当做Object[]类型使用,又可以作为Object[]类型使用。
例如:
public static void test02(){
int[] a = new int[3];
int[] b = new int[4];
int[][] d = new int[2][3];
String[] c = new String[3];
System.out.println(a.getClass() == b.getClass()); // true
//System.out.println(a.getClass() == d.getClass()); // false
//System.out.println(a.getClass() == c.getClass()); // false
System.out.println(a.getClass().getSuperclass().getName()); // java.lang.Object
System.out.println(d.getClass().getSuperclass().getName()); // java.lang.Object
System.out.println(c.getClass().getSuperclass().getName()); // java.lang.Object
}
3.5 泛型反射
泛型只存在于编译期,在运行期会被擦除掉,所以,我们可以使用反射越过泛型检查
public static void main(String[] args) throws Exception {
ArrayList<Integer> aList = new ArrayList<Integer>();
aList.add(1);
aList.add(2);
aList.add(3);
// 利用反射,添加字符串到aList中
Class clazz = aList.getClass();
Method method = clazz.getMethod("add", Object.class);
method.invoke(aList, "张三");
System.out.println(aList.toString());
}
3.5.1 Constructor的泛型反射
构造函数Constructor的泛型反射,就是在Constructor上加入泛型,这样,通过其创建出来的对象就是该类型的对象了,不需要进行强制类型转换了。
例如:
public static void test02() throws Exception, SecurityException{
Class clazz = String.class;
Constructor<String> constructor = clazz.getConstructor(String.class);
String newInstance = constructor.newInstance("abcde");
System.out.println(newInstance);
}
3.5.2 通过反射获取函数形参列表中泛型的实际参数类型
例如:有一个函数
public static void test(List<String> list){
}
通过反射得到泛型String
// 通过反射获取方法
Method method = AnnotationTest.class.getMethod("test", List.class);
// 通过方法得到参数化类型数组
Type[] genericParameterTypes = method.getGenericParameterTypes();
// 因为只有一个参数,所以取第一个
Type genericParameterType = genericParameterTypes[0];
// 强转 ParameterizedType 是 Type的子类,表示参数化类型
ParameterizedType pt = (ParameterizedType)genericParameterType;
// 通过ParameterizedType得到实际参数类型数组
Type[] actualTypeArguments = pt.getActualTypeArguments();
// 取第一个类型
Type type = actualTypeArguments[0];
System.out.println(type);
3.5.3 通过反射获取类上的泛型类型
public BaseDaoImpl<T>{
// 反射:第一步获取class
Class clazz = this.getClass(); // this指正在被调用的那个类
// 获取子类的父类的参数化类型CustomerDaoImpl extends BaseDapImpl<Customer>
Type type = clazz.getGenericSuperclass();
// 将Type类型强转为参数化类型
ParameterizedType pType = (ParameterizedType)type;
// 获取实际类型参数
Type[] actualTypes = pType.getActualTypeArguments();
// 这里只需要获取1个就可以了 ,因为Type接口的实现类是Class,所以,可以直接强转
this.clazz = (Class) actualTypes[0];
}
注意:这种方式有一个前提,需要当前类继承泛型类
在例如:
定义泛型类
package com.bjc.annotation;
public class General<T> {
T field;
public void add(T t){
}
public T findById(Integer id){
return field;
}
public void del(){
}
}
继承该类
public class AnnotationTest extends General<Double>{
public static void main(String[] args) throws Exception{
new AnnotationTest().test();
}
public void test(){
Type genericSuperclass = this.getClass().getGenericSuperclass();
ParameterizedType p = (ParameterizedType)genericSuperclass;
Type type = p.getActualTypeArguments()[0];
System.out.println(type);
}
}
打印结果为Double。
5. 反射一些应用
5.1 反射实现设置属性值
public static void setProperty(Object obj,String fieldName,Object value) throws Exception{
Class class1 = obj.getClass();
Field field = class1.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
测试:
package com.bjc.reflect;
import com.bjc.util.ReflectUtils;
public class ReflectDemo3 {
public static void main(String[] args) throws Exception {
Person p = new Person();
p.setName("张三");
p.setAge(23);
System.out.println(p);
ReflectUtils.setProperty(p, "name", "李四");
System.out.println(p);
}
}
class Person{
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
5.2 实现框架功能
模拟框架,将类交给配置文件管理
配置文件:config.properties
className=java.util.ArrayList
java代码:
public static void test() throws Exception{
InputStream is = new FileInputStream("config.properties");
Properties pro = new Properties();
pro.load(is);
is.close(); // 关闭流
String className = pro.getProperty("className");
Collection c = (Collection) Class.forName(className).newInstance();
c.add("abc1");
c.add("abc2");
c.add("abc3");
System.out.println(c.toString());
}
5.2 JavaBean的内省操作——PropertyDescriptor
JavaBean是一种特殊的java类,主要用于传递数据信息,该类中的方法主要通过get与set方法访问私有的字段,且方法名符合某种命名规则。在JavaEE开发中,JavaBean的使用非常广泛,所以JDK提供了一套对其进行操作的API,这套API就叫做内省。
5.2.1 创建PropertyDescriptor对象
创建PropertyDescriptor对象,其有两个参数,第一个参数为JavaBean的属性名,第二个参数为JavaBean的字节码。
例如:
pulicstatic void test3() throws Exception{
Point p = new Point(3,5);
String propertyName = "x";
PropertyDescriptor pd = new PropertyDescriptor(propertyName, Point.class);
}
5.2.2 得到get方法并获取值——getReadMethod
通过PropertyDescriptor对象来获取getReadMethod方法
例如:
/**
* @param obj javaBean对象
* @param propertyName JavaBean的属性名称
* @return 返回属性值
* @throws IntrospectionException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
private static Object getProperties(Object obj, String propertyName)
throws IntrospectionException, IllegalAccessException, InvocationTargetException {
PropertyDescriptor pd = new PropertyDescriptor(propertyName, obj.getClass());
Method readMethod = pd.getReadMethod();
Object invoke = readMethod.invoke(obj);
return invoke;
}
注意:get方法没有参数,所以invoke方法调用的时候,只需要传入被执行的对象即可
5.2.3 得到set方法并设置值——getWriteMethod
通过PropertyDescriptor对象来获取getWriteMethod方法
/**
* @param obj javaBean对象
* @param propertyName JavaBean属性名
* @param value 需要给属性设置的值
* @throws IntrospectionException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
private static void setProperties(Object obj, String propertyName,Object value)
throws IntrospectionException, IllegalAccessException, InvocationTargetException {
PropertyDescriptor pd = new PropertyDescriptor(propertyName, obj.getClass());
Method writeMethod = pd.getWriteMethod();
writeMethod.invoke(obj, value);
}
注意:在JavaBean中set方法通常会传递一个参数,所以在调用invoke方法的时候,需要有参数,第一个参数表示被执行的对象,第二个参数开始为传递的形参。
5.3 javaBean的内省操作——Introspector
对于javaBean还有一个相对比较复杂的操作,就是采用遍历BeanInfo的所有属性方式来查找和设置某个JavaBean对象的属性。
在程序中,将一个java类当做JavaBean来看,就是调用Introspector.getBeanInfo方法,得到的BeanInfo对象封装了把这个类当做JavaBean看的结果信息。
private static Object getProperties1(Object obj, String propertyName)
throws IntrospectionException, IllegalAccessException, InvocationTargetException {
Object retVal = null;
// 获取BeanInfo对象
BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
// 得到BeanInfo中的所有的属性描述
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for(PropertyDescriptor p : propertyDescriptors){
// 如果传递过来的属性名与得到的属性名相等
if(propertyName.equals(p.getName())){
Method readMethod = p.getReadMethod();
retVal = readMethod.invoke(obj);
break;
}
}
return retVal;
}