内部类的其中一个优势就是可以简化代码,现在以一个常用的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修饰,这个在内部类的基础知识点钟已经提到,这里就不再过多阐述了。