淺談Java內(nèi)部類的四個(gè)應(yīng)用場(chǎng)景文章分類:Java編程Java內(nèi)部類是Java言語的一個(gè)很重要的概念,,《Java編程思想》花了很大的篇幅來講述這個(gè)概念。但是我們?cè)趯?shí)踐中很少用到它,,雖然我們?cè)诤芏鄷r(shí)候會(huì)被動(dòng)的使用到它,,但它仍然像一個(gè)幕后英雄一樣,不為我們所知,,不為我們所用,。
本文不試圖來講述Java內(nèi)部類的今生前世、來龍去脈,,這些在網(wǎng)絡(luò)上都已經(jīng)汗牛充棟,。如果讀者想了解這些,可以在網(wǎng)絡(luò)上搜索來學(xué)習(xí),。Java內(nèi)部類總是躲在它的外部類里,,像一個(gè)幕后英雄一樣。但是幕后英雄也有用武之地,,在很多時(shí)候,,恰當(dāng)?shù)氖褂肑ava內(nèi)部類能起到讓人拍案叫絕的作用。本文試圖談一談讓這個(gè)幕后英雄也有用武之地的四個(gè)場(chǎng)景,,希望引起大家對(duì)使用Java內(nèi)部類的興趣,。 以下的文字,要求大家熟悉Java內(nèi)部類的概念后來閱讀,。 場(chǎng)景一:當(dāng)某個(gè)類除了它的外部類,,不再被其他的類使用時(shí) 我們說這個(gè)內(nèi)部類依附于它的外部類而存在,,可能的原因有:1,、不可能為其他的類使用,;2、出于某種原因,,不能被其他類引用,,可能會(huì)引起錯(cuò)誤。等等,。這個(gè)場(chǎng)景是我們使用內(nèi)部類比較多的一個(gè)場(chǎng)景,。下面我們以一個(gè)大家熟悉的例子來說明。 在我們的企業(yè)級(jí)Java項(xiàng)目開發(fā)過程中,,數(shù)據(jù)庫(kù)連接池是一個(gè)我們經(jīng)常要用到的概念,。雖然在很多時(shí)候,我們都是用的第三方的數(shù)據(jù)庫(kù)連接池,,不需要我們親自來做這個(gè)數(shù)據(jù)庫(kù)連接池,。但是,作為我們Java內(nèi)部類使用的第一個(gè)場(chǎng)景,,這個(gè)數(shù)據(jù)庫(kù)連接池是一個(gè)很好的例子,。為了簡(jiǎn)單起見,以下我們就來簡(jiǎn)單的模擬一下數(shù)據(jù)庫(kù)連接池,,在我們的例子中,,我們只實(shí)現(xiàn)數(shù)據(jù)庫(kù)連接池的一些簡(jiǎn)單的功能。如果想完全實(shí)現(xiàn)它,,大家不妨自己試一試,。 首先,我們定義一個(gè)接口,,將數(shù)據(jù)庫(kù)連接池的功能先定義出來,,如下:
public interface Pool extends TimerListener { //初始化連接池 public boolean init(); //銷毀連接池 public void destory(); //取得一個(gè)連接 public Connection getConn(); //還有一些其他的功能,,這里不再列出 …… } 有了這個(gè)功能接口,,我們就可以在它的基礎(chǔ)上實(shí)現(xiàn)數(shù)據(jù)庫(kù)連接池的部分功能了。我們首先想到這個(gè)數(shù)據(jù)庫(kù)連接池類的操作對(duì)象應(yīng)該是由Connection對(duì)象組成的一個(gè)數(shù)組,,既然是數(shù)組,,我們的池在取得Connection的時(shí)候,就要對(duì)數(shù)組元素進(jìn)行遍歷,,看看Connection對(duì)象是否已經(jīng)被使用,,所以數(shù)組里每一個(gè)Connection對(duì)象都要有一個(gè)使用標(biāo)志。我們?cè)賹?duì)連接池的功能進(jìn)行分析,,會(huì)發(fā)現(xiàn)每一個(gè)Connection對(duì)象還要一個(gè)上次訪問時(shí)間和使用次數(shù),。 通過上面的分析,我們可以得出,,連接池里的數(shù)組的元素應(yīng)該是由對(duì)象組成,,該對(duì)象的類可能如下:
public class PoolConn { private Connection conn; private boolean isUse; private long lastAccess; private int useCount; …… } 下面的省略號(hào)省掉的是關(guān)于四個(gè)屬性的一些get和set方法,。我們可以看到這個(gè)類的核心就是Connection,其他的一些屬性都是Connection的一些標(biāo)志,??梢哉f這個(gè)類只有在連接池這個(gè)類里有用,其他地方用不到,。這時(shí)候,,我們就該考慮是不是可以把這個(gè)類作為一個(gè)內(nèi)部類呢?而且我們把它作為一個(gè)內(nèi)部類以后,,可以把它定義成一個(gè)私有類,,然后將它的屬性公開,這樣省掉了那些無謂的get和set方法,。下面我們就試試看:
public class ConnectPool implements Pool { //存在Connection的數(shù)組 private PoolConn[] poolConns; //連接池的最小連接數(shù) private int min; //連接池的最大連接數(shù) private int max; //一個(gè)連接的最大使用次數(shù) private int maxUseCount; //一個(gè)連接的最大空閑時(shí)間 private long maxTimeout; //同一時(shí)間的Connection最大使用個(gè)數(shù) private int maxConns; //定時(shí)器 private Timer timer; public boolean init() { try { …… this.poolConns = new PoolConn[this.min]; for(int i=0;i<this.min;i++) { PoolConn poolConn = new PoolConn(); poolConn.conn = ConnectionManager.getConnection(); poolConn.isUse = false; poolConn.lastAccess = new Date().getTime(); poolConn.useCount = 0; this.poolConns[i] = poolConn; } …… return true; } catch(Exception e) { return false; } } …… private class PoolConn { public Connection conn; public boolean isUse; public long lastAccess; public int useCount; } } 因?yàn)楸疚牟皇菍n}來講述數(shù)據(jù)庫(kù)連接池的,,所以在上面的代碼中絕大部分的內(nèi)容被省略掉了。PoolConn類不大可能被除了ConnectionPool類的其他類使用到,,把它作為ConnectionPool的私有內(nèi)部類不會(huì)影響到其他類,。同時(shí),我們可以看到,,使用了內(nèi)部類,,使得我們可以將該內(nèi)部類的數(shù)據(jù)公開,ConnectionPool類可以直接操作PoolConn類的數(shù)據(jù)成員,,避免了因set和get方法帶來的麻煩,。 上面的一個(gè)例子,是使用內(nèi)部類使得你的代碼得到簡(jiǎn)化和方便,。還有些情況下,,你可能要避免你的類被除了它的外部類以外的類使用到,這時(shí)候你卻不得不使用內(nèi)部類來解決問題,。 場(chǎng)景二:解決一些非面向?qū)ο蟮恼Z句塊 這些語句塊包括if…else if…else語句,,case語句,等等,。這些語句都不是面向?qū)ο蟮?,給我們?cè)斐闪讼到y(tǒng)的擴(kuò)展上的麻煩。我們可以看看,,在模式中,,有多少模式是用來解決由if語句帶來的擴(kuò)展性的問題。 Java編程中還有一個(gè)困擾我們的問題,,那就是try…catch…問題,,特別是在JDBC編程過程中。請(qǐng)看下面的代碼:
try { String[] divisionData = null; conn = manager.getInstance().getConnection(); stmt = (OracleCallableStatement)conn.prepareCall("{ Call PM_GET_PRODUCT.HEADER_DIVISION(?, ?) }"); stmt.setLong(1 ,productId.longValue() ); stmt.registerOutParameter(2, oracle.jdbc.OracleTypes.CURSOR); ; stmt.execute(); ResultSet rs = stmt.getCursor(2); int i = 0 ; String strDivision = ""; while( rs.next() ) { strDivision += rs.getString("DIVISION_ID") + "," ; } int length = strDivision.length() ; if(length != 0 ) { strDivision = strDivision.substring(0,length - 1); } divisionData = StringUtil.split(strDivision, ",") ; map.put("Division", strDivision ) ; LoggerAgent.debug("GetHeaderProcess","getDivisionData","getValue + " + strDivision +" " + productId) ; }catch(Exception e) { LoggerAgent.error("GetHeaderData", "getDivisionData", "SQLException: " + e); e.printStackTrace() ; }finally { manager.close(stmt); manager.releaseConnection(conn); } 這是我們最最常用的一個(gè)JDBC編程的代碼示例,。一個(gè)系統(tǒng)有很多這樣的查詢方法,,這段代碼一般分作三段:try關(guān)鍵字括起來的那段是用來做查詢操作的,,catch關(guān)鍵字括起來的那段需要做兩件事,記錄出錯(cuò)的原因和事務(wù)回滾(如果需要的話),,finally關(guān)鍵字括起來的那段用來釋放數(shù)據(jù)庫(kù)連接。 我們的煩惱是:try關(guān)鍵字括起來的那段是變化的,,每個(gè)方法的一般都不一樣,。而 catch和finally關(guān)鍵字括起來的那兩段卻一般都是不變的,每個(gè)方法的那兩段都是一樣的,。既然后面那兩段是一樣的,,我們就非常希望將它們提取出來,做一個(gè)單獨(dú)的方法,,然后讓每一個(gè)使用到它們的方法調(diào)用,。但是,try…catch…finally…是一個(gè)完整的語句段,,不能把它們分開,。這樣的結(jié)果,使得我們不得不在每一個(gè)數(shù)據(jù)層方法里重復(fù)的寫相同的catch…finally…這兩段語句,。 既然不能將那些討厭的try…catch…finally…作為一個(gè)公用方法提出去,,那么我們還是需要想其他的辦法來解決這個(gè)問題。不然我們老是寫那么重復(fù)代碼,,真是既繁瑣,,又不容易維護(hù)。 我們?nèi)菀紫氲?,既然catch…finally…這兩段代碼不能提出來,,那么我們能不能將try…里面的代碼提出去呢?唉喲,,try…里面的代碼是可變的呢,。怎么辦? 既然try…里面的代碼是可變的,,這意味著這些代碼是可擴(kuò)展的,,是應(yīng)該由用戶來實(shí)現(xiàn)的,對(duì)于這樣的可擴(kuò)展內(nèi)容,,我們很容易想到用接口來定義它們,,然后由用戶去實(shí)現(xiàn)。這樣以來我們首先定義一個(gè)接口:
public interface DataManager { public void manageData(); } 我們需要用戶在manageData()方法中實(shí)現(xiàn)他們對(duì)數(shù)據(jù)層訪問的代碼,,也就是try…里面的代碼,。 然后我們使用一個(gè)模板類來實(shí)現(xiàn)所有的try…catch…finally…語句的功能,如下: public class DataTemplate { public void execute(DataManager dm) { try { dm.manageData(); } catch(Exception e) { LoggerAgent.error("GetHeaderData", "getDivisionData", "SQLException: " + e); e.printStackTrace() ; }finally { manager.close(stmt); manager.releaseConnection(conn); } } } 這樣,,一個(gè)模板類就完成了,。我們也通過這個(gè)模板類將catch…finally…兩段代碼提出來了,。我們來看看使用了這個(gè)模板類的數(shù)據(jù)層方法是怎么實(shí)現(xiàn)的: new DataTemplate().execute(new DataManager() { public void manageData() { String[] divisionData = null; conn = manager.getInstance().getConnection(); stmt = (OracleCallableStatement)conn.prepareCall("{ Call PM_GET_PRODUCT.HEADER_DIVISION(?, ?) }"); stmt.setLong(1 ,productId.longValue() ); stmt.registerOutParameter(2, oracle.jdbc.OracleTypes.CURSOR); ; stmt.execute(); ResultSet rs = stmt.getCursor(2); int i = 0 ; String strDivision = ""; while( rs.next() ) { strDivision += rs.getString("DIVISION_ID") + "," ; } int length = strDivision.length() ; if(length != 0 ) { strDivision = strDivision.substring(0,length - 1); } divisionData = StringUtil.split(strDivision, ",") ; map.put("Division", strDivision ) ; LoggerAgent.debug("GetHeaderProcess","getDivisionData","getValue + " + strDivision +" " + productId) ; } }); 注意:本段代碼僅供思路上的參考,沒有經(jīng)過上機(jī)測(cè)試,。 我們可以看到,,正是這個(gè)實(shí)現(xiàn)了DataManager接口得匿名內(nèi)部類的使用,才使得我們解決了對(duì)try…catch…finally…語句的改造,。這樣,,第一為我們解決了令人痛苦的重復(fù)代碼;第二也讓我們?cè)跀?shù)據(jù)層方法的編碼中,,直接關(guān)注對(duì)數(shù)據(jù)的操作,,不用關(guān)心那些必需的但是與數(shù)據(jù)操作無關(guān)的東西。 我們現(xiàn)在來回想一下Spring框架的數(shù)據(jù)層,,是不是正是使用了這種方法呢,? 場(chǎng)景之三:一些多算法場(chǎng)合 假如我們有這樣一個(gè)需求:我們的一個(gè)方法用來對(duì)數(shù)組排序并且依次打印各元素,對(duì)數(shù)組排序方法有很多種,,用哪種方法排序交給用戶自己確定,。 對(duì)于這樣一個(gè)需求,我們很容易解決,。我們決定給哪些排序算法定義一個(gè)接口,,具體的算法實(shí)現(xiàn)由用戶自己完成,只要求他實(shí)現(xiàn)我們的接口就行,。 public interface SortAlgor { public void sort(int[] is); } 這樣,,我們?cè)僭诜椒ɡ飳?shí)現(xiàn)先排序后打印,代碼如下: public void printSortedArray(int[] is,SortAlgor sa) { …… sa.sort(is); for(int i=0;i<is.length;i++) { System.out.print(is[i]+” “); } System.out.println(); } 客戶端對(duì)上面方法的使用如下: int[] is = new int[]{3,1,4,9,2}; printSortedArray(is,new SortAlgor() { public void sort(is) { int k = 0; for(int i=0;i<is.length;i++) { for(int j=i+1;j<is.length;j++) { if(is[i]>is[j]) { k = is[i]; is[i] = is[j]; is[j] = k; } } } } }); 這樣的用法很多,,我們都或多或少的被動(dòng)的使用過,。如在Swing編程中,我們經(jīng)常需要對(duì)組件增加監(jiān)聽器對(duì)象,,如下所示: spinner2.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { System.out.println("Source: " + e.getSource()); } } ); 在Arrays包里,,對(duì)元素為對(duì)象的數(shù)組的排序: Arrays.sort(emps,new Comparator(){ Public int compare(Object o1,Object o2) { return ((Employee)o1).getServedYears()-((Employee)o2).getServedYears(); } }); 這樣的例子還有很多,,JDK教會(huì)了我們很多使用內(nèi)部類的方法,。隨時(shí)我們都可以看一看API,看看還會(huì)在什么地方使用到內(nèi)部類呢,? 場(chǎng)景之四:適當(dāng)使用內(nèi)部類,,使得代碼更加靈活和富有擴(kuò)展性 適當(dāng)?shù)氖褂脙?nèi)部類,可以使得你的代碼更加靈活和富有擴(kuò)展性,。當(dāng)然,,在這里頭起作用的還是一些模式的運(yùn)行,但如果不配以內(nèi)部類的使用,這些方法的使用效果就差遠(yuǎn)了,。不信,?請(qǐng)看下面的例子: 我們記得簡(jiǎn)單工廠模式的作用就是將客戶對(duì)各個(gè)對(duì)象的依賴轉(zhuǎn)移到了工廠類里。很顯然,,簡(jiǎn)單工廠模式并沒有消除那些依賴,,只是簡(jiǎn)單的將它們轉(zhuǎn)移到了工廠類里。如果有新的對(duì)象增加進(jìn)來,,則我們需要修改工廠類,。所以我們需要對(duì)工廠類做進(jìn)一步的改造,進(jìn)一步消除它對(duì)具體類的依賴,。以前我們提供過一個(gè)使用反射來消除依賴的方法,;這里,,我們將提供另外一種方法,。 這種方法是將工廠進(jìn)一步抽象,而將具體的工廠類交由具體類的創(chuàng)建者來實(shí)現(xiàn),,這樣,,工廠類和具體類的依賴的問題就得到了解決。而工廠的使用者則調(diào)用抽象的工廠來獲得具體類的對(duì)象,。如下,。
我們以一個(gè)生產(chǎn)形體的工廠為例,,下面是這些形體的接口: package polyFactory; public interface Shape { public void draw(); public void erase(); } 通過上面的描述,,大家都可能已經(jīng)猜到,這個(gè)抽象的工廠肯定使用的是模板方法模式,。如下: package polyFactory; import java.util.HashMap; import java.util.Map; public abstract class ShapeFactory { protected abstract Shape create(); private static Map factories = new HashMap(); public static void addFactory(String id,ShapeFactory f) { factories.put(id,f); } public static final Shape createShape(String id) { if(!factories.containsKey(id)) { try { Class.forName("polyFactory."+id); } catch(ClassNotFoundException e) { throw new RuntimeException("Bad shape creation : "+id); } } return ((ShapeFactory)factories.get(id)).create(); } } 不錯(cuò),,正是模板方法模式的運(yùn)用。這個(gè)類蠻簡(jiǎn)單的:首先是一個(gè)create()方法,,用來產(chǎn)生具體類的對(duì)象,,留交各具體工廠實(shí)現(xiàn)去實(shí)現(xiàn)。然后是一個(gè)Map類型的靜態(tài)變量,,用來存放具體工廠的實(shí)現(xiàn)以及他們的ID號(hào),。接著的一個(gè)方法使用來增加一個(gè)具體工廠的實(shí)現(xiàn)。最后一個(gè)靜態(tài)方法是用來獲取具體對(duì)象,,里面的那個(gè)Class.forName……的作用是調(diào)用以ID號(hào)為類名的類的一些靜態(tài)的東西,。 下面,我們來看具體的類的實(shí)現(xiàn):
package polyFactory; public class Circle implements Shape { public void draw() { // TODO Auto-generated method stub System.out.println("the circle is drawing..."); } public void erase() { // TODO Auto-generated method stub System.out.println("the circle is erasing..."); } private static class Factory extends ShapeFactory { protected Shape create() { return new Circle(); } } static {ShapeFactory.addFactory("Circle",new Factory());} } 這個(gè)類的其他的地方也平常得很。但就是后面的那個(gè)內(nèi)部類Factory用得好,。第一呢,,這個(gè)類只做一件事,就是產(chǎn)生一個(gè)Circle對(duì)象,,與其他類無關(guān),,就這一個(gè)條也就滿足了使用內(nèi)部類的條件。第二呢,,這個(gè)Factory類需要是靜態(tài)的,,這也得要求它被使用內(nèi)部類,不然,,下面的ShapeFacotry.addFactory就沒辦法add了,。而最后的那個(gè)靜態(tài)的語句塊是用來將具體的工廠類添加到抽象的工廠里面去。在抽象工廠里調(diào)用Class.forName就會(huì)執(zhí)行這個(gè)靜態(tài)的語句塊了,。 下面仍然是一個(gè)具體類:
|
|