內部類使用場景(轉)

轉自:https://blog.csdn.net/hivon/article/details/606312

Java內部類是Java言語的一個很重要的概念,《Java編程思想》花了很大的篇幅來講述這個概念。但是我們在實踐中很少用到它,雖然我們在很多時候會被動的使用到它,但它仍然像一個幕後英雄一樣,不爲我們所知,不爲我們所用。
本文不試圖來講述Java內部類的今生前世、來龍去脈,這些在網絡上都已經汗牛充棟。如果讀者想了解這些,可以在網絡上搜索來學習。Java內部類總是躲在它的外部類裏,像一個幕後英雄一樣。但是幕後英雄也有用武之地,在很多時候,恰當的使用Java內部類能起到讓人拍案叫絕的作用。本文試圖談一談讓這個幕後英雄也有用武之地的四個場景,希望引起大家對使用Java內部類的興趣。
以下的文字,要求大家熟悉Java內部類的概念後來閱讀。
 
 
場景一:當某個類除了它的外部類,不再被其他的類使用時
我們說這個內部類依附於它的外部類而存在,可能的原因有:1、不可能爲其他的類使用;2、出於某種原因,不能被其他類引用,可能會引起錯誤。等等。這個場景是我們使用內部類比較多的一個場景。下面我們以一個大家熟悉的例子來說明。
在我們的企業級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編程過程中。請看下面的代碼:
……
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...
       這個方法是巧妙地使用了內部類,將具體類的實現和它的具體工廠類綁定起來,由具體類的實現者在這個內部類的具體工廠裏去產生一個具體類的對象,這當然容易得多。雖然需要每一個具體類都創建一個具體工廠類,但由於具體工廠類是一個內部類,這樣也不會隨着具體類的增加而不斷增加新的工廠類,使得代碼看起來很臃腫,這也是本方法不得不使用內部類的一個原因吧。
--------------------- 
作者:hivon 
來源:CSDN 
原文:https://blog.csdn.net/hivon/article/details/606312 
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章