1 解耦
例,我们验证邮箱,将邮箱校验逻辑和校验成功失败后处理的业务逻辑拆分。
public class EmailValidation1 {
// 定义校验逻辑
static Pattern emailPattern = Pattern.compile("^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$");
// 定义校验成功失败回调逻辑
static Consumer<String> success = s -> System.out.println("Mail sent to " + s);
static Consumer<String> failure = s -> System.err.println("Error message logged: " + s);
// 应用
public static void main(String... args) {
emailChecker.apply("this.is@my.email").bind(success, failure);
emailChecker.apply(null).bind(success, failure);
emailChecker.apply("").bind(success, failure);
}
// 校验逻辑
static Function<String, Result<String>> emailChecker = s ->
s == null
? new Result.Failure<>("email must not be null")
: s.length() == 0
? new Result.Failure<>("email must not be empty")
: emailPattern.matcher(s).matches()
? new Result.Success<>(s)
: new Result.Failure<>("email " + s + " is invalid.");
}
接口定义:
public interface Result<T> {
void bind(Consumer<T> success, Consumer<String> failure);
static <T> Result<T> failure(String message) {
return new Failure<>(message);
}
static <T> Result<T> success(T value) {
return new Success<>(value);
}
class Success<T> implements Result<T> {
private final T value;
private Success(T t) {value = t;}
@Override
public void bind(Consumer<T> success, Consumer<String> failure) {
success.accept(value);
}
}
class Failure<T> implements Result<T> {
private final String errorMessage;
private Failure(String s) {this.errorMessage = s;}
@Override
public void bind(Consumer<T> success, Consumer<String> failure) {
failure.accept(errorMessage);
}
}
}
2 处理集合
@Test
public void testFold() {
List<Integer> listInteger = list(1, 2, 3, 4, 5, 6);
// 对集合元素进行累加
assertEquals(Integer.valueOf(21), fold(listInteger, 0, x -> y -> x + y));
// 字符串拼接
Function<Integer, Function<String, String>> f = x -> y -> "(" + + " + " + y + ")";
assertEquals("(1 + (2 + (3 + (4 + (5 + 0)))))", foldRight(list, "0", f));
}
// 对集合元素进行折叠
public static Integer fold(List<Integer> is, Integer identity,
Function<Integer, Function<Integer, Integer>> f) {
for (Integer i : is)
identity = f.apply(identity).apply(i);
return identity;
}
public static <T, U> U foldRight(List<T> ts, U identity, Function<T, Function<U, U>> f) {
for (int i = ts.size(); i > 0; i--) {
identity = f.apply(ts.get(i - 1)).apply(identity);
}
return identity;
}
3 按规则生成数据
@Test
public void testUnfold() {
assertEquals("[1, 2, 4, 8]", unfold(1, x -> x * 2, x -> x < 10).toString());
assertEquals("[x, xx, xxx, xxxx]", unfold("x", x -> x + "x", x -> x.length() < 5).toString());
}
// 对折叠(表达式也算是折叠)进行展开
public static <T> List<T> unfold(T seed, Function<T, T> f, Function<T, Boolean> p) {
List<T> result = new ArrayList<>();
T temp = seed;
while (p.apply(temp)) {
result = append(result, temp);
temp = f.apply(temp);
}
return result;
}
public static <T> List<T> append(List<T> list, T t) {
List<T> ts = new ArrayList<>(list);
ts.add(t);
return Collections.unmodifiableList(ts);
}
// 范围生成
public static List<Integer> range(Integer start, Integer end) {
return range_(list(), start, end).eval(); // eval利用迭代,执行生成逻辑
}
private static TailCall<List<Integer>> range_(List<Integer> acc, Integer start, Integer end) {
return end <= start
? ret(acc) // 见5递归抽象,封装计算完成后的结果
// 函数不能修改对象,因此append逻辑是根据现有的数据创建新的list对象,再添加新元素,返回
: sus(() -> range_(append(acc, start), start + 1, end));
}
@Test
public void testRange() {
assertEquals("[0, 1, 2, 3, 4]", Range.range(0, 5).toString());
assertEquals("[]", Range.range(5, 1).toString());
}
4 尾递归
// 线性递归
Integer sum(List<Integer> list) {
return list.isEmpty()
? 0
: head(list) + sum(tail(list));
}
// 尾递归
Integer sumTail(List<Integer> list, int acc) {
return list.isEmpty()
? acc
// 比线型递归多一个参数,这个参数就是上一次函数调用计算的值
// 可以看到,当前函数计算结果后直接return,没有剩余任何指令,不会再回来了
: sumTail(tail(list), acc + head(list));
}
如果编译器支持消除尾调用
(Tail Call Eliminate),使用尾递归能优化普通的线型递归。
优点:尾递归每次调用都在收集结果,避免了线性递归不收集结果只能依靠依次展开消耗大量栈内存。
测试结果:线性递归sum调用次数5464、5462、5464,尾递归sumTail调用次数5033、5030、5030。这,,,怎么资源消耗还提升了呢?答:很遗憾,Java没有TCE。
尾递归就真的没有用了吗?请看6。
5 递归抽象
上面的尾递归在Java中没有用,那很不行啊,Java的递归深度问题难道就无解了么?
有还是有的,利用函数式、迭代辅助实现:
// 方法调用可以暂停和恢复
public abstract class TailCall<T> {
public abstract TailCall<T> resume();
public abstract T eval();
public abstract boolean isSuspend();
public static class Return<T> extends TailCall<T> {
private final T t; // 存储递归计算最终结果
public Return(T t) {this.t = t;}
@Override
public T eval() {return t;}
@Override
public boolean isSuspend() {return false;}
@Override
public TailCall<T> resume() {throw new IllegalStateException("Return has no resume");}
}
public static class Suspend<T> extends TailCall<T> {
private final Supplier<TailCall<T>> resume; // 存储下一个调用
public Suspend(Supplier<TailCall<T>> resume) {this.resume = resume;}
@Override
public T eval() {throw new IllegalStateException("Suspend has no value");}
@Override
public boolean isSuspend() {return true;}
@Override
public TailCall<T> resume() {return resume.get();}
}
// 工厂方法
public static <T> Return<T> ret(T t) {
return new Return<>(t);
}
public static <T> Suspend<T> sus(Supplier<TailCall<T>> s) {
return new Suspend<>(s);
}
}
定义计算逻辑:
static TailCall<Integer> add(int x, int y) {
return y == 0
? TailCall.ret(x) // 保存最终计算结果
: TailCall.sus(() -> add(x + 1, y - 1)); // 利用Lambda表达式创建对象并保存当前调用计算结果
}
测试:
@Test
public void test() {
TailCall<Integer> tailCall = add(0, 100000000);
while (tailCall.isSuspend())
tailCall = tailCall.resume();
assertEquals(Integer.valueOf(100000000), tailCall.eval());
}
暴露更好用的API:
修改Suspend:
@Override
public T eval() {
TailCall<T> tailRec = this;
while (tailRec.isSuspend()) {
tailRec = tailRec.resume();
}
return tailRec.eval();
}
重新封装计算逻辑,add方法变字段(add是固定算法,就不传参了):
static Function<Integer, Function<Integer, Integer>> add = x -> y -> {
class AddHelper { // 定义计算逻辑
final Function<Integer, Function<Integer, TailCall<Integer>>> addHelper =
a -> b -> b == 0
? ret(a)
: sus(() -> this.addHelper.apply(a + 1).apply(b - 1)); // this 指向 AddHelper 实例
}
return new AddHelper().addHelper.apply(x).apply(y).eval();
};
调用测试:
@Test
public void test() {
assertEquals(Integer.valueOf(100000003), add.apply(3).apply(100000000));
}
6 递归优化
我们常用递归斐波那契数列写走楼梯的题,然而普通写法哪怕输入30都得计算半天,而优化后,哪怕是几百万的楼梯都能很快计算出结果。
// 时间复杂度 2^n,拉胯得很
public int fib(int num) {
if (num == 0 || num == 1)
return num;
return fib(num - 1) + fib(num - 2);
}
// 优化1 尾递归优化
public BigInteger fib_(BigInteger acc1, BigInteger acc2, BigInteger x) {
return x.equals(BigInteger.ZERO)
? BigInteger.ZERO
: x.equals(BigInteger.ONE)
? acc1.add(acc2)
: fib_(acc2, acc1.add(acc2), x.subtract(BigInteger.ONE)); // n 长度的斐波那契数列就会调用n次函数计算
}
// 优化2 迭代,避免栈溢出
private static TailCall<BigInteger> fib_2(BigInteger acc1, BigInteger acc2, BigInteger x) {
return x.equals(BigInteger.ZERO)
? ret(BigInteger.ZERO)
: x.equals(BigInteger.ONE)
? ret(acc1.add(acc2))
: sus(() -> fib_2(acc2, acc1.add(acc2), x.subtract(BigInteger.ONE)));
}
@Test
public void testFib1() {
System.out.println(fib(5)); // 只能计算几十,否则时间非常长
}
// 解决计算慢,利用到了尾递归,在时间复杂度上进行优化
@Test
public void testFib2() {
System.out.println(fib_(BigInteger.ONE, BigInteger.ZERO, BigInteger.valueOf(5))); // 8000 左右,主要看线程栈的大小
}
// 解决上面堆栈溢出,利用递归变迭代,在空间复杂度上进行优化
@Test
public void testFib3() {
// 轻松计算几十万的斐波那契数列
// 计算出下一个逻辑,根据下一个逻辑再计算,直到终止条件;通过迭代完成上述逻辑,相当于变成了线型计算
// (将递归计算值,转变为迭代计算计算公式)
// 可以简单理解为,走泥巴路,丢一块石头踏上去,再丢一块石头,再走,,,直到走出去为止
System.out.println(fib_2(BigInteger.ONE, BigInteger.ZERO, BigInteger.valueOf(6)).eval());
}
7 组合函数
static <T> Function<T, T> composeAll(List<Function<T, T>> list) {
// 应用:x.apply(y).apply(z) 调用顺序:x<-y<-z
// 等价于 x -> y -> x.compose(y) 等价于 x -> x::compose
return foldRight(list, identity(), x -> y -> z -> x.apply(y.apply(z)));
}
static <T> Function<T, T> composeAllViaAndThen(List<Function<T, T>> list) {
return foldRight(list, identity(), x -> y -> y.andThen(x));
}
测试:
@Test
public void testComposeAll1() {
Function<String, String> f1 = x -> x + "a";
Function<String, String> f2 = x -> x + "b";
Function<String, String> f3 = x -> x + "c";
Function<String, String> f4 = x -> x + "d";
Function<String, String> f5 = x -> x + "e";
List<Function<String, String>> list = Arrays.asList(f1, f2, f3, f4, f5);
assertEquals("edcba", ComposeAll.composeAll(list).apply(""));
}
@Test
public void testComposeAllViaAndThen() {
Function<String, String> f1 = x -> x + "a";
Function<String, String> f2 = x -> x + "b";
Function<String, String> f3 = x -> x + "c";
Function<String, String> f4 = x -> x + "d";
Function<String, String> f5 = x -> x + "e";
List<Function<String, String>> list = Arrays.asList(f1, f2, f3, f4, f5);
assertEquals("edcba", ComposeAll.composeAllViaAndThen(list).apply(""));
}
8 函数记忆化
定义一个缓存器:
public class Memorizer<T, U> {
private final Map<T, U> cache = new ConcurrentHashMap<>();
private Memorizer() {}
public static <T, U> Function<T, U> memoize(Function<T, U> function) {
return new Memorizer<T, U>().doMemoize(function);
}
private Function<T, U> doMemoize(Function<T, U> function) {
return input -> cache.computeIfAbsent(input, function::apply);
}
}
测试:
// 定义计算逻辑
private static Integer longCalculation(Integer x) {
// 模拟耗时计算
try {Thread.sleep(1_000);} catch (InterruptedException ignored) {}
return x * 2;
}
// 定义多参函数,组合计算逻辑
public static Function<Tuple3<Integer, Integer, Integer>, Integer> ft =
x -> longCalculation(x._1) + longCalculation(x._2) - longCalculation(x._3);
// 将函数与计算结果缓存,实现记忆化
public static Function<Tuple3<Integer, Integer, Integer>, Integer> ftm = Memorizer.memoize(ft);
@Test
public void automaticMemoizationExample3() {
long startTime = System.currentTimeMillis();
// 将Tuple3对象作为key,保存其计算结果
Integer result1 = ftm.apply(new Tuple3<>(2, 3, 4));
long time1 = System.currentTimeMillis() - startTime;
startTime = System.currentTimeMillis();
Integer result2 = ftm.apply(new Tuple3<>(2, 3, 4));
long time2 = System.currentTimeMillis() - startTime;
System.out.println("result1=" + result1 + "计算result1耗时:" + time1);
System.out.println("result2=" + result2 + "计算result2耗时:" + time2);
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/180303.html