浅谈Java内部类的四个应用场景

Java内部类是Java言语的一个很重要的概念,《Java编程思想》花了很大的篇幅来说述这个概念。可是咱们在实践中不多用到它,虽然咱们在不少时候会被动的使用到它,但它仍然像一个幕后英雄同样,不为咱们所知,不为咱们所用。
本文不试图来说述Java内部类的此生前世、前因后果,这些在网络上都已经汗牛充栋。若是读者想了解这些,能够在网络上搜索来学习。Java内部类老是躲在它的外部类里,像一个幕后英雄同样。可是幕后英雄也有用武之地,在不少时候,恰当的使用Java内部类能起到让人拍案叫绝的做用。本文试图谈一谈让这个幕后英雄也有用武之地的四个场景,但愿引发你们对使用Java内部类的兴趣。
如下的文字,要求你们熟悉Java内部类的概念后来阅读。


场景一:当某个类除了它的外部类,再也不被其余的类使用时
咱们说这个内部类依附于它的外部类而存在,可能的缘由有:一、不可能为其余的类使用;二、出于某种缘由,不能被其余类引用,可能会引发错误。等等。这个场景是咱们使用内部类比较多的一个场景。下面咱们以一个你们熟悉的例子来讲明。
在咱们的企业级Java项目开发过程当中,数据库链接池是一个咱们常常要用到的概念。虽然在不少时候,咱们都是用的第三方的数据库链接池,不须要咱们亲自来作这个数据库链接池。可是,做为咱们Java内部类使用的第一个场景,这个数据库链接池是一个很好的例子。为了简单起见,如下咱们就来简单的模拟一下数据库链接池,在咱们的例子中,咱们只实现数据库链接池的一些简单的功能。若是想彻底实现它,你们不妨本身试一试。
首先,咱们定义一个接口,将数据库链接池的功能先定义出来,以下:java

Java代码 复制代码 收藏代码算法

  1. public interface Pool extends TimerListener  
  2. {  
  3.         //初始化链接池  
  4.         public boolean init();  
  5.         //销毁链接池  
  6.         public void destory();  
  7.         //取得一个链接  
  8.         public Connection getConn();  
  9.         //还有一些其余的功能,这里再也不列出  
  10.         ……  
  11. }  
public interface Pool extends TimerListener
{
        //初始化链接池
        public boolean init();
        //销毁链接池
        public void destory();
        //取得一个链接
        public Connection getConn();
        //还有一些其余的功能,这里再也不列出
        ……
}


有了这个功能接口,咱们就能够在它的基础上实现数据库链接池的部分功能了。咱们首先想到这个数据库链接池类的操做对象应该是由Connection对象组成的一个数组,既然是数组,咱们的池在取得Connection的时候,就要对数组元素进行遍历,看看Connection对象是否已经被使用,因此数组里每个Connection对象都要有一个使用标志。咱们再对链接池的功能进行分析,会发现每个Connection对象还要一个上次访问时间和使用次数。
经过上面的分析,咱们能够得出,链接池里的数组的元素应该是由对象组成,该对象的类可能以下:数据库

Java代码 复制代码 收藏代码编程

  1. public class PoolConn  
  2. {  
  3.         private Connection conn;  
  4.         private boolean isUse;  
  5.         private long lastAccess;  
  6.         private int useCount;  
  7.         ……  
  8. }  
public class PoolConn
{
        private Connection conn;
        private boolean isUse;
        private long lastAccess;
        private int useCount;
        ……
}


下面的省略号省掉的是关于四个属性的一些get和set方法。咱们能够看到这个类的核心就是Connection,其余的一些属性都是Connection的一些标志。能够说这个类只有在链接池这个类里有用,其余地方用不到。这时候,咱们就该考虑是否是能够把这个类做为一个内部类呢?并且咱们把它做为一个内部类之后,能够把它定义成一个私有类,而后将它的属性公开,这样省掉了那些无谓的get和set方法。下面咱们就试试看:数组

Java代码 复制代码 收藏代码网络

  1. public class ConnectPool implements Pool  
  2. {  
  3.         //存在Connection的数组  
  4.         private PoolConn[] poolConns;  
  5.         //链接池的最小链接数  
  6.         private int min;  
  7.         //链接池的最大链接数  
  8.         private int max;  
  9.         //一个链接的最大使用次数  
  10.         private int maxUseCount;  
  11.         //一个链接的最大空闲时间  
  12.         private long maxTimeout;  
  13.         //同一时间的Connection最大使用个数  
  14.         private int maxConns;  
  15.         //定时器  
  16.         private Timer timer;  
  17.         public boolean init()  
  18.         {  
  19.                try  
  20.                {  
  21.                       ……  
  22.                       this.poolConns = new PoolConn[this.min];  
  23.                       for(int i=0;i<this.min;i++)  
  24.                       {  
  25.                              PoolConn poolConn = new PoolConn();  
  26.                              poolConn.conn = ConnectionManager.getConnection();  
  27.                              poolConn.isUse = false;  
  28.                              poolConn.lastAccess = new Date().getTime();  
  29.                              poolConn.useCount = 0;  
  30.                              this.poolConns[i] = poolConn;  
  31. }  
  32. ……  
  33. return true;  
  34.                }  
  35.                catch(Exception e)  
  36.                {  
  37.                       return false;  
  38. }  
  39. }  
  40. ……  
  41. private class PoolConn  
  42. {  
  43.        public Connection conn;  
  44.        public boolean isUse;  
  45. public long lastAccess;  
  46.        public int useCount;  
  47. }  
  48. }  
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

Java代码 复制代码 收藏代码框架

  1. try  
  2.          {  
  3.                 String[] divisionData = null;  
  4.                 conn = manager.getInstance().getConnection();  
  5.                 stmt = (OracleCallableStatement)conn.prepareCall("{ Call PM_GET_PRODUCT.HEADER_DIVISION(?, ?) }");  
  6.                 stmt.setLong(1 ,productId.longValue() );  
  7.                 stmt.registerOutParameter(2, oracle.jdbc.OracleTypes.CURSOR); ;  
  8.                 stmt.execute();  
  9.                 ResultSet rs = stmt.getCursor(2);  
  10.                 int i = 0 ;  
  11.                 String strDivision = "";  
  12.                 while( rs.next() )  
  13.                 {  
  14.                              strDivision += rs.getString("DIVISION_ID") + "," ;  
  15.                   }  
  16.                   int length = strDivision.length() ;  
  17.                   if(length != 0 )  
  18.                   {  
  19.                          strDivision = strDivision.substring(0,length - 1);  
  20.                   }  
  21.                   divisionData = StringUtil.split(strDivision, ",") ;  
  22.                   map.put("Division", strDivision ) ;  
  23.                   LoggerAgent.debug("GetHeaderProcess","getDivisionData","getValue + " + strDivision +" " + productId) ;  
  24.        }catch(Exception e)  
  25.         {  
  26.                        LoggerAgent.error("GetHeaderData", "getDivisionData",  
  27.                                                      "SQLException: " + e);  
  28.                        e.printStackTrace() ;  
  29.    
  30.        }finally  
  31.         {  
  32.                        manager.close(stmt);  
  33.                        manager.releaseConnection(conn);  
  34.         }  
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…里面的代码是可变的,这意味着这些代码是可扩展的,是应该由用户来实现的,对于这样的可扩展内容,咱们很容易想到用接口来定义它们,而后由用户去实现。这样以来咱们首先定义一个接口:学习

Java代码 复制代码 收藏代码测试

  1. public interface DataManager  
  2. {  
  3.         public void manageData();  
  4. }  
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,看看还会在什么地方使用到内部类呢?



场景之四:适当使用内部类,使得代码更加灵活和富有扩展性
适当的使用内部类,可使得你的代码更加灵活和富有扩展性。固然,在这里头起做用的仍是一些模式的运行,但若是不配之内部类的使用,这些方法的使用效果就差远了。不信?请看下面的例子:
咱们记得简单工厂模式的做用就是将客户对各个对象的依赖转移到了工厂类里。很显然,简单工厂模式并无消除那些依赖,只是简单的将它们转移到了工厂类里。若是有新的对象增长进来,则咱们须要修改工厂类。因此咱们须要对工厂类作进一步的改造,进一步消除它对具体类的依赖。之前咱们提供过一个使用反射来消除依赖的方法;这里,咱们将提供另一种方法。
这种方法是将工厂进一步抽象,而将具体的工厂类交由具体类的建立者来实现,这样,工厂类和具体类的依赖的问题就获得了解决。而工厂的使用者则调用抽象的工厂来得到具体类的对象。以下。

Java代码 复制代码 收藏代码

  1. 咱们以一个生产形体的工厂为例,下面是这些形体的接口:  
  2. package polyFactory;  
  3.    
  4. public interface Shape {  
  5. public void draw();  
  6. public void erase();  
  7.    
  8. }  
  9.    
  10. 经过上面的描述,你们均可能已经猜到,这个抽象的工厂确定使用的是模板方法模式。以下:  
  11. package polyFactory;  
  12.    
  13. import java.util.HashMap;  
  14. import java.util.Map;  
  15.    
  16.    
  17. public abstract class ShapeFactory {  
  18. protected abstract Shape create();  
  19. private static Map factories = new HashMap();  
  20. public static void addFactory(String id,ShapeFactory f)  
  21. {  
  22.        factories.put(id,f);  
  23. }  
  24. public static final Shape createShape(String id)  
  25. {  
  26.        if(!factories.containsKey(id))  
  27.         {  
  28.                try  
  29.                {  
  30.                       Class.forName("polyFactory."+id);  
  31.                }  
  32.                catch(ClassNotFoundException e)  
  33.                {  
  34.                       throw new RuntimeException("Bad shape creation : "+id);  
  35.                }  
  36.         }  
  37.         return ((ShapeFactory)factories.get(id)).create();  
  38. }  
  39. }  
咱们以一个生产形体的工厂为例,下面是这些形体的接口:
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号为类名的类的一些静态的东西。
下面,咱们来看具体的类的实现:

Java代码 复制代码 收藏代码

  1. package polyFactory;  
  2.    
  3. public class Circle implements Shape {  
  4.    
  5.    
  6. public void draw() {  
  7.         // TODO Auto-generated method stub  
  8.        System.out.println("the circle is drawing...");  
  9. }  
  10.    
  11.    
  12. public void erase() {  
  13.         // TODO Auto-generated method stub  
  14.        System.out.println("the circle is erasing...");  
  15. }  
  16. private static class Factory extends ShapeFactory  
  17. {  
  18.        protected Shape create()  
  19.         {  
  20.                return new Circle();  
  21.         }  
  22. }  
  23. static {ShapeFactory.addFactory("Circle",new Factory());}  
  24.    
  25. }  
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就会执行这个静态的语句块了。
下面仍然是一个具体类:

Java代码 复制代码 收藏代码

  1. package polyFactory;  
  2.    
  3. public class Square implements Shape {  
  4.    
  5. public void draw() {  
  6.         // TODO Auto-generated method stub  
  7.        System.out.println("the square is drawing...");  
  8. }  
  9.    
  10. public void erase() {  
  11.         // TODO Auto-generated method stub  
  12.        System.out.println("the square is erasing...");  
  13. }  
  14. private static class Factory extends ShapeFactory  
  15. {  
  16.        protected Shape create()  
  17.         {  
  18.                return new Square();  
  19.         }  
  20. }  
  21. static {ShapeFactory.addFactory("Square",new Factory());}  
  22.    
  23. }  
  24. 最后,咱们来测试一下:  
  25. String[] ids = new String[]{"Circle","Square","Square","Circle"};  
  26.         for(int i=0;i<ids.length;i++)  
  27.         {  
  28.                Shape shape = ShapeFactory.createShape(ids[i]);  
  29.                shape.draw();  
  30.                shape.erase();  
  31.         }  
  32. 测试结果为:  
  33. the circle is drawing...  
  34. the circle is erasing...  
  35. the square is drawing...  
  36. the square is erasing...  
  37. the square is drawing...  
  38. the square is erasing...  
  39. the circle is drawing...  
  40. the circle is erasing...  
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...

       这个方法是巧妙地使用了内部类,将具体类的实现和它的具体工厂类绑定起来,由具体类的实现者在这个内部类的具体工厂里去产生一个具体类的对象,这固然容易得多。虽然须要每个具体类都建立一个具体工厂类,但因为具体工厂类是一个内部类,这样也不会随着具体类的增长而不断增长新的工厂类,使得代码看起来很臃肿,这也是本方法不得不使用内部类的一个缘由吧。

相关文章
相关标签/搜索