Java的面向?qū)ο缶幊?/a>">Java的面向?qū)ο缶幊?/a>
1061
2025-04-02
作為一種重要特性,Java反射機(jī)制在很多地方會用到。在此做一小結(jié),供朋友們參考。
首先從一個問題開始著手。
可惡的問題又來了,NoSuchFieldException,如下圖所示:
完全不知道這個question是從哪里來的。以前也遇到過這樣的問題,后來解決了,但是沒有寫文檔,再次相遇這樣的問題,傻了。
經(jīng)過上網(wǎng)一番查找,發(fā)現(xiàn)遇到這個問題的小盆友還真不少,這個問題是屬于java反射機(jī)制里的。
這是一個反射對象時候的異常,已經(jīng)被捕獲了的。這個報錯代碼是混淆了的,是不是這個question對象被混淆成其他名了。。
源代碼如下:
public?static?
Class
//加載數(shù)據(jù)庫驅(qū)動
new?MysqlUtil();
//連接數(shù)據(jù)庫
MysqlUtil.GetConnection();
//?構(gòu)造一個初始容量為?10?的空列表。
List
//?表示占位符的第一個位置
int?index?=?1;
pstmt?=?connection.prepareStatement(sql);
System.out.println("MysqlUtil:"?+?params);
//?判斷所填充的占位符是否有值;判斷集合的標(biāo)準(zhǔn)方式
if?(params?!=?null?&&?!params.isEmpty())?{
for?(int?i?=?0;?i?
//?使用給定對象設(shè)置指定參數(shù)的值。第二個參數(shù)必須是Object類型
pstmt.setObject(index++,?params.get(i));
}
}
//?返回查詢結(jié)果
resultset?=?pstmt.executeQuery();
//?獲取列的相關(guān)信息
java.sql.ResultSetMetaData?metdata?=?resultset.getMetaData();
//?獲取列數(shù)
int?col_lenth?=?metdata.getColumnCount();
while?(resultset.next())?{
//?通過反射機(jī)制創(chuàng)建一個實(shí)例(生成對象)
T?resultObject?=?cls.newInstance();
for?(int?i?=?0;?i?
String?cols_name?=?metdata.getColumnName(i?+?1);
Object?cols_value?=?resultset.getObject(cols_name);
if?(cols_value?==?null)?{
cols_value?=?"";
}
//?通過字段名獲得反射(返回一個?Field?對象,該對象反映此?Class?對象所表示的類或接口的指定已聲明字段)
Field?field?=?cls.getDeclaredField(cols_name);
//?打開javabean的私有訪問權(quán)限
field.setAccessible(true);
field.set(resultObject,?cols_value);
}
list.add(resultObject);
}
//關(guān)閉數(shù)據(jù)庫
MysqlUtil.releaseConn();
return?list;
}
調(diào)用以上方法的方法如下:
public?static?void?main(String[]?args)?{
//Question?question?=?new?Question();
//
//Field?property?=?null;
//try?{
//property?=?question.getClass().getDeclaredField("description");
//}?catch?(NoSuchFieldException?e1)?{
//e1.printStackTrace();
//}?catch?(SecurityException?e1)?{
//e1.printStackTrace();
//}
//????????System.out.println(property);
List
String?sql?=?"SELECT?*?FROM?question?";
try?{
System.out.println(MysqlUtil.findMoreRefResult(sql,?params,?Question.class));
}?catch?(Exception?e)?{
e.printStackTrace();
}
}
在調(diào)用findMoreRefResult()方法的時候,請注意紅色背景部分,先獲取數(shù)據(jù)庫表中字段名,然后依據(jù)該字段名來反射此?Class?對象所表示的類或接口的指定已聲明字段。也就是說數(shù)據(jù)庫中的字段名應(yīng)該與類中的屬性字段名一一對應(yīng)才對,這是符合“建數(shù)據(jù)表時應(yīng)與類一一對應(yīng)”原則的。
下面,我們來深入學(xué)習(xí)一下Java的反射機(jī)制,只有做到深入了解某件事情,當(dāng)這件事情發(fā)生問題時,我們自然就會懂得解決之道。
注:下面的內(nèi)容只了解基本的原理就行,代碼可忽略。
一、反射的基礎(chǔ)---Class
Class是所有java類的一個總稱,就好像各式各樣的人都可以用Person來稱呼,每一個類被加載之后都會在內(nèi)存中生存一個Class對象,這個對象我們通常稱之為字節(jié)碼,而我們通過調(diào)用一個類創(chuàng)造的對象其實(shí)都是字節(jié)碼搞出來的,一個類只會產(chǎn)生一份字節(jié)碼。
那么我們怎么獲得一個類的Class呢?有三種方式:
1.調(diào)用某個類的class屬性
2.使用Class的forName()靜態(tài)方法
3.調(diào)用某個對象的getClass()方法。
下面我們通過一個實(shí)例來展示下上面兩點(diǎn):
ClassDemo1.java
package?com.lyl.exercise;
public?class?ClassDemo1?{
public?static?void?main(String[]?args)?throws?ClassNotFoundException{
String?str="iteye";
Class?cl1=String.class;
Class?cl2=str.getClass();
Class?cl3=Class.forName("java.lang.String");
System.out.println("str對象與String是同一份字節(jié)碼嗎?"+(cl1==cl2));
System.out.println("通過Class.forName()獲得的字節(jié)碼與String.class一樣嗎?"+(cl1==cl3));
}
}
通過查看JDK文檔,我們可以發(fā)現(xiàn),Class有許多方法,通過這些方法我們可以得到j(luò)ava類的相關(guān)信息,Constructor,Method,Field等,具體的大家可以參考JDK文檔。
二、反射的應(yīng)用
那么什么是反射呢?曾經(jīng)有人說過,反射就是把java類中的各種成分映射成相應(yīng)的java類。為什么呢?從上一個講解中大家是否發(fā)現(xiàn),通過Class我們可以解析出一個java類的各種成分,他們返回的也是一個類,所以這句話還是很有道理的。
下面讓我們來看看反射的一些應(yīng)用:
1.使用反射生成對象
通過反射來生成對象有兩種方式:
a.使用Class對象的newInstance()方法
b.使用Class對象獲取指定的Constructor對象,再調(diào)用Constructor對象的newInstance()方法來創(chuàng)建Class對應(yīng)類的實(shí)例。
實(shí)例:?ClassDemo3.java
package?com.lyl.exercise;
import?java.lang.reflect.Constructor;
public?class?ClassDemo3?{
public?static?void?main(String[]?args)?throws?Exception{
//使用Class對象的newInstance()方法
String?str=(String)Class.forName("java.lang.String").newInstance();
//使用Class對象獲取指定的Constructor對象,再調(diào)用Constructor對象的newInstance()方法來創(chuàng)建Class對應(yīng)類的實(shí)例。
Constructor?constructor=String.class.getConstructor(StringBuffer.class);
String?str2=(String)constructor.newInstance(new?StringBuffer("abc"));
}
}
2.成員變量的反射
接上實(shí)例大家看看:
ReflectDemo1.java
package?com.lyl.exercise;
import?java.lang.reflect.Field;
public?class?ReflectDemo1?{
public?static?void?main(String[]?args)?throws?Exception?{
ReflectHelper?rh=new?ReflectHelper("iteye",?"javaeye");
Field?fieldb=rh.getClass().getField("b");
//fieldb只是類的字段,不是對象的,所以要想獲得對象上的值,要使用get()方法
System.out.println(fieldb.get(rh));
//如果我們使用上面方法來訪問a的值呢?拋出java.lang.NoSuchFieldException
/*Field?fielda=rh.getClass().getField("a");
System.out.println(fieldb.get(rh));
因?yàn)閍是私有成員變量
*/
//我們可以通過暴力反射來獲取它的值
Field?fielda=rh.getClass().getDeclaredField("a");
//設(shè)置為可以訪問
fielda.setAccessible(true);
System.out.println(fielda.get(rh));
}
}
ReflectHelper.java
package?com.lyl.exercise;
public?class?ReflectHelper?{
private?String?a;
public?String?b;
public?ReflectHelper(String?a,String?b){
this.a=a;
this.b=b;
}
}
如果將上面的搞懂,那么我們不妨來做一道經(jīng)典的題目:將一個類中所有String類型的成員變量,其中含有的‘a(chǎn)’替換為‘b’。
這個題目很明顯通過反射可以輕易的完成,大家不妨來看看。
package?com.lyl.exercise;
import?java.lang.reflect.Field;
public?class?ReflectDemo2?{
public?static?void?main(String[]?args)?throws?Exception?{
ReflectHelper?rh=new?ReflectHelper("abc",?"basketball");
changeStringValue(rh);
System.out.println(rh);
}
private?static?void?changeStringValue(Object?object)?throws?Exception{
Field[]?fields=object.getClass().getFields();
for(Field?field:fields){
if(field.getType()==String.class){
String?oldValue=(String)field.get(object);
String?newValue=oldValue.replace('a','b');
//將object的String類型的變量替換為newValue
field.set(object,?newValue);
}
}
}
}
這樣就搞定了,是不是很簡單?
一、ClassLoader初步
類加載器負(fù)責(zé)加載所有的類,系統(tǒng)為所有被載入內(nèi)存中的類生成一個java.lang.Class實(shí)例。一旦一個類被載入到JVM中,同一個類就不會再次被載入了,這是針對同一個加載器,不同的加載器還是可以加載同一個類的,不同加載器加載同一個類在JVM中是不同的。因?yàn)樵贘VM中用類的全限定類名加類加載器作為其唯一標(biāo)識。
在JVM啟動時,會形成有三個類加載器組成的初始類加載器層次結(jié)構(gòu):
-->Bootstrap?ClassLoader:根類加載器
-->Extension?ClassLoader:擴(kuò)展類加載器
-->System?ClassLoader:系統(tǒng)類加載器
Bootstrap?ClassLoader是使用C++寫的,我們是無法得到它的源碼,它是java的核心Loader,比如我們通過String.class.getClassLoader()是獲得不到它的名字的,返回的是空值。
如果父類加載器加載了某個類,子類就不會加載了。
ClassLoader動態(tài)加載:
a.并非一次性加載
b.需要運(yùn)行時才加載
c.可以觀察類的具體加載過程:java?-verbose:class??在eclipse中只要配置后面的-verbose:class
d.static語句塊在加載后執(zhí)行一次。
e.dynamic語句塊每次new新的對象都會執(zhí)行
等同于構(gòu)造方法中的語句。
用的比較少。
具體看參見:http://gzcj.iteye.com/blog/394644
二、使用反射實(shí)現(xiàn)JDK動態(tài)代理
在java中提供了Proxy類和一個InvocationHandler接口,通過他們倆我們就可以創(chuàng)建JDK動態(tài)代理,下面讓我們通過一個實(shí)例來看看如何使用吧:
Person接口
package?com.lyl.reflect;
public?interface?Person?{
void?walk();
void?sayHello(String?name);
}
MyInvocationHandler.java
package?com.lyl.reflect;
import?java.lang.reflect.InvocationHandler;
import?java.lang.reflect.Method;
public?class?MyInvocationHandler?implements?InvocationHandler?{
@Override
public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)
throws?Throwable?{
System.out.println("正在運(yùn)行的方法:"+method);
if(args!=null){
System.out.println("執(zhí)行該方法傳入的參數(shù):");
for(Object?arg:args){
System.out.println(arg);
}
}else?{
System.out.println("該方法沒有傳入?yún)?shù)!");
}
return?null;
}
}
ProxyTest.java
package?com.lyl.reflect;
import?java.lang.reflect.InvocationHandler;
import?java.lang.reflect.Proxy;
public?class?ProxyTest?{
public?static?void?main(String[]?args)?{
InvocationHandler?handler=new?MyInvocationHandler();
//返回Person接口的代理類實(shí)例
Person?person=(Person)Proxy.newProxyInstance(Person.class.getClassLoader(),?new?Class[]{Person.class},?handler);
person.walk();
person.sayHello("ITEYE");
}
}
通過Proxy類的newProxyInstance()方法我們可以獲得一個指定接口的代理類實(shí)例,在MyInvocationHandler中會自動執(zhí)行invoke方法,執(zhí)行結(jié)果如下:
正在運(yùn)行的方法:public?abstract?void?com.lyl.reflect.Person.walk()
該方法沒有傳入?yún)?shù)!
正在運(yùn)行的方法:public?abstract?void?com.lyl.reflect.Person.sayHello(java.lang.String)
執(zhí)行該方法傳入的參數(shù):
ITEYE
首先必須明一點(diǎn)?Field類主要是用來輔助獲取和操作類的屬性的!
1.怎么通過反射獲取類的屬性
先來看JDK提供的方法有如下幾種:
a)Class.getDeclaredField(String?name);
返回一個?Field?對象,該對象反映此?Class?對象所表示的類或接口的指定已聲明字段。
b)Class.getDeclaredFields();
返回?Field?對象的一個數(shù)組,這些對象反映此?Class?對象所表示的類或接口所聲明的所有字段。
c)Class.getField(String?name);
返回一個?Field?對象,它反映此?Class?對象所表示的類或接口的指定公共成員字段。
d)Class.getField();
返回一個包含某些?Field?對象的數(shù)組,這些對象反映此?Class?對象所表示的類或接口的所有可訪問公共字段。
來一個例子來說明一下?:
實(shí)體類:
import?java.util.Date;
/**
*?@ClassName:?Student
*?@Description:?學(xué)生實(shí)體
*?@author??***
*?@date?2014-3-18?下午5:17:35
*?@version?V1.0
*/
public?class?Student
{
private?Long?id;
private?String?name;
private?Date?createdate;
private?String?no;
public?String?nickname;
public?Long?getId()
{
return?id;
}
public?void?setId(Long?id)
{
this.id?=?id;
}
public?String?getName()
{
return?name;
}
public?void?setName(String?name)
{
this.name?=?name;
}
................
}
測試類:
import?java.lang.reflect.Field;
import?java.util.Date;
/**
*?@ClassName:?ReflectFieldTest
*?@Description:?反射Field測試。
*?@author??***
*?@date?2014-3-18?下午5:16:17
*?@version?V1.0
*/
public?class?ReflectFieldTest
{
public?static?void?main(String[]?args)
{
Student?stu=new?Student();
stu.setId(1L);
stu.setName("Josean");
stu.setNo("201403185203344");
stu.setCreatedate(new?Date());
try
{
Field?property1=stu.getClass().getDeclaredField("name");
//private?java.lang.String?com.cx.test.Student.name
System.out.println(property1);
Field?property3=stu.getClass().getField("nickname");
//public?java.lang.String?com.cx.test.Student.nickname
System.out.println(property3);
//錯誤方法?getField系列方法只能獲取公共字段
//Field?property2=stu.getClass().getField("name");
//System.out.println(property2);
//會拋java.lang.NoSuchFieldException
}?catch?(SecurityException?e)
{
e.printStackTrace();
}?catch?(NoSuchFieldException?e)
{
e.printStackTrace();
}
}
}
2.進(jìn)行屬性獲取、更改
得到這個Field之后你就可以獲取他的值或者設(shè)置他的值了。
獲取它的值用get類型方法,針對常見類型提供了對應(yīng)get方法,這里就不一一列舉了。
值得注意的是獲取私有屬性的時候必須先設(shè)置Accessible為true,然后才能獲取。
同理設(shè)置的時候調(diào)用set類型方法,這里也不一一列舉了,下面放代碼。
import?java.lang.reflect.Field;
import?java.util.Date;
/**
*?@ClassName:?ReflectFieldTest
*?@Description:?反射Field測試。
*?@author?JoseanLuo
*?@date?2014-3-18?下午5:16:17
*?@version?V1.0
*/
public?class?ReflectFieldTest
{
public?static?void?main(String[]?args)?throws?Exception
{????Student?stu=new?Student();
stu.setId(1L);
stu.setName("Josean");
stu.setNo("201403185203344");
stu.setCreatedate(new?Date());
stu.setNickname("copyman");
Field?property1=stu.getClass().getDeclaredField("name");
//System.out.println(property1);
//out:private?java.lang.String?com.cx.test.Student.name
Field?property3=stu.getClass().getField("nickname");
System.out.println(property3.get(stu));
//System.out.println(property3);
//out:public?java.lang.String?com.cx.test.Student.nickname
//錯誤方法?getField系列方法只能獲取公共字段
//Field?property2=stu.getClass().getField("name");
//System.out.println(property2);
//會拋java.lang.NoSuchFieldException
Field?[]?prFields4=stu.getClass().getDeclaredFields();
for(Field?field:prFields4)
{
System.out.println(field);
System.out.println(field.equals(property1));
//私有變量必須先設(shè)置Accessible為true
field.setAccessible(true);
//獲取用get類方法。
System.out.println(field.get(stu));
}
//設(shè)置用set類方法
property3.set(stu,?"名字被我改了,哈哈");
System.out.println(stu.getNickname());
}
}
這個是控制臺輸出:
copyman
private?java.lang.Long?com.cx.test.Student.id
false
1
private?java.lang.String?com.cx.test.Student.name
true
Josean
private?java.util.Date?com.cx.test.Student.createdate
false
Tue?Mar?18?18:19:39?CST?2014
private?java.lang.String?com.cx.test.Student.no
false
201403185203344
public?java.lang.String?com.cx.test.Student.nickname
false
copyman
名字被我改了,哈哈
Java 數(shù)據(jù)庫
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。