`

java匿名内部类的使用场景

    博客分类:
  • Java
阅读更多
幕后英雄的用武之地
                                                         ——浅谈Java内部类的四个应用场景

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...

        这个方法是巧妙地使用了内部类,将具体类的实现和它的具体工厂类绑定起来,由具体类的实现者在这个内部类的具体工厂里去产生一个具体类的对象,这当然容易得多。虽然需要每一个具体类都创建一个具体工厂类,但由于具体工厂类是一个内部类,这样也不会随着具体类的增加而不断增加新的工厂类,使得代码看起来很臃肿,这也是本方法不得不使用内部类的一个原因吧。
原帖来自于网易社区:http://club.163.com/viewArticleByWWW.m?boardId=java&articleId=java_10999f615630022ZZ

 

<script></script>
分享到:
评论

相关推荐

    java 匿名内部类的使用规范

    本篇文章将深入探讨Java匿名内部类的使用规范,帮助你更好地理解和应用这一特性。 首先,理解匿名内部类的基本概念。匿名内部类没有名字,它不能被其他类直接引用,但可以作为局部变量、成员变量或者方法参数。它...

    Java 匿名内部类

    Java匿名内部类是Java语言中一个独特且强大的特性,它允许我们在不定义单独类的情况下创建对象。这种类没有名字,通常用作一次性实现某个接口或继承某个类的需求。在Java编程中,匿名内部类主要用于事件处理、回调...

    java 内部类使用(内部匿名类)

    内部类分为几种类型,包括成员内部类、局部内部类、匿名内部类以及方法参数内部类。 1. **成员内部类**:成员内部类就像是外部类的一个普通成员,可以是静态或非静态的。非静态内部类拥有对外部类的引用,可以直接...

    Java4Android 35_内部类和匿名内部类

    在Java编程语言中,内部类和匿名内部类是两种特殊类型的类,它们为代码的组织和功能实现提供了独特的灵活性。本教程"Java4Android 35_内部类和匿名内部类"深入探讨了这两个概念,旨在帮助开发者更好地理解和运用它们...

    发两个匿名内部类使用的例子

    在标题“发两个匿名内部类使用的例子”中,我们将会探讨两种不同的场景,如何利用匿名内部类来解决问题。这篇博客文章的链接虽然没有提供具体内容,但我们可以根据主题来深入讲解匿名内部类的基本概念、用途及其优势...

    JAVA_匿名内部类

    Java匿名内部类是Java语言中一个独特且强大的特性,它允许我们在不定义完整类的情况下创建对象。这种类没有名字,通常用作一次性实现某个接口或继承某个类的情况,尤其是在需要简洁代码的地方。以下是对Java匿名内部...

    Java的内部类讲解案例代码(成员内部类、局部内部类、匿名内部类、静态内部类、外部类访问四种内部类、其他类访问四种内部类...)

    - 不同类型的内部类(静态内部类、成员内部类、局部内部类和匿名内部类)的区别和用法 - 内部类的优缺点以及使用场景 这些目标将帮助你理解和掌握内部类的概念,并能够在适当的时候使用内部类来解决问题...

    匿名内部类核心重点知识讲解和经典例子讲解

    匿名内部类是Java编程语言中一个独特且强大的特性,它允许我们在定义类的同时不为它提供名字。这种类主要用于创建一次性使用的对象,特别是在需要实现某个接口或者继承某个类的场合,而无需创建独立的文件来定义这个...

    Android开发匿名内部类

    总结来说,匿名内部类是Java编程中一种强大的工具,它允许开发者快速实现接口,简化代码,特别是在需要事件处理或访问外部类私有成员的场景下。在Android开发中,匿名内部类经常用于创建自定义的`OnClickListener`、...

    基于Java的匿名内部类分析与应用.pdf

    匿名内部类的应用场景 匿名内部类广泛应用于Java开发中,例如: * 实现接口中的方法 * 创建Event处理器 * 创建Thread对象 * 实现数据结构中的节点类 结论 匿名内部类是Java语言中的一大特色,它提供了一种灵活的...

    java 内部类 局部内部类 匿名类 实例代码

    以上四个例子分别展示了Java内部类的四种类型及其使用场景。在实际编程中,根据需求选择合适的内部类类型可以提高代码的可读性和可维护性。通过理解这些内部类的差异和用法,开发者能够更好地利用Java的这一特性来...

    内部类知识总结3--匿名内部类

    例如,事件监听器的回调函数常常使用匿名内部类来实现。 2. **匿名内部类的声明与实例化** 匿名内部类可以直接在需要的地方声明并实例化,格式如下: ```java new InterfaceName() { // 实现接口方法 }; ``` ...

    匿名内部类代码

    文件`FactoryTest`和`FactoryTest2`可能包含了不同的工厂模式实现或匿名内部类的使用场景,比如多态、策略模式等。在实际项目中,你可以根据需求动态地创建不同类型的对象,或者在运行时改变对象的行为,这些都是...

    Android 匿名内部类作为事件监听类

    本文将深入探讨如何使用匿名内部类作为事件监听类,以及其在Android开发中的应用。 首先,我们需要理解什么是匿名内部类。在Java中,匿名内部类是没有名字的类,它可以直接继承一个类或实现一个接口。在Android中,...

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

    Java中的内部类分为静态内部类(Static Inner Class)和非静态内部类(Non-static Inner Class),非静态内部类又包括成员内部类(Member Inner Class)、局部内部类(Local Inner Class)以及匿名内部类(Anonymous...

    使用内部和匿名类优化Java代码

    使用内部类和匿名类可以优化Java代码,提高代码的复用性和模块化。然而,过度使用或不恰当使用这些特性可能导致代码过于复杂,难以理解和维护。因此,应谨慎选择何时使用内部类和匿名类,确保它们的使用符合设计原则...

    java内部类使用例子

    这种内部类可以是成员内部类、局部内部类、匿名内部类或静态内部类,每种都有其特定的用途和使用场景。在这个"java内部类使用例子"中,我们将深入探讨这些类型,并通过实际代码来理解它们的工作原理。 首先,成员...

    java中的匿名内部类总结

    本篇文章将深入探讨匿名内部类的概念、使用场景以及其在实际编程中的应用。 首先,匿名内部类没有名称,但它仍然是一个完整的类,它可以实现接口或者继承其他类。当一个类只在一个地方被使用,并且它的功能相对简单...

    Java特别篇-匿名对象与匿名内部类

    ### Java特别篇-匿名对象与匿名内部类 #### 一、匿名对象 在Java中,对象通常通过定义一个变量来引用。例如,在`Student`类的例子中,我们首先定义了一个`Student`类,并且在`Test`类的`main`方法中通过以下方式...

Global site tag (gtag.js) - Google Analytics