反射的API介绍:
反射的基石——Class类
· Java程序中的各个类属于同一类事物,描述这类事物的Java类名就是Class。
· 对比提问:众多的人用一个什么类表示?众多的类用什么什么类表示?
答: 人-->Person;类-->Class
· 对比提问:Person类代表人,它的实例对象就是张三、李四这样一个个具体的人,Class类代表java类。它的各个实例分别又对应什么?
答: 对应各个类在内存中的字节码。例如:Person类的字节码,ArrList的字节码等等。
一个类被加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码。不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示。这些对象虽然具有相同的类型,这个类型是什么呢?Class。
· Java程序中的各个java类,它们属于同一类事物。用一个类来描述这类事物:Class(注意C的大写)
· 获取各个字节码对应的实例对象,方法有3种:
1. 类名.class
2. new 对象().getClass()
3. Class.forName("类名或者对象名"); ——这种方法中传递的字符串可以是一个变量
· 反射还有9个预定义对象:八个基本数据类型+void
· Class的方法:
//Class类没有提供构造函数,所以用静态方法获取:
//返回与带有给定字符串名的类或接口相关联的 Class 对象
static Class<?> forName(String className);
//使用给定的类加载器,返回与带有给定字符串名的类或接口相关联的Class对象
static Class<?> forName(String name, boolean initialize, ClassLoader loader);
类名.class //获取该类的字节码文件
对象名.getClass(); //获取该对象对应的字节码文件
boolean isArray();//判断该类是否是一个数组
boolean isPrimitive();//判断该类是否是一个基本类型
· Class使用方法的代码示例:
//获取字节码的3种方式
String str = "abc";
Class c1 = str.getClass();
Class c2 = String.class;
Class c3 = Class.forName("java.lang.String");
System.out.println(c1 == c2);//true
System.out.println(c1 == c3);//true
//结果都为true,说明不管什么方法返回的字节码文件都是同一份字节码
//判断该字节码文件是否为数组类型
System.out.println(int[].class.isArray());//true
//判断是否为基本类型的字节码
System.out.println(int.class.isPrimitive());//true
System.out.println(String.class.isPrimitive());//false
//9个预定类型
System.out.println(int.class == Integer.TYPE);//true
· 总结:只要在源程序中出现的实例类型,都有各自的Class实例对象
理解反射:
Constructor类:代表类的构造方法
//通过Class类身上的方法获取
Constructor<T> getConstructor(Class<?>... parametterTypes);//返回一个Constructor方法
Constructor<T>[] getConstrucrors();//返回该类的所有的构造方法的一个数组
· 代码示例:
//获取String类的所有构造方法
Constructor[] constructors = Class.forName("java.lang.String").getConstructors();
//得到String类,参数为StringBuffer的构造方法
Constructor constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class);
//创建实例对象,通常方式:String str = new String(new StringBuffer("abc"));
//反射方式:
String str = (String)constructor.newInstance(new StringBuffer("abc"));//这里要传递类型的具体对象
System.out.println(str);//abc
· Class.newInstance():
Field类:代表某个类中的一个成员变量
Field getField(String name);//返回一个 Field 对象
Field[] getFields();//返回一个包含某些 Field 对象的数组
· Field类的方法:
Object get(Object obj);//返回指定对象上此Field表示的字段值
void set(Object obj,Object value);//将指定对象上此field对象表示的字段值设置为新的值
void setAccessible(boolean flag);//当flag为true时,代表这个字段可以访问
· 代码示例:
//有一个ReflectPoint类,有x,y 2个变量,通过构造方法赋值
//x变量被private修饰,y变量被public修饰
ReflectPoint pt1 = new ReflectPoint(3,5);
Field fieldY = pt1.getClass().getField("y");
//此时,fieldY的值不是5,因为fieldY不是对象身上的变量,而是类上的,要想用它去取某个对象上对应的值
System.out.println(fieldY.get(pt1));
//获取被private修饰的成员变量时,不能用getField方法
Field fieldX = pt1.getClass().getDeclaredField("x");
fieldX.setAccessible(true);//设置可见,暴力反射
System.out.println(fieldX.get(pt1));
·
代码练习:将任意一个对象中的所有String类型的成员变量所对应的字符串内容的“b”改成"a"
Test t = new Test();//有一个Test类。其中的String成员变量已经有默认初始化值
//通过对象的Class文件获取字段
Field[] fields = t.getClass().getFields();
for(Field field : fields) {
//判断Class文件是否相同时使用==连接
if(field.getType() == String.class) {
//获取每个String类型的成员变量
String oldValue = (String)field.get(t);
String newValue = oldValue.replace('b', 'a');
field.set(t, newValue);
}
}
Method类,代表某个类中的一个成员方法:
Method getMethod(String name, Class<?>... parameterTypes);//返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
Method[] getMethods();// 返回一个包含某些 Method 对象的数组
· 调用方法的代码示例:
Method charAt = Class.forName("java.lang.String").getMethod("charAt",int.class);
//通常方式:System.out.println(str.charAt(1));
//反射方式:
System.out.println(charAt.invoke(str,1));
//如果传递给Method对象的invoke方法的第一个参数为null,说明该Method方法对应的是一个静态的方法
· 代码:用反射的方式执行某个类中的main方法。
写一个程序,这个程序能够执行用户提供的类名,去执行该类中的main方法
public class MethodForArr {
public static void main(String[] args) throws Exception{
Method mainMethod = Class.forName("reflect.Arr").getMethod("main", String[].class);
//这里强转成Object是为了告诉编译器,我给的是一个对象,并不是一个数组
mainMethod.invoke(null, (Object)new String[]{"1","2","3"});
}
}
class Arr {
public static void main(String[] args) {
for(String arg : args) {
System.out.println(arg);
}
}
}
数组的反射:
Array工具类:
//一个方法,打印传递的对象,如果是数组就把数组的每一个参数打印
public static void print(Object obj) throws Exception{
//获取这个对象的字节码,对字节码进行判断
Class c = obj.getClass();
if(c.isArray()) {
//Array工具类提供了数组的反射
int len = Array.getLength(c);
for(int x=0; x<len; x++) {
System.out.println(Array.get(obj, x));
}
} else {
System.out.println(obj);
}
}
反射的作用
实现框架功能
//在写程序时,并不知道以后会什么类型的集合存储元素,将类型写在配置文件中
FileInputStream fileIn = new FileInputStream("config.properties");
Properties prop = new Properties();
prop.load(fileIn);
fileIn.close();
String className = prop.getProperty("className");
Collection<String> coll = (Collection<String>) Class.forName(className).newInstance();
coll.add("a");
coll.add("b");
coll.add("b");
coll.add("c");
/*
* 当className为java.util.HashSet时,size为3.因为HashSet不可以存放重复元素
*/
System.out.println(coll.size());
用类加载器加载文件:
内省
了解JavaBean
使用内省对JavaBean的简单操作:
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
class Person {
//符合JavaBean规范的Person类
//有name和age 2个属性
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class IntrospectionDemo {
public static void main(String[] args) throws Exception {
//创建JavaBean的Person类
Person p = new Person();
//创建内省类,设置p对象的name,age值
PropertyDescriptor pn = new PropertyDescriptor("name", p.getClass());
PropertyDescriptor pa = new PropertyDescriptor("age", Person.class);
Method setPersonName = pn.getWriteMethod();
Method setPersonAge = pa.getWriteMethod();
setPersonName.invoke(p, "bruce");
setPersonAge.invoke(p, 20);
//使用内省对象,获取p对象的name值
Method getPersonName = pn.getReadMethod();
Method getPersonAge = pa.getReadMethod();
String personName = (String)getPersonName.invoke(p);
int personAge = (Integer)getPersonAge.invoke(p);
System.out.println("personName:"+personName+"...personAge:"+personAge);
}
}
使用内省对JavaBean的复杂操作:
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
public class IntrospectionDemo2 {
public static void main(String[] args) throws Exception {
//内省的复杂操作
Person p = new Person();
String propertyName = "name";
String personName = null;
//将一个类当作JavaBean的类,获得BeanInfo信息
BeanInfo info = Introspector.getBeanInfo(Person.class);
//从信息中获取所有的PropertyDescriptor
PropertyDescriptor[] pds = info.getPropertyDescriptors();
for(PropertyDescriptor pd : pds) {
if(pd.getName().equals(propertyName)) {
//当遍历要需要的属性之后时,设置这个属性的内容
Method setName = pd.getWriteMethod();
setName.invoke(p, "bruce");
//获取这个值的信息
Method getName = pd.getReadMethod();
personName = (String)getName.invoke(p);
break;
}
}
System.out.println(personName);
}
}
BeanUtils:
· 是Apache组织写的操作JavaBean的工具类:需要在apache的官网的进行下载。
· 首先需要2个jar包,一个是commons-logging,另一个是commons-beanutils
· 导入我们需要的jar包,代码示例:
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
public class BeanUtilsDemo {
public static void main(String[] args) throws Exception {
Person p = new Person();
BeanUtils.setProperty(p, "name", "bruce");
BeanUtils.setProperty(p, "age", "19");
//Person类上有Date属性,并且Date的初始化值要写为new Date()
//Date类上有setTime的方法。Utils可以设置这个time
BeanUtils.setProperty(p, "birthday.time", "111");
System.out.println(BeanUtils.getProperty(p, "name"));
System.out.println(BeanUtils.getProperty(p, "age"));
System.out.println(BeanUtils.getProperty(p, "birthday"));
//BeanUtils操作和返回的对象都是String类型的
//PropertyUtils操作的具体类型,不需要进行转换
PropertyUtils.setProperty(p, "name", "lee");
PropertyUtils.setProperty(p, "age", 17);
System.out.println(PropertyUtils.getProperty(p, "name"));
System.out.println(PropertyUtils.getProperty(p, "age"));
}
}