本篇文章将基于java和python分别介绍Lambda表达式,包括定义,使用等
java函数式编程
自jdk1.8开始,java中引入了函数式编程,使编程更加简洁灵活。接下来通过详细的例子阐述
函数式接口
Lambda语法
Function
方法引用
引用静态方法
引用指定对象的实例方法
引用任意对象的实例方法
引用构造方法
stream
有如下的person类,属性为name、sex、age
package lambda.pojo; /** * @author liufeifei * @date 2018/06/26 */ public class Person { public enum Sex{ MALE,FEMALE; } private String name; private Sex sex; private int age; public Person(String name, Sex sex, int age) { this.name = name; this.sex = sex; this.age = age; } public Person() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public Sex getSex() { return sex; } public void setSex(Sex sex) { this.sex = sex; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void printPerson() { System.out.println("name:" + this.name + " age:"+ this.age + " sex:" + this.sex); } }
现在需要筛选List<Person>中年龄大于age的人员,另一个需求需要筛选性别为男性的人员。我们可能会写如下两个实现方法供使用方调用:
public static void printPersonsOlderThan(List<Person> persons,int age) { for(Person p:persons) { if(p.getAge() >= age) { p.printPerson(); } } } public static void printPersonsSex(List<Person> persons,Sex sex) { for(Person p:persons) { if(p.getSex() == sex) { p.printPerson(); } } }
以上的场景得到了解决。但是麻烦来了,如果还需要筛选“zhang”姓的人员,还要筛选年龄段在low和hight之间的人员。。。
显然以上设计是不合理的,如果继续增加方法,方法数量呈直线增加,我们可以考虑设计接口,并在print方法中传入接口,用户只需要传入实现接口的匿名类
--接口 public interface CheckPerson { boolean test(Person p); } --打印方法 printPersons(List<Person> persons, CheckPerson checkPerson) { for(Person p:persons) { if(checkPerson.test(p)) { p.printPerson(); } } } --调用时传入匿名类 Test.printPersons(list,new CheckPerson() { public boolean test(Person p) { return p.getAge() >= 18 && p.getAge() <= 25; } });
通过申明CheckPerson接口和实现printPersons一个方法,我们就可以筛选满足个人条件的数据进行打印。
函数式接口
上述CheckPerson接口为函数式接口。java中将只有一个抽象方法的接口称为函数式接口(不包括和Object中同名的方法,如Comparator中同时含有compare和equals抽象方法,Object中有equals方法)。在api文档中包含@FunctionalInterface注解的均为函数式接口
Lambda语法
以上通过匿名类方式较复杂,针对函数式接口,java有其特定的语法,称为Lambda表达式,你可以通过如下方式调用
Test.printPersons(list,p -> p.getAge() >= 18 && p.getAge() <= 25);
lambda语法分三部分
参数列表
上述用法完整参数形式为(Person p) ,可以省略掉类型申明和括号
箭头
参数和方法体之间用 ->指向
方法体
方法体可以是一个表达式,也可以是语句块。还是以上面为例子,你也可以这样写
-- 完整形式 Test.printPersons(list,(Person p) -> {return p.getAge() >= 18 && p.getAge() <= 25;}); -- 简写形式 Test.printPersons(list,(Person p) -> p.getAge() >= 18 && p.getAge() <= 25);
Lambda表达式可以看成一个方法申明;你可以认为它是一个没用名称的匿名方法
Function
通过上面实例我们可以看到,为了使用lambda表达式我们需要申明一个函数式接口,这在一定程度上增加了复杂度,有没有可能不需要申明接口也能使用lambda表达式,答案是肯定的。接下来该Function登场了,在java.util.function包下申明了很多函数式接口。其中具有代表性的为如下:
-- Consumer 接口 public interface Consumer<T> { void accept(T t); } -- Predicate 接口 public interface Predicate<T> { boolean test(T t); } -- Function 接口 public interface Function<T, R> { R apply(T t); } -- Supplier 接口 public interface Supplier<T> { T get(); }
Consumer 表示有输入参数,没有返回类型的方法
Predicate 表示有输入参数,返回类型为boolean的方法
Function 表示有输入参数且有返回类型的方法
Supplier 表示没有输入参数有返回类型的方法
有了以上的接口申明,针对此类问题再也不需要进行另外接口申明,于是我们的方法可以改写为如下:
-- 方法 public static void print(List<Person> persons,Predicate<Person> func) { for(Person p:persons) { if(func.test(p)) { p.printPerson(); } } } -- 调用 Test.print(list,(Person p) -> p.getAge() >= 18 && p.getAge() <= 25);
方法引用
针对lambda还有更简洁的使用方式
类型 | 例子 |
---|---|
引用静态方法 | ContainingClass::staticMethodName |
引用指定对象的实例方法 | ContainingObject::instanceMethodName |
引用任意对象的实例方法 | ContainingType::methodName |
引用构造方法 | ClassName::new |
引用静态方法
List<String> list1 = new ArrayList<>(Arrays.asList("xxx","yyy","zzz",null)); -- 静态方法 原始使用 list1.stream().filter(item -> Strings.isNotEmpty(item)); -- 静态方法 方法引用 list1.stream().filter(Strings::isNotEmpty)
引用指定对象的实例方法
List<String> list1 = new ArrayList<>(Arrays.asList("xxx","yyy","zzz",null)); --原始写法 list1.stream().map(item -> item.toUpperCase()); -- 方法引用 list1.stream().map(String::toUpperCase);
引用任意对象的实例方法
String[] stringArray = { "Barbara", "James", "Mary", "John","Patricia", "Robert", "Michael", "Linda" }; Arrays.sort(stringArray, String::compareToIgnoreCase);
引用构造方法
public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>> DEST transferElements( SOURCE sourceCollection, Supplier<DEST> collectionFactory) { DEST result = collectionFactory.get(); for (T t : sourceCollection) { result.add(t); } return result; } Set<Person> rosterSetLambda = transferElements(roster, () -> { return new HashSet<>(); }); Set<Person> rosterSet = transferElements(roster, HashSet::new); Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);
Stream
如果要对Collection进行一系列聚合操作(filter、map等),需要用到Stream和管道
一个管道是顺序的聚合操作。接下来的操作给出了stream的使用方法
-- roster为list roster .stream() .filter(e -> e.getGender() == Person.Sex.MALE) .forEach(e -> System.out.println(e.getName()));
一个管道包括如下部分:
一个源:源可以是集合,数组,泛型函数。上例中为collection roster
0个或者多个中间操作,如filter,此操作产生一个新的stream(流)
流是一系列元素。 与集合不同,它不是存储元素的数据结构。 相反,流通过管道从源传输值。 此示例通过stream()方法从集合roster创建流。
过滤器操作返回与predicate匹配元素(此操作的参数)新流。 在此示例中,predicate是lambda表达式e - > e.getGender()== Person.Sex.MALE。 如果对象e的gender字段的值为Person.Sex.MALE,则返回布尔值true。 因此,此示例中的过滤器操作返回包含集合名单中所有男性成员的流。
终端操作。 终结操作(例如forEach)产生非流结果,例如原始值,集合,或者对于forEach,根本没有值。 在此示例中,forEach操作的参数是lambda表达式e - > System.out.println(e.getName()),它在对象e上调用方法getName。 (Java运行时和编译器推断对象e的类型是Person。)
以下示例计算集合名单中包含的所有男性成员的平均年龄,其中管道包含聚合操作过滤器,mapToInt和average
double average = roster .stream() .filter(p -> p.getGender() == Person.Sex.MALE) .mapToInt(Person::getAge) .average() .getAsDouble();
python匿名函数
python中lambda又叫匿名函数,其语法为
lambda [arg1 [,art2,…artn]]:expression # [arg1 [,art2,…artn]] 为参数 # expression 为计算表达式
匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果
实战
有list为[1,2,3,4,5,6,7],需要先过滤掉其中偶数,然后对每个元素进行,然后对每个元素求和,此处要求我们用filter、map、reduce等函数进行操作
# python版本为3.5 from functools import reduce li = [1, 2, 3, 4, 5, 6, 7] def filter_func(x): return x % 2 == 1 def map_func(x): return x * x def reduce_func(x, y): return x + y print(reduce(reduce_func,map(map_func,filter(filter_func, li)))) # lambda表达式 print(reduce(lambda x, y: x + y, map(lambda x: x * x, filter(lambda x: x % 2 == 1, li)))) # 使用sum简洁写法 print(sum(map(lambda x: x * x, filter(lambda x: x % 2 == 1, li))))