Java关于Scala的“视界(view bound)”的模拟

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);
    }

你可能感兴趣的:(java,scala,算法,Access)