java和python中函数式编程

3 篇文章 0 订阅
本篇文章将基于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() {
              @Override
              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 接口
  @FunctionalInterface
  public interface Consumer<T> {
    void accept(T t);
  }
  ​
  -- Predicate 接口
  @FunctionalInterface
  public interface Predicate<T> {
    boolean test(T t);
  }
  ​
  -- Function 接口
  @FunctionalInterface
  public interface Function<T, R> {
    R apply(T t);
  }
  ​
  -- Supplier 接口
  @FunctionalInterface
  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))))


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值