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")));
}
}