[14-03] 示例:利用匿名内部类简化代码


内部类的其中一个优势就是可以简化代码,现在以一个常用的JDBC获取数据封装对象的例子,来简单谈谈如何使用匿名内部类来简化代码。

下面这段代码,是用JDBC连接,到数据库查询到数据之后,将数据封装到对象中进行返回,很常见的场景
public List<DepartmentMember> getMemberByDepartmentId(long departmentId) { 
    List<DepartmentMember> memberList = new ArrayList<DepartmentMember>();
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    String sql = "SELECT m.id,m.name,m.phone,d.value  " +
                 "FROM t_department_member m LEFT JOIN p_dictionary d ON m.job_id = d.id WHERE m.department_id=?";

    try {
        conn = DBUtil.getConnection();
        ps = conn.prepareStatement(sql);
        ps.setLong(1, departmentId);
        rs = ps.executeQuery();
        while (rs.next()) {
            DepartmentMember member = new DepartmentMember();
            member.setId(rs.getLong("id"));
            member.setName(rs.getString("name"));
            member.setPhone(rs.getString("phone"));
            member.setJob_name(rs.getString("value"));
            memberList.add(member);
        }

    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        DBUtil.closeConnection(conn, ps, rs);
    }

    return memberList;
}

可以看到,使用JDBC连接获取数据的话,重复的代码很多,数据连接,获取语句对象,执行查询操作,抓取异常处理,以及关闭连接。这部分代码完全没必要每次都写,想想看,每写一次类似的获取数据的方法,这些代码要copy一次,也是很麻烦的了。

设想一下,我能不能写一个方法,把这些重复的代码写好,每次只需要传入需要变动的那部分就行了。那么问题来了,变动的部分,如果只是sql,那还行,毕竟是String字符串,可以作为形参传入;那我 while(rs.next()) 中的执行代码也想传入,怎么办?Java是不像JS那种可以把函数作为参数的,只能是对象。

没关系,我们先按这个思路试一下,把重复的代码单独写在一个方法中:
  • 为了能让支持其他数据库连接,我们把连接对象作为方法参数传入
  • 为了传入变动代码,我们需要设定一个抽象类作为参数,在方法中执行其方法
public void executeQuery(Connection conn, String sql, SqlExecute action) {
    long start = System.currentTimeMillis();
    log.debug("execute query start, sql:");
    log.debug(sql);

    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        ps = conn.prepareStatement(sql);
        //set prepared sql params
        action.setParam(ps);
        rs = ps.executeQuery();
        while (rs.next()) {
            //do things while resultset.next()
            action.next(rs);
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        DBUtil.closeConnection(conn, ps, rs);
    }

    long end = System.currentTimeMillis();
    log.debug("execute query end, cost time:" + (end - start) + "ms");
}
public abstract class SqlExecute {

    /**
     * 预编译语句对象赋值
     *
     * @param ps PreparedStatement预编译语句对象
     */
    void setParam(PreparedStatement ps) throws SQLException {
        //for override
    }

    /**
     * 执行操作
     *
     * @param rs ResultSet结果集
     */
    void next(ResultSet rs) throws SQLException {
        //for override
    }

}

可以看到,由此以来,我只需要调用 executeQuery(Connection conn, String sql, SqlExecute action) 方法,然后需要执行的操作,以SqlExecute的实现类来传入就可以了。但是,每次都为了一个方法,新建一个类来实现SqlExecute,反而显得更繁琐了。幸好,我们有匿名内部类,实际上,我们使用的时候,会变成这样。还是以文章前面的JDBC连接为示例:
public List<DepartmentMember> getMemberByDepartmentId(final long departmentId) {
    final List<DepartmentMember> memberList = new ArrayList<DepartmentMember>();
    String sql = "SELECT m.id,m.name,m.phone,d.value " +
                 "FROM t_department_member m LEFT JOIN p_dictionary d ON m.job_id = d.id WHERE m.department_id=?";

    executeQuery(DBUtil.getConnection(), sql, new SqlExecute() {
        @Override
        void setParam(PreparedStatement ps) throws SQLException {
            ps.setLong(1, departmentId);
        }
        @Override
        void next(ResultSet rs) throws SQLException {
            DepartmentMember member = new DepartmentMember();
            member.setId(rs.getLong("id"));
            member.setName(rs.getString("name"));
            member.setPhone(rs.getString("phone"));
            member.setJob_name(rs.getString("value"));
            memberList.add(member);
        }
    });

    return memberList;
}

如上方式可以看到,相当于直接把变动的代码作为了参数传入方法。而好处在于,代码变得简洁直观,setParam() 部分就是设置预编译语句对象的值,而next() 就是在对结果集每一行要执行的操作,一目了然,如果后续需要代码变动,在哪里改就可以迅速找到,同时也不需要在此处去特别使用try catch包括代码和处理异常。另外,在executeQuery方法中,顺便还加入了日志打印的方法,因此你也不必再单独每个方法去写一些日志输出的方法了。

最后,值得一提的是,因为内部类的原理,所以这里可以看到,传参的基本数据类型(如departmentId),以及非内部类中的引用数据类型(对象,如memberList),都必须标注为final修饰,这个在内部类的基础知识点钟已经提到,这里就不再过多阐述了。


posted @ 2018-01-20 12:37  Dulk  阅读(547)  评论(0编辑  收藏  举报