Java內部類是Java言語的一個很重要的概念,《Java編程思想》花了很大的篇幅來說述這個概念。可是咱們在實踐中不多用到它,雖然咱們在不少時候會被動的使用到它,但它仍然像一個幕後英雄同樣,不爲咱們所知,不爲咱們所用。
本文不試圖來說述Java內部類的此生前世、前因後果,這些在網絡上都已經汗牛充棟。若是讀者想了解這些,能夠在網絡上搜索來學習。Java內部類老是躲在它的外部類裏,像一個幕後英雄同樣。可是幕後英雄也有用武之地,在不少時候,恰當的使用Java內部類能起到讓人拍案叫絕的做用。本文試圖談一談讓這個幕後英雄也有用武之地的四個場景,但願引發你們對使用Java內部類的興趣。
如下的文字,要求你們熟悉Java內部類的概念後來閱讀。
場景一:當某個類除了它的外部類,再也不被其餘的類使用時
咱們說這個內部類依附於它的外部類而存在,可能的緣由有:一、不可能爲其餘的類使用;二、出於某種緣由,不能被其餘類引用,可能會引發錯誤。等等。這個場景是咱們使用內部類比較多的一個場景。下面咱們以一個你們熟悉的例子來講明。
在咱們的企業級Java項目開發過程當中,數據庫鏈接池是一個咱們常常要用到的概念。雖然在不少時候,咱們都是用的第三方的數據庫鏈接池,不須要咱們親自來作這個數據庫鏈接池。可是,做爲咱們Java內部類使用的第一個場景,這個數據庫鏈接池是一個很好的例子。爲了簡單起見,如下咱們就來簡單的模擬一下數據庫鏈接池,在咱們的例子中,咱們只實現數據庫鏈接池的一些簡單的功能。若是想徹底實現它,你們不妨本身試一試。
首先,咱們定義一個接口,將數據庫鏈接池的功能先定義出來,以下:java
public interface Pool extends TimerListener { //初始化鏈接池 public boolean init(); //銷燬鏈接池 public void destory(); //取得一個鏈接 public Connection getConn(); //還有一些其餘的功能,這裏再也不列出 …… }
有了這個功能接口,咱們就能夠在它的基礎上實現數據庫鏈接池的部分功能了。咱們首先想到這個數據庫鏈接池類的操做對象應該是由Connection對象組成的一個數組,既然是數組,咱們的池在取得Connection的時候,就要對數組元素進行遍歷,看看Connection對象是否已經被使用,因此數組裏每個Connection對象都要有一個使用標誌。咱們再對鏈接池的功能進行分析,會發現每個Connection對象還要一個上次訪問時間和使用次數。
經過上面的分析,咱們能夠得出,鏈接池裏的數組的元素應該是由對象組成,該對象的類可能以下:數據庫
public class PoolConn { private Connection conn; private boolean isUse; private long lastAccess; private int useCount; …… }
下面的省略號省掉的是關於四個屬性的一些get和set方法。咱們能夠看到這個類的核心就是Connection,其餘的一些屬性都是Connection的一些標誌。能夠說這個類只有在鏈接池這個類裏有用,其餘地方用不到。這時候,咱們就該考慮是否是能夠把這個類做爲一個內部類呢?並且咱們把它做爲一個內部類之後,能夠把它定義成一個私有類,而後將它的屬性公開,這樣省掉了那些無謂的get和set方法。下面咱們就試試看:數組
public class ConnectPool implements Pool { //存在Connection的數組 private PoolConn[] poolConns; //鏈接池的最小鏈接數 private int min; //鏈接池的最大鏈接數 private int max; //一個鏈接的最大使用次數 private int maxUseCount; //一個鏈接的最大空閒時間 private long maxTimeout; //同一時間的Connection最大使用個數 private int maxConns; //定時器 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; } }
由於本文不是專題來說述數據庫鏈接池的,因此在上面的代碼中絕大部分的內容被省略掉了。PoolConn類不大可能被除了ConnectionPool類的其餘類使用到,把它做爲ConnectionPool的私有內部類不會影響到其餘類。同時,咱們能夠看到,使用了內部類,使得咱們能夠將該內部類的數據公開,ConnectionPool類能夠直接操做PoolConn類的數據成員,避免了因set和get方法帶來的麻煩。
上面的一個例子,是使用內部類使得你的代碼獲得簡化和方便。還有些狀況下,你可能要避免你的類被除了它的外部類之外的類使用到,這時候你卻不得不使用內部類來解決問題。
場景二:解決一些非面向對象的語句塊
這些語句塊包括if…else if…else語句,case語句,等等。這些語句都不是面向對象的,給咱們形成了系統的擴展上的麻煩。咱們能夠看看,在模式中,有多少模式是用來解決由if語句帶來的擴展性的問題。
Java編程中還有一個困擾咱們的問題,那就是try…catch…問題,特別是在JDBC編程過程當中。請看下面的代碼:oracle
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); }
這是咱們最最經常使用的一個JDBC編程的代碼示例。一個系統有不少這樣的查詢方法,這段代碼通常分做三段:try關鍵字括起來的那段是用來作查詢操做的,catch關鍵字括起來的那段須要作兩件事,記錄出錯的緣由和事務回滾(若是須要的話),finally關鍵字括起來的那段用來釋放數據庫鏈接。
咱們的煩惱是:try關鍵字括起來的那段是變化的,每一個方法的通常都不同。而 catch和finally關鍵字括起來的那兩段卻通常都是不變的,每一個方法的那兩段都是同樣的。既而後面那兩段是同樣的,咱們就很是但願將它們提取出來,作一個單獨的方法,而後讓每個使用到它們的方法調用。可是,try…catch…finally…是一個完整的語句段,不能把它們分開。這樣的結果,使得咱們不得不在每個數據層方法裏重複的寫相同的catch…finally…這兩段語句。
既然不能將那些討厭的try…catch…finally…做爲一個公用方法提出去,那麼咱們仍是須要想其餘的辦法來解決這個問題。否則咱們總是寫那麼重複代碼,真是既繁瑣,又不容易維護。
咱們容易想到,既然catch…finally…這兩段代碼不能提出來,那麼咱們能不能將try…裏面的代碼提出去呢?唉喲,try…裏面的代碼是可變的呢。怎麼辦?
既然try…裏面的代碼是可變的,這意味着這些代碼是可擴展的,是應該由用戶來實現的,對於這樣的可擴展內容,咱們很容易想到用接口來定義它們,而後由用戶去實現。這樣以來咱們首先定義一個接口:學習
public interface DataManager { public void manageData(); }
咱們須要用戶在manageData()方法中實現他們對數據層訪問的代碼,也就是try…裏面的代碼。
而後咱們使用一個模板類來實現全部的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);
}
}
}
這樣,一個模板類就完成了。咱們也經過這個模板類將catch…finally…兩段代碼提出來了。咱們來看看使用了這個模板類的數據層方法是怎麼實現的:
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) ;
}
});
注意:本段代碼僅供思路上的參考,沒有通過上機測試。
咱們能夠看到,正是這個實現了DataManager接口得匿名內部類的使用,才使得咱們解決了對try…catch…finally…語句的改造。這樣,第一爲咱們解決了使人痛苦的重複代碼;第二也讓咱們在數據層方法的編碼中,直接關注對數據的操做,不用關心那些必需的可是與數據操做無關的東西。
咱們如今來回想一下Spring框架的數據層,是否是正是使用了這種方法呢?
場景之三:一些多算法場合
假如咱們有這樣一個需求:咱們的一個方法用來對數組排序而且依次打印各元素,對數組排序方法有不少種,用哪一種方法排序交給用戶本身肯定。
對於這樣一個需求,咱們很容易解決。咱們決定給哪些排序算法定義一個接口,具體的算法實現由用戶本身完成,只要求他實現咱們的接口就行。
public interface SortAlgor
{
public void sort(int[] is);
}
這樣,咱們再在方法裏實現先排序後打印,代碼以下:
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();
}
客戶端對上面方法的使用以下:
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;
}
}
}
}
});
這樣的用法不少,咱們都或多或少的被動的使用過。如在Swing編程中,咱們常常須要對組件增長監聽器對象,以下所示:
spinner2.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
System.out.println("Source: " + e.getSource());
}
}
);
在Arrays包裏,對元素爲對象的數組的排序:
Arrays.sort(emps,new Comparator(){
Public int compare(Object o1,Object o2)
{
return ((Employee)o1).getServedYears()-((Employee)o2).getServedYears();
}
});
這樣的例子還有不少,JDK教會了咱們不少使用內部類的方法。隨時咱們均可以看一看API,看看還會在什麼地方使用到內部類呢?
場景之四:適當使用內部類,使得代碼更加靈活和富有擴展性
適當的使用內部類,可使得你的代碼更加靈活和富有擴展性。固然,在這裏頭起做用的仍是一些模式的運行,但若是不配之內部類的使用,這些方法的使用效果就差遠了。不信?請看下面的例子:
咱們記得簡單工廠模式的做用就是將客戶對各個對象的依賴轉移到了工廠類裏。很顯然,簡單工廠模式並無消除那些依賴,只是簡單的將它們轉移到了工廠類裏。若是有新的對象增長進來,則咱們須要修改工廠類。因此咱們須要對工廠類作進一步的改造,進一步消除它對具體類的依賴。之前咱們提供過一個使用反射來消除依賴的方法;這裏,咱們將提供另一種方法。
這種方法是將工廠進一步抽象,而將具體的工廠類交由具體類的建立者來實現,這樣,工廠類和具體類的依賴的問題就獲得瞭解決。而工廠的使用者則調用抽象的工廠來得到具體類的對象。以下。
咱們以一個生產形體的工廠爲例,下面是這些形體的接口: package polyFactory; public interface Shape { public void draw(); public void erase(); } 經過上面的描述,你們均可能已經猜到,這個抽象的工廠確定使用的是模板方法模式。以下: 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(); } }
不錯,正是模板方法模式的運用。這個類蠻簡單的:首先是一個create()方法,用來產生具體類的對象,留交各具體工廠實現去實現。而後是一個Map類型的靜態變量,用來存放具體工廠的實現以及他們的ID號。接着的一個方法使用來增長一個具體工廠的實現。最後一個靜態方法是用來獲取具體對象,裏面的那個Class.forName……的做用是調用以ID號爲類名的類的一些靜態的東西。
下面,咱們來看具體的類的實現:
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());} }
這個類的其餘的地方也日常得很。但就是後面的那個內部類Factory用得好。第一呢,這個類只作一件事,就是產生一個Circle對象,與其餘類無關,就這一個條也就知足了使用內部類的條件。第二呢,這個Factory類須要是靜態的,這也得要求它被使用內部類,否則,下面的ShapeFacotry.addFactory就沒辦法add了。而最後的那個靜態的語句塊是用來將具體的工廠類添加到抽象的工廠裏面去。在抽象工廠裏調用Class.forName就會執行這個靜態的語句塊了。
下面仍然是一個具體類:
package polyFactory; public class Square implements Shape { public void draw() { // TODO Auto-generated method stub System.out.println("the square is drawing..."); } public void erase() { // TODO Auto-generated method stub System.out.println("the square is erasing..."); } private static class Factory extends ShapeFactory { protected Shape create() { return new Square(); } } static {ShapeFactory.addFactory("Square",new Factory());} } 最後,咱們來測試一下: String[] ids = new String[]{"Circle","Square","Square","Circle"}; for(int i=0;i<ids.length;i++) { Shape shape = ShapeFactory.createShape(ids[i]); shape.draw(); shape.erase(); } 測試結果爲: the circle is drawing... the circle is erasing... the square is drawing... the square is erasing... the square is drawing... the square is erasing... the circle is drawing... the circle is erasing...
這個方法是巧妙地使用了內部類,將具體類的實現和它的具體工廠類綁定起來,由具體類的實現者在這個內部類的具體工廠裏去產生一個具體類的對象,這固然容易得多。雖然須要每個具體類都建立一個具體工廠類,但因爲具體工廠類是一個內部類,這樣也不會隨着具體類的增長而不斷增長新的工廠類,使得代碼看起來很臃腫,這也是本方法不得不使用內部類的一個緣由吧。