..

在 Java & Python 中,如何优雅的筛选一堆苹果🤔

最近在看《Java 8实战》这本书,第一部分讲了很多函数式编程与 lambda 匿名函数的应用,不禁让我想起了以前写 python 对应的实现。

需求: 在一堆苹果中,筛选出重量大于 100g 的苹果🍎,同时也支持过滤所有绿色的苹果

Java 版本

方案一:通过行为参数化传递代码

了解过「策略模式」的同学,都知道可以将「行为」作为参数,增加代码的灵活性与可读性。

但看上去有一些累赘哦。。🤔

public interface ApplePredict {
    boolean test(Apple apple);
}

public class AppleHeavyPredict implements ApplePredict {
    @Override
    public boolean test(Apple apple) {
        return apple.getWeight() > 200;
    }
}

public static List<Apple> filterApples(List<Apple> apples, ApplePredict predict) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : apples) {
        if (predict.test(apple)) { result.add(apple); }
    }
    return result;
}
	
// 获取苹果
List<Apple> heavyApples = filterApples(apples, new AppleHeavyPredict());
// 为什么不直接写个 for 循环过滤呢?
// 因为这样可以针对颜色等属性,更加灵活的过滤
List<Apple> heavyApples = filterApples(apples, new AppleColorPredict());

方案二:使用 lambda & Predicate

同时使用内置的 Predicate 方法代替 ApplePredict,然后利用匿名函数代替 AppleHeavyPredict

import java.util.function.Predicate;
public static List<Apple> filterApples(List<Apple> apples, Predicate<Apple> predict) {...}

// 获取苹果
List<Apple> lambdaHeavyApples = filterApples(apples, (Apple apple) -> apple.getWeight() > 100);

方案三:使用 方法引用

java8 支持方法的引用,分为三种:

  1. 静态方法,e.g. Integer::parseInt
  2. 类型 - 实例方法,e.g. String::length
  3. 实例对象 - 实例方法,e.g. expensiveTransaction::getValue

所以 lambda 又可以简化为方法引用(当然成本为在 Apple 中新增了一个isHeavyApple方法):

import java.util.function.Predicate;
public static List<Apple> filterApples(List<Apple> apples, Predicate<Apple> predict) {...}

// 获取苹果
List<Apple> lambdaHeavyApples = filterApples(apples, Apple::isHeavyApple);
List<Apple> lambdaHeavyApples = filterApples(apples, Apple::isGreen);

方案四:使用 Stream

那么 filterApples 是否也可以被省略呢?利用 java8 中的 Stream 一行代码过滤出你想要的苹果:

List<Apple> heavyApples = apples.stream()
    .filter(Apple::isHeavyApple)
    .collect(Collectors.toList());

Python 版本

方案一:lambda + filter

虽然语法上略有不同,但大致思路与 java 的实现可以说基本一致!

apples = [Apple("green", 150), Apple("red", 100)]
heavy_apples = list(filter(lambda x: x.weight > 100, apples))

同样支持直接将「方法引用」作为参数:

def is_heavy(apple: Apple):
    return apple.weight > 100

apples = [Apple("green", 150), Apple("red", 100)]
heavy_apples = list(filter(is_heavy, apples))

方案二:列表解析

但很久以前也不记得在哪本书上看到,不推荐 filter 而统一使用更为直观的 list comprehension:

heavy_apples = [apple for apple in apples if apple.weight > 100]

个人感想

记得大学里学习 java 用的还是 1.6,还没有这么多骚操作。。

虽然现在双放都可以用一行代码实现需求,但个人觉得这轮比拼还是 python 的 list comprehension 更胜一筹🤔

因为这种特有的写法更符合人类直觉,你觉得呢?😄