java反射的基础-Class类
java程序中的各个java类,属于同一类事物,可以用一个类来描述这类事物,这个类的名字就是Class。
Class类描述了java类的名称、访问属性、类所属的包名、字段名称的列表、方法名称的列表等。
Class对象代表了某个java类的字节码文件,将硬盘中存储的类字节码文件加载到内存有3种方法:
1. 类名.class,如Class clazz1=Person.class
2. 对象.getClass(),如Class clazz2=new Person().getClass()
3. Class.forName("类名")
如Class clazz3=Class.forName("cn.itcast.Person"),必须是完整类名(加包路径),该方法会抛ClassNotFoundException异常。
Jvm只会加载一份字节码文件到内存,同一个程序中用3种不同方法获取同一个类的字节码文件得到的Class对象是同一个。
九个预定义的Class实例对象
8种基本数据类型(byte,short,int,long,char,float,double,boolean)和void也都有Class类对象,如int.class, void.class.
只要是在源程序中出现的类型,都有各自的Class实例对象,如int[]等。
public class ReflectDemo {
public static void main(String[] args) throws Exception
{
String str="abc";
//3种方式加载String类的字节码文件
Class cls1=str.getClass();
Class cls2=String.class;
Class cls3=Class.forName("java.lang.String");
//下面2句都打印true,说明内存中只有一份String类的字节码文件
System.out.println(cls1==cls2);
System.out.println(cls2==cls3);
//Class类常用方法
//判断Class对象是否表示一个基本类型,只有8种基本数据类型的Class对象调用该方法时返回true
System.out.println(cls1.isPrimitive());//false
System.out.println(int.class.isPrimitive());//true
//int和Integer的Class对象并不是同一个
System.out.println(int.class==Integer.class);//false
//8种基本数据类型都有一个静态常量TYPE,表示包装类型所包含的基本类型的Class实例
System.out.println(int.class==Integer.TYPE);//true
//int[]数组的Class对象也不表示基本类型,而是数组类型。
System.out.println(int[].class.isPrimitive());//false
System.out.println(int[].class.isArray());//true
}
}
反射(Reflect)
反射就是把java类中的各种成分映射成相应的java类。
一个java类的组成部分:成员变量、方法、构造函数、包信息等都可以用一个个java类来表示,分别是Field、Method、Contructor、Package等,都是java.lang.reflect包中的类。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,这些实例对象可调用Class类的方法得到,得到这些实例对象后可通过架构思想创建java类对象,使得对象中的方法。
下面通过代码演示java类中构造函数、方法和成员变量的反射方式:
import java.lang.reflect.*;
class Point{
private int x;
private int y;
public int h;
Point(int x, int y){
this.x=x;
this.y=y;
}
private Point(int x,int y, int h){
this.x=x;
this.y=y;
this.h=h;
}
private void show(){
System.out.println("x...."+x+" , "+"y...."+y);
}
}
public class ReflectDemo2{
public static void main(String[] args) throws Exception
{
String str1="wus";
//反射String类的一个带参数的构造函数
Constructor con1=String.class.getConstructor(StringBuffer.class);
//通过Constructor对象的newInstance()方法创建String对象,newInstance()方法中需传递对应参数,返回Object对象,使用返回值时需强制转换。
String str2=(String)con1.newInstance(new StringBuffer("can"));
System.out.println(str2.charAt(2));
//暴力反射Point类中的私有构造函数
Constructor con2=Point.class.getDeclaredConstructor(int.class,int.class,int.class);
//上面得到的con2所代表的构造函数仍是私有的,使用前需要强制公开其权限。
con2.setAccessible(true);
Point p1=(Point)con2.newInstance(6,7,8);
//反射String类中的charAt()方法
Method methodCharAt=String.class.getMethod("charAt",int.class);
//使用Method对象的invoke()函数执行str1的charAt()方法,这是jdk1.5中invoke()调用方式。
System.out.println(methodCharAt.invoke(str1,1));
//jdk1.4中invoke()函数调用方式
System.out.println(methodCharAt.invoke(str1, new Object[]{2}));
//暴力反射Point类中的无参私有方法,下面的null也可以不写
Method methodShow=Point.class.getDeclaredMethod("show",null);
methodShow.setAccessible(true);
methodShow.invoke(p1);
//调用Class对象的getField()方法得反射Point类中的一个公有成员变量x.
Field fieldh=p1.getClass().getField("h");
//调用Field对象的get()方法,传入具体对象,获取该对象中成员变量x的值。
System.out.println(fieldh.get(p1));
//暴力反射成员变量y
Field fieldy=p1.getClass().getDeclaredField("y");
fieldy.setAccessible(true);
System.out.println(fieldy.get(p1));
}
}
注解:1. 通过Class对象的getConstructor()、getMethod()等方法反射类的构造函数、成员方法时,会抛出NoSuchMethodException和SecurityException,getField()等方法会抛出NoSuchFieldException和SecurityException。
2. newInstance()、invoke()和get()方法抛出IllegalAccessException和IllegalArgumentException,setAccessible()会抛出SecurityException异常。
3. 私有权限的构造函数和成员必须使用getDeclaredXXX()方法来反射,否则会报错,反射后使用前必须公开其权限。
4. Class类中也有newInstance()方法,是先反射出类的无参构造函数,再创建实例,因此使用该方法时,类中必须有无参构造函数。
5. 静态成员的反射,得到静态成员的Method或Field对象后,再调用invoke()或get()时,不用传递具体的对象参数,用null即可。
jdk1.5和jdk1.4中Method类的invoke()方法函数形参是不一样的:
jdk1.5中是可变参数形式Object invoke(Object obj, Object...args).
jdk1.4中是参数数组形式Object invoke(Object obj, Object[] obj).
jdk1.5为了兼容jdk1.4,invoke()方法要接收到一个参数数组时,也会将数组进行拆分,当成方法的多个参数。
import java.lang.reflect.*;
public class ReflectDemo3{
public static void main(String[] args) throws Exception
{
String startingClassName=args[0];
Class clazz=Class.forName(startingClassName);
Method mainMethod=clazz.getMethod("main",String[].class);
//jdk1.5会将参数数组拆成多个String对象,从而会报IllegalArgumentException异常
//mainMethod.invoke(null,String[]{"111","222","333"});
//可以用下面2种方法规避这个问题
//将String[]数组封装到一个Object[]数组中,Object[]数组里只有一个元素。
mainMethod.invoke(null,new Object[]{new String[]{"111","222","333"}});
//将String[]数组强转成Object类型,运行时String[]数组也就不会被拆分了。
mainMethod.invoke(null,(Object)new String[]{"111","222","333"});
Method showMethod=clazz.getMethod("show", int[].class);
//int[]等基本数据类型数组不会被自动拆分,编译通过
showMethod.invoke(null, new int[]{6,7,8});
}
}
int[]等基本类型数组和String[]等引用型数组在很多使用及处理方式上都有所不同,基本类型数组更多时候是作为一个整体使用,引用型对象数组则会被自动拆分,这一点从上面的代码中已经可以看出来,下面再具体说明:
import java.lang.reflect.Array;
import java.util.Arrays;
public class ReflectDemo4 {
public static void main(String[] args) {
int[] a1=new int[]{1,2,3};
int[] a2=new int[4];
int[][] a3=new int[2][3];
String[] s1=new String[]{"a","b","c"};
System.out.println(a1.getClass()==a2.getClass());//true
/*
下面2句编译失败,根本无法比较:
Incompatible operand types Class<capture#3-of ? extends int[]> and Class<capture#4-of ? extends String[]>
Incompatible operand types Class<capture#5-of ? extends int[]> and Class<capture#6-of ? extends int[][]>
*/
//System.out.println(a1.getClass()==s1.getClass());
//System.out.println(a1.getClass()==a3.getClass());
//打印[I, [代表数组,I代表int类型
System.out.println(a1.getClass().getName());
//int[]和String[]的Class对象的父类都是Object
System.out.println(a1.getClass().getSuperclass().getName());
System.out.println(s1.getClass().getSuperclass().getName());
Object obj1=a1;
Object obj2=s1;
Object obj3=a3;
//int[]一维数组不能赋给Object[]数组
//Object[] obj4=a1;
//int[][]二维数组和String[]都可以赋给Object[]数组
Object[] obj5=a3;
Object[] obj6=s1;
System.out.println(obj1);
System.out.println(obj2);
System.out.println(obj3);
System.out.println(obj5);
System.out.println(obj6);
/*
jdk1.4和jdk1.5中,Arrays类中asList()方法形参形式并不同
jdk1.4中是asList(Object[] objs)
jdk1.5中是asList(T... a),可变参数形式
*/
//int[]数组不能转换成Object[]数组,所以只能匹配jdk1.5中的asList()可变形参方式,从而将整 个int[]数组作为一个元素放在List集合
System.out.println(Arrays.asList(a1));
//String[]数组可以转换成Object[]数组,jdk1.5兼容jdk1.4,接收到String[]数组时会自动进行拆分,匹配Object[]数组
System.out.println(Arrays.asList(s1));
//打印Object对象,可以传入数组或单个对象
printObject(a1);
//printObject(a3);
printObject("xyz");
}
//数组的反射
public static void printObject(Object obj){
Class clazz=obj.getClass();
if(clazz.isArray()){
int len=Array.getLength(obj);
for(int i=0;i<len;i++)
System.out.println(Array.get(obj, i));
}else{
System.out.println(obj);
}
}
}
/*
运行结果:
true
[I
java.lang.Object
java.lang.Object
[I@54fe0ce1
[Ljava.lang.String;@72ffb35e
[[I@71591b4d
[[I@71591b4d
[Ljava.lang.String;@72ffb35e
[[I@54fe0ce1]
[a, b, c]
1
2
3
xyz
*/
软件开发时,一般先做框架,这样可以提高开发效率时,而做框架时,具体使用哪些类及类的具体功能可能都还未实现,不能直接用new创建某些对象,只能先从配置文件读取类名或传入类名字符串等进行反射,等运行时,再填补具体的对象。
package cn.itcast.day03;
import java.io.*;
import java.util.Properties;
public class ReflectDemo5 {
public static void main(String[] args) throws Exception
{
//这里相对路径针对的是当前java project的工作空间根目录,如当前java project是Blog3,Eclipse工作空间是F:\eclipsework\workspace,那么这里的相对路径根目录就是F:\eclipsework\workspace\Blog3
FileInputStream fis=new FileInputStream("config.properties");
//eclipse在保存文件时,会将源程序src文件夹中的java文件进行自动编译,并将生成的class文件自动放到bin目录下,将非java文件原封不动地移到bin目录下。
//下面这种方式的相对路径就是当前java project的class文件根目录,不含包名,在eclipse中编程时就是bin目录,完整路径是F:\eclipsework\workspace\Blog3\bin
//可以直接将config.properties文件写在java project中src目录下,这样该文件会自动被放到bin目录下
//使用这种方式获取配置文件时,路径名不能以"/"开头,否则会找不到文件
InputStream is=ReflectDemo5.class.getClassLoader().getResourceAsStream("cn/itcast/day03/config.properties");
//下面这种方式的相对路径是调用getResourceAsStream()方法的Class对象对应的class文件所在目录,含包名,即F:\eclipsework\workspace\Blog3\bin\cn\itcast\day03
//这种方式也可以使用绝对路径,加上包名,即"/cn/itcast/day03/config.properties
InputStream iss=ReflectDemo5.class.getResourceAsStream("Resource/config.properties");
Properties prop=new Properties();
prop.load(iss);
String className=prop.getProperty("className");
System.out.println(className);
}
}
1. 程序中写路径名是一定要用完整路径,但完整的路径不是硬编码,而是运算出来的。
2. 第一种方式除了可以读配置文件,还可以用FileWriter写配置文件。
3. 第二种和第三种方式,其实config.properties文件放置的路径目录是一样的,只不过程序中代码写法有点不同,第三种方式更简洁。
内省是专门用来操作JavaBean的。
get和set方法名中除get和set以外剩下的名称是属性名称,但使用属性名时要注意的是,如果属性名第二个字母为小写,则第一个字母也自动小写,如getAge()方法中的属性名应该是age。get和set方法一般是供外部访问成员变量的,多为public权限。
1. JavaEE开发中,经常要用到JavaBean,很多环境要求按照JavaBean方式进行操作。
2. JDK中提供了对JavaBean进行操作的一些API, 这套API就称为内省,用内省来操作JavaBean比使用普通类的方式更方便。
/*
定义Person类
*/
package cn.itcast.day03;
import java.util.Date;
public class Person{
String name;
int age;
Date birthday;
public Person(){
}
public Person(String name, int age, Date birthday) {
super();
this.name = name;
this.age = age;
this.birthday = birthday;
}
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 Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String toString(){
return this.name+"..."+this.age+"..."+this.birthday;
}
}
package cn.itcast.day03;
import java.beans.*;
import java.lang.reflect.*;
/*
给定对象和属性名,只知道类中有该属性的get和set方法,用内省来获取和设置对象的属性值。
*/
public class JavaBeanDemo1 {
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, IntrospectionException
{
Person p=new Person("lisi",20,"1987-09-09");
System.out.println(getProperty(p,"age"));
setProperty(p,"age",33);
System.out.println(getProperty(p,"age"));
}
public static void setProperty(Object obj,String propertyName,Object value) throws IntrospectionException,IllegalAccessException,InvocationTargetException
{
//获取属性描述器
PropertyDescriptor pd=new PropertyDescriptor(propertyName,obj.getClass());
//获取属性的get方法
Method writeMethod=pd.getWriteMethod();
writeMethod.invoke(obj, value);
//获取要操作的属性所属类型
System.out.println("属性类型:"+pd.getPropertyType());
}
public static Object getProperty(Object obj, String propertyName) throws IntrospectionException,IllegalAccessException,InvocationTargetException
{
PropertyDescriptor pd=new PropertyDescriptor(propertyName,obj.getClass());
//获取属性的set方法。
Method readMethod=pd.getReadMethod();
Object retVal=readMethod.invoke(obj);
return retVal;
}
//另一种方式获取属性值的方式,略显复杂,类似地的方式也可以重写setProperty方法。
public static Object getProperty2(Object obj, String propertyName) throws IntrospectionException,IllegalAccessException,InvocationTargetException
{
//BeanInfo对象封装了把这个类当作JavaBean看的结果信息,里面有javaBean的所有属性,包括从所有父类继承过来的属性。
BeanInfo beanInfo=Introspector.getBeanInfo(obj.getClass());
//下面的方式可以去除从Object类中继承的属性,只获取javabean自己的属性。
//BeanInfo beanInfo=Introspector.getBeanInfo(obj.getClass(), Object.class);
PropertyDescriptor[] pds=beanInfo.getPropertyDescriptors();
Object retVal=null;
for(PropertyDescriptor pd:pds){
if(pd.getName().equals(propertyName)){
Method readMethod=pd.getReadMethod();
retVal=readMethod.invoke(obj);
break;
}
}
return retVal;
}
}
/*
运行结果:
20
属性类型:int
33
*/
package cn.itcast.day03;
import java.lang.reflect.*;
import java.util.*;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
public class beanUtilsDemo {
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException
{
Person p1=new Person("lisi",20,"1987-09-09");
Person p2=new Person();
System.out.println(BeanUtils.getProperty(p1,"age"));
//BeanUtils中getProperty()得到的属性值都是String类型的,setProperty()方法参数形式是setProperty(Object obj,String name,Object value),如果value值为String字符串,BeanUtils也会自动该字符串转成实际属性的类型,但只限于8种基本数据类型,不能自动转成对象类型
System.out.println(BeanUtils.getProperty(p1,"age").getClass().getName());
BeanUtils.setProperty(p1, "age", "30");
//PropertyUtils中,getPropety()方法得到的属性值就是实际javabean中的属性类型,同样setProperty()方法中value值必须与javabean中属性实际类型一致,不能写成字符串形式,否则运行时会报IllegalArgumentException异常。
PropertyUtils.setProperty(p1, "age", 50);
System.out.println(PropertyUtils.getProperty(p1, "age").getClass().getName());
System.out.println(BeanUtils.getProperty(p1,"age"));
//将一个javabean对象的属性cope到另一个对象
BeanUtils.copyProperties(p2, p1);
System.out.println(BeanUtils.getProperty(p2,"name"));
//javabean与map之间的转换
//javabean转换成Map集合
Map map=BeanUtils.describe(p1);
System.out.println(map);
//BeanUtils还可直接操作Map集合
BeanUtils.setProperty(map, "age", 40);
System.out.println(map);
//Map集合封装成javabean
Person p3=new Person();
BeanUtils.populate(p3, map);
System.out.println(p3);
//BeanUtils中字符串和8种基本数据类型会按需要自动相互转换。
Map map2=new HashMap();
map2.put("name", "zhaoliu");
map2.put("age", "37");//写成37也可以
map2.put("birthday", "1987-02-02");
Person p4=new Person();
BeanUtils.populate(p4, map2);
System.out.println(p4);
}
}
/*
运行结果:
20
java.lang.String
java.lang.Integer
50
lisi
{birthday=1987-09-09, name=lisi, age=50, class=class cn.itcast.day03.Person}
{birthday=1987-09-09, name=lisi, age=40, class=class cn.itcast.day03.Person}
lisi...40...1987-09-09
zhaoliu...37...1987-02-02
*/
上面的演示中已经指出,BeanUtils中字符串和8种基本数据类型可以根据需要相互自动转换,那如果javabean中有一个属性不是基本数据类型,也不是字符串,怎么用BeanUtils设置该属性值呢?
这时候就需要在BeanUtils中注册一个转换器。
注册转换器是使用ConverUtils工具类中的register(Converter con, Class clazz)方法,其中的Converter是一个接口,该接口中只有一个方法Object convert(Class type, Object value),使用时只需复写该方法,可以使用匿名内部类实现。
/*注册Date日期转换器,方便用BeanUtils设置javabean的Date属性*/ import java.util.Date;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import org.apache.commons.beanutils.*;
import org.apache.commons.beanutils.locale.converters.DateLocaleConverter;
import cn.itcast.day03.Person11;
public class ConvertUtilsDemo {
public static void main(String[] args) throws Exception
{
setDate();
}
public static void setDate() throws Exception
{
Person p=new Person("tianqi",10,new Date(System.currentTimeMillis()));
String birthday="2002-10-09";
SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd");
System.out.println(df.format(PropertyUtils.getProperty(p, "birthday")));
//注册日期转换器
ConvertUtils.register(new Converter()
{
public Object convert(Class type,Object value){
if(value==null)
return null;
if(!(value instanceof String))
throw new ConversionException("只支持String类型的转换");
String str=(String)value;
if(str.trim().equals(""))
return null;
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
try{
return sdf.parse(str);
}
catch(ParseException e){
throw new RuntimeException(e);
}
}
}, Date.class);
BeanUtils.setProperty(p,"birthday",birthday);
//另一种方式 ,DateLocaleConverter是Converter接口的一个实现子类,但该类内部在bug,不太健壮,在value值为空时,仍可转换,不会报错。
ConvertUtils.register(new DateLocaleConverter(), Date.class);
System.out.println(df.format(PropertyUtils.getProperty(p, "birthday")));
}
}