原始的一段代码

public long addToList(final String key, final String... elements ) {
    try (Jedis jedis = jedisPool.getResource()){
        return jedis.rpush(key, elements);
    }
}

public long del(final String key) {
    try (Jedis jedis = jedisPool.getResource()) {
        return jedis.del(key);
    }
}

如果只是使用上面的形式的话,每调用一个 Jedis 的操作,都需要重复地写一遍 try with resource 这个形式的语句,而且基本上是没有办法进行复用。这个问题在 Java 8 之前应该是很难解决的。但是 Java 8 出来之后,使用 Java 8 提供的 Functional Interface,就可以将上面的代码当中重复的部分提取出来。

首先,明确我们的目标,我们想要将上面两段代码当中重复的部分,抽取出公共的部分,形成下面的形式。

R doJedisOperation(aFunction) {
  jedis = getJedisResource()
  return aFunction(jedis)
}

在 Ruby 或者 Lisp 等函数作为一等公民的语言当中,我们可以很容易通过语言的内置支持完成上面的重构,但是在 Java 当中,上面的操作需要通过Function接口的帮助。Function<T, R> 接口表示该对象代表一个“函数”,这个“函数”以一个类型为 T 的对象作为参数,产生一个结果,其类型为 R。

通过上面的形式,可以合理地推出下面三点:

1、这个函数的输入参数类型为 Jedis;
2、函数的具体代码,由用户在调用该方法时提供;
3、这个函数的返回参数类型最终由用户在函数的具体代码中定义。

通过上面三点,我们可以大概想到,将执行 jedis 操作放在 function 函数当中。于是有下面的代码

public <R> R doRedisOp(Function<Jedis, R> function) {
    return function.apply(jedis);
}

// 调用方法
// 上面的添加列表可以简化成
doRedisOp(jedis -> jedis.rpush(key, elements));

// 而删除操作则是
doRedisOp(jedis -> jedis.del(key));

可以看到,现在 Java 8 的函数式的编程的方法,可以比较简单地消除一些在前面版本当中很难去消除的一些重复代码,例如上面代码当中的调用 Jedis 资源,执行 Redis 操作的重复代码。

如果在以前的版本当中,我能够想到的大概思路就是写一个抽象类,这个类当中,有一个抽象方法,这个抽象代码可以在代码当中使用内部匿名类的方法使用,做一些操作。大致的代码如下:

public abstract class JedisOperation<T> {

    private JedisPool jedisPool = new JedisPool("localhost");

    protected abstract T jedisOp(Jedis jedis);

    public T doOp() {
        try (Jedis jedis = jedisPool.getResource()){
            return jedisOp(jedis);
        }
    }
}

// 调用方法

public class CallJedisOperation {
    public static void main(String[] args) {

        new JedisOperation<Long>() {
            @Override
            protected Long jedisOp(Jedis jedis) {
                return jedis.del("key");
            }
        }.doOp();

        new JedisOperation<Long>() {
            @Override
            protected Long jedisOp(Jedis jedis) {
                return jedis.rpush("key", "element");
            }
        }.doOp();
    }
}

用上面的方法是可以将 Jedis 的资源获取的代码放在一个地方的,然后这么做,反倒产生了更多的代码。相比于使用新的语法,这种做法实在是有点落后。

个人感受:

1、虽然 Java 8 当中的函数式编程虽然相比与 Lisp Ruby 这类语言,不够优雅,然而在实际运用当中还是有很大的用处的。
2、上面的例子当中,还体现了写程序当中很需要重视的一个原则:隔离不变和变化的代码。在这个例子当中,不变的代码就是获取 Jedis 资源的代码,而变化的代码则是对于这个 Jedis 代码如何进行操作。
3、当你需要做一些操作做延迟计算的时候,会使用到函数式的编程,这和 abstract 方法是一个道理。使用函数式的编程,我们会先定义好一个计算过程,这个计算过程的参数是一个函数,而这个函数由在别的地方调用的代码提供。使用 abstract 方法则是将这个延迟计算的代码放到了子类的实现当中。