挖掘Jakarta Commons中隱藏的寶貝
如果你不熟悉Jakarta Commons話,那么很有可能你已經(jīng)重新發(fā)明了好幾個(gè)輪子。在你編寫(xiě)更多的普通的框架或工具之前,體驗(yàn)一下Commons吧。它將會(huì)大大地節(jié)約你的時(shí) 間。太多的人自己寫(xiě)一個(gè),其實(shí)是與Commons Lang中的StringUtils重復(fù)的StringUtils類,或者,開(kāi)發(fā)者不知道從Commons Collections中重新創(chuàng)建工具,哪怕commons-collections.jar已經(jīng)在classpath中可用了。真的, 請(qǐng)停一下。看看Commons Collections API,然后再回到你的任務(wù)中;我發(fā)誓你會(huì)發(fā)現(xiàn)一些簡(jiǎn)單有用的東西可以幫你在明年節(jié)省一周的時(shí)間。如果大家花一點(diǎn)時(shí)間看看Jakarta Commons,我們將會(huì)得到更少的重復(fù)代碼—我們將在重用的宗旨下真正做一些有用的事情。
我確實(shí)看到這樣的情況發(fā)生過(guò):一些人研究了一 下Commons BeanUtils或者Commons Collections,然后總是有“啊,如果我那時(shí)知道這個(gè)的話,我就不會(huì)寫(xiě)那一萬(wàn)行的代碼了”這樣的時(shí)刻。Jakarta Commons仍有一部分保持相當(dāng)?shù)纳衩?比如,許多人還沒(méi)有聽(tīng)說(shuō)過(guò)Commons CLI和Commons Configuration,并且大多數(shù)人還沒(méi)有注意到Commons Collections中的functors(算子)包的價(jià)值。在這一系列中,我會(huì)專門強(qiáng)調(diào)一些Jakarta Commons中較少得到重視的工具和功能。
在這一系列的第一部分,我將探索定義在Commons Digester中的XML規(guī)則,Commons Collections中的功能,和使用一個(gè)有趣的應(yīng)用,Commons JXPath,來(lái)查詢一個(gè)對(duì)象的List。Jakarta Commons包含的功能目的在于幫助你解決低層次的編程問(wèn)題:遍歷集合,解析XML和從List中檢出對(duì)象。我建議你花一些時(shí)間在這些小功能上,學(xué)習(xí) Jakarta Commons真的會(huì)為你節(jié)省不少時(shí)間。
并不簡(jiǎn)單地是學(xué)習(xí)使用Commons Digester來(lái)解析XML或者使用CollectionUtils的Predicate來(lái)過(guò)濾一個(gè)集合,而是當(dāng)你一旦意識(shí)到如何將這些功能組合起來(lái)使 用并且如何將Commons集成到你的項(xiàng)目中去的時(shí)候,你才會(huì)真正地看到它的好處。如果你這樣做地話,你將會(huì)把commons-lang.jar, commons-beanutils.jar,和 commons-digester.jar當(dāng)成JVM本身來(lái)看待。
如果你對(duì) Jakarta Commons更深的內(nèi)容感興趣的話,可以看一下Jakarta Commons Cookbook。這本書(shū)給你很多方法來(lái)更好的使用Commons,并告訴你如何將Jakarta Commons與其它的小的開(kāi)源組件集成,如Velocity, FreeMarker, Lucene, 和 Jakarta Slide。這本書(shū),我介紹了一組廣泛的工具從Commons Lang中的簡(jiǎn)單工具到組合了Commons Digester, Commons Collections, 和Jakarta Lucene來(lái)搜索威廉.莎士比亞的著作。我希望這一系列和Jakarta Commons Cookbook這本書(shū)能夠提供給你一些有趣的低層次的編程問(wèn)題的解決方案。
1. 用于Commons Digester的基于XML的規(guī)則集
Commons Digester 1.6提供了將XML轉(zhuǎn)化為對(duì)象的最簡(jiǎn)單的方法。Digester已經(jīng)由O'Reilly網(wǎng)站上的兩篇文章介紹過(guò)了:“學(xué)習(xí)和使用Jakarta Digester”,作者是Philipp K. Janert,和“使用Jakarta Commons, 第二部分”,作者是Vikram Goyal。兩篇文章都演示了XML規(guī)則集的使用,但如何在XML中定義規(guī)則集并沒(méi)有理解。大多所見(jiàn)到的Digester的使用是程序化地定義規(guī)則集,以 已編譯的形式。你應(yīng)該避免硬編碼的Digester規(guī)則,特別是當(dāng)你可以將映射信息存儲(chǔ)在外部文件中或一個(gè)類路徑資源中時(shí)。外部化一個(gè)Digester規(guī) 則可以更好地適應(yīng)一個(gè)演化中的XML文檔結(jié)構(gòu)或者說(shuō)一個(gè)演化中的對(duì)象模型。
為了演示在XML中定義規(guī)則集與硬編碼的規(guī)則集之間的區(qū)別,考慮系統(tǒng)解析XML給一個(gè)Person bean,包括在下面定義的屬性—id, name和age。
package org.test;
public class Person {
public String id;
public String name;
public int age;
public Person() {}
public String getId() { return id; }
public void setId(String id) {
this.id = id;
}
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;
}
}
確認(rèn)你的應(yīng)用需要解析一個(gè)包含了多個(gè)person元素的XML文件。下面的XML文件,data.xml,包含了兩個(gè)person元素,你想要把它們解析到Person對(duì)象中:
Tom Higgins
25
Barney Smith
75
Susan Shields
53
你 希望如果結(jié)構(gòu)和XML文件的內(nèi)容在未來(lái)幾個(gè)月中變化,你不需要在已編譯的Java代碼中硬編碼XML文件的結(jié)構(gòu)。為了做到這一點(diǎn),你需要在一個(gè)XML文件 中定義Digester的規(guī)則,并且它可以作為一種資源從類路徑中裝入。下面的XML文檔,person-rules.xml,映射person元素到 Person bean:
paramtype="java.lang.Object"/>
上述所做的是指示Digester創(chuàng)建一個(gè)新的Person實(shí)例,當(dāng)它遇到一個(gè)person元素時(shí),調(diào)用add()來(lái)將Person對(duì)象加入到一個(gè)ArrayList中,設(shè)置person元素中相匹配的屬性,并從下一級(jí)元素name和age中設(shè)置name和age的屬性。
現(xiàn) 在你已經(jīng)看到了Person類,會(huì)被解析的文檔,和以XML的形式定義的Digester規(guī)則。現(xiàn)在你需要?jiǎng)?chuàng)建一個(gè)由person-rules.xml定 義了規(guī)則的Digester的實(shí)例。下面的代碼創(chuàng)建 了一個(gè)Digester,通過(guò)將person-rules.xml的URL傳遞給DigesterLoader
既然person-rules.xml文件是與解析它的類在同一個(gè)包內(nèi)的類路徑資源,URL可以通過(guò)getClass().getResource()來(lái)得到。DigesterLoader然后解析規(guī)則并將它加到新創(chuàng)建的Digester上:
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.xmlrules.DigesterLoader;
// 從XML規(guī)則集中配置Digester
URL rules = getClass().getResource("./person-rules.xml");
Digester digester =
DigesterLoader.createDigester(rules);
// 將空的List推入到Digester的堆棧
List people = new ArrayList();
digester.push( people );
// 解析XML文檔
InputStream input = new FileInputStream( "data.xml" );
digester.parse( input );
一旦Digester完成對(duì)data.xml的解析,三個(gè)Person對(duì)象將會(huì)在ArrayList people中。
與 將規(guī)則定義在XML不同的方法是使用簡(jiǎn)便的方法將它們加入到一個(gè)Digester實(shí)例中。大多數(shù)文章和例子都用這種方法,使用 addObjectCreate() 和 addBeanPropertySetter()這樣的方法來(lái)將規(guī)則加入中Digester上。下面的代碼加入了與定義在person- rules.xml中相同的規(guī)則:
digester.addObjectCreate("people/person", Person.class);
digester.addSetNext("people/person", "add", "java.lang.Object");
digester.addBeanPropertySetter("people/person", "name");
digester.addBeanPropertySetter("people/person", "age");
如果你曾經(jīng)發(fā)現(xiàn)自己正在用一個(gè)有著2500行代碼的類,用SAX來(lái)解析一個(gè)巨大的XML文檔,或者
使用DOM或JDOM的完整的一個(gè)集合類,你就會(huì)理解XML的解析比它應(yīng)該做的要復(fù)雜的多,就大多數(shù)情況來(lái)說(shuō)。如果你正在建一個(gè)有著嚴(yán)格的速度和內(nèi)存要求
的高效的系統(tǒng),你會(huì)需要SAX解析器的速度。如果你需要DOM級(jí)別3的復(fù)雜度,你會(huì)需要像Apache
Xerces的解析器。但如果你只是簡(jiǎn)單的試圖將幾個(gè)XML文檔解析到對(duì)象中去的話,看一下Commons Digester,
并把你的規(guī)則定義在一個(gè)XML文件中。
任何時(shí)候你都應(yīng)該將配置信息從硬編碼中移出來(lái)。我會(huì)建議你在一個(gè)XML文件中定義規(guī)則并從文件系統(tǒng)
或類路徑中裝入它。這樣可以使你的程序更好地適應(yīng)XML文檔以及對(duì)象模型的變化。有關(guān)在XML文件中定義Digester規(guī)則的更多的資料,參看
Jakarta Commons Cookbook一書(shū)的6.2節(jié),“將XML文檔轉(zhuǎn)換為對(duì)象”
2.Commons Collections中的算子
算
子成為Commons Collections
3.1中的有趣的部分有兩個(gè)原因:它們沒(méi)有得到應(yīng)得的重視并且它們有改變你編程的方式的潛力。算子只是一個(gè)奇特的名字,它代表了一個(gè)包裝了函數(shù)的對(duì)象—一
個(gè)“函數(shù)對(duì)象”。當(dāng)然,它們不是一回事。如果你曾經(jīng)使用過(guò)C和C++的方法指針,你就會(huì)理解算子的威力。
一個(gè)算子是一個(gè)對(duì)象—一個(gè)Predicate,一個(gè)Closure, 一個(gè)Transformer。
Predicates
求對(duì)象的值并返回一個(gè)boolean,Transformer求對(duì)象的值并返回新對(duì)象,Closure接受對(duì)象并執(zhí)行代碼。算子可以被組合成組合算子來(lái)模
仿循環(huán),邏輯表達(dá)式,和控制結(jié)構(gòu),并且算子也可以被用來(lái)過(guò)濾和操作集合中的元素。在這么短的篇幅中解釋清楚算子是不可能的,所以跳過(guò)介紹,我將會(huì)通過(guò)使用
和不使用算子來(lái)解決同一問(wèn)題(解釋算子)。在這個(gè)例子中,從一個(gè)ArrayList中而來(lái)的Student對(duì)象會(huì)被排序到兩個(gè)List中,如果他們符合某
種標(biāo)準(zhǔn)的話。
成績(jī)?yōu)锳的學(xué)生會(huì)被加到honorRollStudents(光榮榜)中,得D和F的學(xué)生被加到
problemStudents
(問(wèn)題學(xué)生)list中。學(xué)生分開(kāi)以后,系統(tǒng)將會(huì)遍歷每個(gè)list,給加入到光榮榜中學(xué)生一個(gè)獎(jiǎng)勵(lì),并安排與問(wèn)題學(xué)生的家長(zhǎng)談話的時(shí)間表。下面的代碼不使
用算子實(shí)現(xiàn)了這個(gè)過(guò)程:
List allStudents = getAllStudents();
// 創(chuàng)建兩個(gè)ArrayList來(lái)存放榮譽(yù)學(xué)生和問(wèn)題學(xué)生
List honorRollStudents = new ArrayList();
List problemStudents = new ArrayList();
// 遍歷所有學(xué)生,將榮譽(yù)學(xué)生放入一個(gè)List,問(wèn)題學(xué)生放入另一個(gè)
Iterator allStudentsIter = allStudents.iterator();
while( allStudentsIter.hasNext() ) {
Student s = (Student) allStudentsIter.next();
if( s.getGrade().equals( "A" ) ) {
honorRollStudents.add( s );
} else if( s.getGrade().equals( "B" ) &&
s.getAttendance() == PERFECT) {
honorRollStudents.add( s );
} else if( s.getGrade().equals( "D" ) ||
s.getGrade().equals( "F" ) ) {
problemStudents.add( s );
} else if( s.getStatus() == SUSPENDED ) {
problemStudents.add( s );
}
}
// 對(duì)于的有榮譽(yù)學(xué)生,增加一個(gè)獎(jiǎng)勵(lì)并存儲(chǔ)到數(shù)據(jù)庫(kù)中
Iterator honorRollIter =
honorRollStudents.iterator();
while( honorRollIter.hasNext() ) {
Student s = (Student) honorRollIter.next();
// 給學(xué)生記錄增加一個(gè)獎(jiǎng)勵(lì)
s.addAward( "honor roll", 2005 );
Database.saveStudent( s );
}
// 對(duì)所有問(wèn)題學(xué)生,增加一個(gè)注釋并存儲(chǔ)到數(shù)據(jù)庫(kù)中
Iterator problemIter = problemStudents.iterator();
while( problemIter.hasNext() ) {
Student s = (Student) problemIter.next();
// 將學(xué)生標(biāo)記為需特殊注意
s.addNote( "talk to student", 2005 );
s.addNote( "meeting with parents", 2005 );
Database.saveStudent( s );
}
上述例子是非常過(guò)程化的;要想知道Student對(duì)象發(fā)生了什么事必須遍歷每一行代碼。例子的第一部分是基于成績(jī)和考勤對(duì)Student對(duì)象進(jìn)行邏輯判斷。
第二部分對(duì)Student對(duì)象進(jìn)行操作并存儲(chǔ)到數(shù)據(jù)庫(kù)中。像上述這個(gè)有著50行代碼程序也是大多程序所開(kāi)始的—可管理的過(guò)程化的復(fù)雜性。但是當(dāng)需求變化時(shí),問(wèn)題出現(xiàn)了。一旦判斷邏輯改變,你就需要在第一部分中增加更多的邏輯表達(dá)式。
舉 例來(lái)說(shuō),如果一個(gè)有著成績(jī)B和良好出勤記錄,但有五次以上的留堂記錄的學(xué)生被判定為問(wèn)題學(xué)生,那么你的邏輯表達(dá)式將會(huì)如何處理?或者對(duì)于第二部分中,只有 在上一年度不是問(wèn)題學(xué)生的學(xué)生才能進(jìn)入光榮榜的話,如何處理?當(dāng)例外和需求開(kāi)始改變進(jìn)而影響到過(guò)程代碼時(shí),可管理的復(fù)雜性就會(huì)變成不可維護(hù)的面條式的代 碼。
從上面的例子中回來(lái),考慮一下那段代碼到底在做什么。它在一個(gè)List遍歷每一個(gè)對(duì)象,檢查標(biāo)準(zhǔn),如果適用該標(biāo)準(zhǔn),對(duì)此對(duì)象進(jìn)行某些 操作。上述例子可以進(jìn)行改進(jìn)的關(guān)鍵一處在于從代碼中將標(biāo)準(zhǔn)與動(dòng)作解藕開(kāi)來(lái)。下面的兩處代碼引用以一種非常不同的方法解決了上述的問(wèn)題。首先,榮譽(yù)榜和問(wèn)題 學(xué)生的標(biāo)準(zhǔn)被兩個(gè)Predicate對(duì)象模型化了,并且加之于榮譽(yù)學(xué)生和問(wèn)題學(xué)生上的動(dòng)作也被兩個(gè)Closure對(duì)象模型化了。這四個(gè)對(duì)象如下定義:
import org.apache.commons.collections.Closure;
import org.apache.commons.collections.Predicate;
// 匿名的Predicate決定一個(gè)學(xué)生是否加入榮譽(yù)榜
Predicate isHonorRoll = new Predicate() {
public boolean evaluate(Object object) {
Student s = (Student) object;
return( ( s.getGrade().equals( "A" ) ) ||
( s.getGrade().equals( "B" ) &&
s.getAttendance() == PERFECT ) );
}
};
//匿名的Predicate決定一個(gè)學(xué)生是否是問(wèn)題學(xué)生
Predicate isProblem = new Predicate() {
public boolean evaluate(Object object) {
Student s = (Student) object;
return ( ( s.getGrade().equals( "D" ) ||
s.getGrade().equals( "F" ) ) ||
s.getStatus() == SUSPENDED );
}
};
//匿名的Closure將一個(gè)學(xué)生加入榮譽(yù)榜
Closure addToHonorRoll = new Closure() {
public void execute(Object object) {
Student s = (Student) object;
// 對(duì)學(xué)生增加一個(gè)榮譽(yù)記錄
s.addAward( "honor roll", 2005 );
Database.saveStudent( s );
}
};
// 匿名的Closure將學(xué)生標(biāo)記為需特殊注意
Closure flagForAttention = new Closure() {
public void execute(Object object) {
Student s = (Student) object;
// 標(biāo)記學(xué)生為需特殊注意
s.addNote( "talk to student", 2005 );
s.addNote( "meeting with parents", 2005 );
Database.saveStudent( s );
}
};
這 四個(gè)匿名的Predicate和Closure是從作為一個(gè)整體互相分離的。flagForAttention(標(biāo)記為注意)并不知道什么是確定一個(gè)問(wèn)題 學(xué)生的標(biāo)準(zhǔn) 。現(xiàn)在需要的是將正確的Predicate和正確的Closure結(jié)合起來(lái)的方法,這將在下面的例子中展示:
import org.apache.commons.collections.ClosureUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.functors.NOPClosure;
Map predicateMap = new HashMap();
predicateMap.put( isHonorRoll, addToHonorRoll );
predicateMap.put( isProblem, flagForAttention );
predicateMap.put( null, ClosureUtils.nopClosure() );
Closure processStudents =
ClosureUtils.switchClosure( predicateMap );
CollectionUtils.forAllDo( allStudents, processStudents );
在 上面的代碼中,predicateMap將Predicate與Closure進(jìn)行了配對(duì);如果一個(gè)學(xué)生滿足作為鍵值的Predicate的條件,那么它 將把它的值傳到作為Map的值的Closure中。通過(guò)提供一個(gè)NOPClosure值和null鍵對(duì),我們將把不符合任何Predicate條件的 Student對(duì)象傳給由ClosureUtils調(diào)用創(chuàng)建的“不做任何事”或者“無(wú)操作”的NOPClosure。
一個(gè) SwitchClosure, processStudents,從predicateMap中創(chuàng)建。并且通過(guò)使用CollectionUtils.forAllDo()方法,將 processStudents Closure應(yīng)用到allStudents中的每一個(gè)Student對(duì)象上。這是非常不一樣的處理方法;記住,你并沒(méi)有遍歷任何隊(duì)列。而是通過(guò)設(shè)置規(guī)則 和因果關(guān)系,以及CollectionUtils和SwitchClosur來(lái)完成了這些操作。
當(dāng)你將使用Predicate的標(biāo)準(zhǔn)與使 用Closure的動(dòng)作將分離開(kāi)來(lái)時(shí),你的代碼的過(guò)程式處理就少了,而且更容易測(cè)試了。isHonorRoll Predicate能夠與addToHonorRoll Closure分離開(kāi)來(lái)進(jìn)行獨(dú)立的單元測(cè)試,它們也可以合起來(lái)通過(guò)使用Student類的模仿對(duì)象進(jìn)行測(cè)試。第二個(gè)例子也會(huì)演示 CollectionUtils.forAllDo(),它將一個(gè)Closure應(yīng)用到了一個(gè)Collection的每一個(gè)元素中。
你也許注意到了使用算子并沒(méi)用減少代碼行數(shù),實(shí)際上,使用算子還增加了代碼量。但是,通過(guò)算子,你得到了將到了標(biāo)準(zhǔn)與動(dòng)作的模塊性與封裝性的好處。如果你的代碼題已經(jīng)接近于幾百行,那么請(qǐng)考慮一下更少過(guò)程化處理,更多面向?qū)ο蟮慕鉀Q方案—通過(guò)使用算子。
Jakarta Commons Cookbook中的第四章“算子”介紹了Commons Collections中可用的算子,在第五章,“集合”中,向你展示了如何使用算子來(lái)操作Java 集合類API。
所 有的算子-- Closure, Predicate, 和 Transformer—能夠被合并為合并算子來(lái)處理任何種類的邏輯問(wèn)題。switch, while和for結(jié)構(gòu)能夠被SwitchClosure, WhileClosure, 和 ForClosure模型化。
復(fù)合的邏輯表達(dá)式可 以被多個(gè)Predicate構(gòu)建,通過(guò)使用OrPredicate, AndPredicate, AllPredicate, 和 NonePredicate將它們相互聯(lián)接。Commons BeanUtils也包含了算子的實(shí)現(xiàn)被用來(lái)將算子應(yīng)用到bean的屬性中-- BeanPredicate, BeanComparator, 和 BeanPropertyValueChangeClosure。算子是考慮底層的應(yīng)用架構(gòu)的不一樣的方法,它們可以很好地改造你編碼實(shí)現(xiàn)的方法。
3. 使用XPath語(yǔ)法來(lái)查詢對(duì)象和集合
Commons JXPath是一種讓人很吃驚地(非標(biāo)準(zhǔn)的)對(duì)XML標(biāo)準(zhǔn)的使用。XPath一段時(shí)間以來(lái)一直是作為在一個(gè)XSL樣式表中選擇結(jié)點(diǎn)或結(jié)點(diǎn)集的一種方法。如果你用過(guò)XML,你會(huì)很熟悉用這樣的語(yǔ)法/foo/bar來(lái)從foo文檔元素中選擇bar子元素。
Jakarta Commons JXPath增加了一種有趣的手法:你可以用JXPath來(lái)從bean和集合中選擇對(duì)象,其中如servlet上下文和DOM文檔對(duì)象。考慮一個(gè)包含了 Person對(duì)象的列表。每一個(gè)Person對(duì)象有一個(gè)屬性的類型為Job,每一個(gè)Job對(duì)象有一個(gè)salary(薪水)屬性,類型為int。 Person對(duì)象也有一個(gè)coountry屬性,它是兩個(gè)字符的國(guó)家代碼。使用JXPath,你可以很容易地選出所有國(guó)家為美國(guó),薪水超過(guò)一百萬(wàn)美元的 Person對(duì)象。下面是設(shè)置一個(gè)由JXPath過(guò)濾地bean的List的代碼:
// Person的構(gòu)造器設(shè)置姓和國(guó)家代碼
Person person1 = new Person( "Tim", "US" );
Person person2 = new Person( "John", "US" );
Person person3 = new Person( "Al", "US" );
Person person4 = new Person( "Tony", "GB" );
// Job的構(gòu)造器設(shè)工作名稱和薪水
person1.setJob( new Job( "Developer", 40000 ) );
person2.setJob( new Job( "Senator", 150000 ) );
person3.setJob( new Job( "Comedian", 3400302 ) );
person4.setJob( new Job( "Minister", 2000000 ) );
Person[] personArr =
new Person[] { person1, person2,
person3, person4 };
List people = Arrays.asList( personArr );
people List包含了四個(gè)bean: Tim, John, Al, 和George。Tim是一個(gè)掙4萬(wàn)美元的開(kāi)發(fā)者,John是一個(gè)掙15萬(wàn)美元的參議員,Al是一個(gè)掙340萬(wàn)美元的喜劇演員,Tony是一個(gè)掙200萬(wàn) 歐元的部長(zhǎng)。我們的任務(wù)很簡(jiǎn)單:遍歷這個(gè)List,打印出每一個(gè)掙錢超過(guò)100百萬(wàn)美元的美國(guó)公民的名字。記住people是一個(gè)由Person對(duì)象構(gòu)成 的ArrayList,讓我們先看一下沒(méi)有利用JXPath便利的解決方案:
Iterator peopleIter = people.getIterator();
while( peopleIter.hasNext() ) {
Person person = (Person) peopleIter.next();
if( person.getCountry() != null &&
person.getCountry().equals( "US" ) &&
person.getJob() != null &&
person.getJob().getSalary() > 1000000 ) {
print( person.getFirstName() + " "
person.getLastName() );
}
}
}
}
上 面的例子是繁重的,并有些容易犯錯(cuò)。為了發(fā)現(xiàn)合適的Person對(duì)象,你必須首先遍歷每一個(gè)Person對(duì)象并且檢查conuntry的屬性。如果 country屬性不為空并且符合要求,那么你就要檢查job屬性并看一下它是否不為空并且salary屬性的值大于100萬(wàn)。上面的例子的代碼行數(shù)可以 被Java 1.5的語(yǔ)法大大減少,但是,哪怕是Java 1.5,你仍舊需要在兩層上作兩次比較。
如果你想對(duì)內(nèi)存中的一組Person對(duì)象也做一些這樣的查詢呢?如果你的應(yīng)用想顯示所有在英格蘭的名叫Tony的人呢?喔,如果你打印出每一個(gè)薪水少于2萬(wàn)的工作的名稱呢?
如 果你將這些對(duì)象存儲(chǔ)到關(guān)系數(shù)據(jù)庫(kù)中,你可以用一個(gè)SQL查詢來(lái)解決問(wèn)題,但你正在處理的是內(nèi)存中的對(duì)象,你可以不必那么奢侈。雖然XPath主要是用在 XML上面,但你可以用它來(lái)寫(xiě)一個(gè)針對(duì)對(duì)象集合的“查詢”,將對(duì)象作為元素和,把bean屬性作為子元素。是的,這是一種對(duì)XPath奇怪的應(yīng)用,但請(qǐng)先 看一下下面的例子如何在people上,一個(gè)由Person對(duì)象構(gòu)成的ArrayList,實(shí)現(xiàn)這三種查詢:
import org.apache.commons.jxpath.JXPathContext;
public List queryCollection(String xpath,
Collection col) {
List results = new ArrayList();
JXPathContext context =
JXPathContext.newContext( col );
Iterator matching =
context.iterate( xpath );
while( matching.hasNext() ) {
results.add( matching.getNext() );
}
return results;
}
String query1 =
".[@country = 'US']/job[@salary > 1000000]/..";
String query2 =
".[@country = 'GB' and @name = 'Tony']";
String query3 =
"./job/name";
List richUsPeople =
queryCollection( query1, people );
List britishTony =
queryCollection( query2, people );
List jobNames =
queryCollection( query3, people );
queryCollection()方法使用了一個(gè)XPath表達(dá)式,將它應(yīng)用到一個(gè)集合上。
XPath表達(dá)式被JXPathContext求值,
JXPathContext由JXPathContext.newContext()調(diào)用創(chuàng)建,并將它傳入要執(zhí)行查詢的集合中。凋用
context.iterate()來(lái)在集合中的每一個(gè)元素上應(yīng)用XPath表達(dá)式,返回包含所有符合條件的“節(jié)點(diǎn)”(這里是“對(duì)象”)的
Iterator。上例中執(zhí)行的第一個(gè)查詢,query1,執(zhí)行了和不使用JXPath的例子相同的查詢。query2選擇所有國(guó)家為GB并且名字屬性為
Tony的Person對(duì)象,query3返回了一個(gè)String對(duì)象的List,包含了所有Job對(duì)象的name屬性。
當(dāng)我第一次看到
Commons JXPath,
它是一個(gè)壞思想的想法觸動(dòng)了我。為什么要把XPath表達(dá)式應(yīng)用到對(duì)象上?有點(diǎn)感覺(jué)不對(duì)。把XPath作為一個(gè)bean的集合的查詢語(yǔ)言的這種意想不到的
用法,在過(guò)去幾年中已經(jīng)好多次給我?guī)?lái)了便利。如果你發(fā)現(xiàn)你在list中循環(huán)來(lái)查找符合條件的元素,請(qǐng)考慮一下JXPath。更多的信息,請(qǐng)參考
Jakarta Commons Cookbook的第12章,“查找和過(guò)濾”,它討論了Commons JXPath和與Commons
Digester配對(duì)的Jakarta Lucene。
還有更多
對(duì)Jakarta
Commons縱深地探索仍然在調(diào)試中。在這一系列的下面幾部分中,我會(huì)介紹一些相關(guān)的工具和功能。在Commons
Collections中設(shè)置操作,在collection中使用Predicate對(duì)象,使用Commons
Configuration來(lái)配置一個(gè)應(yīng)用和使用Commons Betwixt來(lái)讀寫(xiě)XML。能從Jakarta
Commons得到的東西還有很多,不能在幾千字中表達(dá),所以我建議你看一下Jakarta Commons
Cookbook。許多功能可能會(huì),一眼看上去,有點(diǎn)普通,但Jakarta Commons的能量就蘊(yùn)藏在這些工具的相互組合和與你的系統(tǒng)的集成當(dāng)中。
Timothy M. O'Brien是一個(gè)專業(yè)的獨(dú)立的開(kāi)發(fā)者,在Chicago地區(qū)工作和生活。
資源
·onjava.com:onjava.com
·Matrix-Java開(kāi)發(fā)者社區(qū):http://www.matrix.org.cn/
·APACHE:APACHE.org
posted on 2006-02-26 23:01 Vincent.Chen 閱讀(166) 評(píng)論(0) 編輯 收藏 所屬分類: Java