Scala中有一个概念,叫做“视界”。所谓“视界”是指,我不care这个对象具体是什么类型,但我可以把它“视作”某种类型加以利用。
比如说,有如下的函数定义:
def quick[T <% Ordered[T]](list: List[T]): List[T] = {
list match {
case Nil => Nil
case x::xs =>
val (before,after) = xs partition (_ < x)
quick(before) ++ (x :: quick(after))
}
}
OK,这是我从别的地方抄来的快速排序算法实现。重点不在算法这里。在“<%”这个符号上。这个符号说明了,对于T这个类型,只要符合与Ordered[T]的调用方式,甚至可以不是Ordered[T]的子类,都可以放到这个函数中使用。举例来说:
trait Ordered[T] {
def < (x: T): Boolean
}
Ordered特质定义了“<”这个函数。而Int类也定义了相同签名的函数,只是Int类并不继承于Ordered特质(这里继承的说法可能欠妥,Java习惯性思维,见谅)。因此,前面的快速排序使用了“<%”符号说明,只要能把对象“视作”某种类型,就可以用这个函数来排序。
那么,Java中是否可以用同样的思想做些什么事情呢?举例如下:
我们知道,连接数据库有三种常见对象,分别是Connection,Statement,以及ResultSet。这三种对象都有关闭方法,且签名一致。
public interface Connection {
//...
/**
* @exception SQLException if a database access error occurs
*/
void close() throws SQLException;
//...
}
public interface ResultSet {
//...
/**
* @exception SQLException if a database access error occurs
*/
void close() throws SQLException;
//...
}
public interface Statement {
//...
/**
* @exception SQLException if a database access error occurs
*/
void close() throws SQLException;
//...
}
为了聚集问题,摘抄的是JDK1.5的代码,因此可能与你看到的源代码不尽一致。
我们可以发现,三个对象源于三个接口,虽然方法签名一致,但接口不一样,因此无法作为同一类对象发给释放资源的函数。从而为了安全地释放三个对象,你需要这样写代码:
private void recycle(Connection conn, Statement stmt, ResultSet rs)
{
if (rs != null) {
try {
rs.close();
} catch (SQLException ex) {
// no-op
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException ex) {
// no-op
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException ex) {
// no-op
}
}
}
一般来讲,应该是这个样子的吧?每个对象的SQLException都必须单独catch住,并且什么都不做,然后执行下一个对象的释放。当然这里做的有点儿绝对,你可以考虑仅释放conn即可,我只是拿来做例子。
这里我们看到的是,三个对象的接口类型都包含了public void close() throws SQLException;方法,但它们不是同一个类型的对象。那么有什么办法把它们“视作”同一个对象吗?
以下的作法是错误的。
interface Resource
{
void close() throws SQLException;
}
//...
private void close(Resource res)
{
if (res != null) {
try {
res.close();
} catch (SQLException ex) {
// no-op
}
}
}
private void recycle(Connection conn, Statement stmt, ResultSet rs)
{
close((Resource) rs);
close((Resource) stmt);
close((Resource) conn);
}
//...
因为对象不能被造型为非父类或父接口的类型。
不过以上的方法启发了我们,我们需要的就是类似于“视界”效果的一种操作,或者说是辅助方法。因为Java内部没有建立“%>”机制,所以我们需要自己做。
以下是使用Java的Proxy实现的辅助方法,可以把任何对象造型为另一种类型的对象。不过造型之前会首先检查是否合法。目前仅检查函数名与参数列表,若细致一些还可以增加对访问修饰符和抛出异常的检查。
final class ViewBoundHelper
{
private ViewBoundHelper() { /* no-op */ }
private static final class Handler implements InvocationHandler
{
private final Object target;
public Handler(Object t)
{
target = t;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
return getDeclaredMethod(method).invoke(target, args);
}
Method getDeclaredMethod(Method m)
{
Class<?> cls = target.getClass();
while (true) {
try {
m = cls.getDeclaredMethod(m.getName(), m.getParameterTypes());
break;
} catch (NoSuchMethodException ex) {
cls = cls.getSuperclass();
if (cls == null) {
return null;
}
}
}
return m;
}
}
public static Object asIs(Object o, Class<?> intf)
{
Handler handler = new Handler(o);
for (Method method : intf.getDeclaredMethods()) {
if (handler.getDeclaredMethod(method) == null) {
throw new ClassCastException(o.getClass() + "can't be viewed as " + intf.getName());
}
}
return Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[] {
intf
}, handler);
}
}
使用方式如下:
public static void main(String[] args) throws SQLException
{
Connection conn = new FakeConn();
Resource res = (Resource) ViewBoundHelper.asIs(conn, Resource.class);
res.close();
System.out.println(res);
}