原文地址:http://blog.laofu.online/2018/04/20/java-lambda/

为什么使用lambda

在java中我们很容易将一个变量赋值,比如int a =0;int b=a;

但是我们如何将一段代码和一个函数赋值给一个变量?这个变量应该是什么的类型?

在javascript中,可以用一个对象来存储。

var t=function()
{
  int a=1;
  a=a+1;
  alert(a);
} 

在java中,直到java8的lambda的特性问世,才有办法解决这个问题

什么是lambda

什么是lambda? lambda在程序中到底是怎样的一个存在? 首先看代码:

interface eat
{
  void eatFood();
}

public static void main(String[] args)
{

  eat e = () -> System.out.printf("hello\n");
  e.eatFood();

  eat e1 = new eat()
  {
      @Override
      public void eatFood()
      {
          System.out.printf("anoymous class\n");
      }
  };
  e1.eatFood();
}

上面的代码中,e是一个lambda的对象,根据java的继承的特性,我们可以说e对象的类型是继承自eat接口。而e1是一个正常的匿名类的对象.

通过对比, 可以说 lambda的表达式其实是接口的实现的“另一种方式”。这种方式更加简洁,更容易阅读。除了代码层面的简洁外,在编译的结果时候lambda也不会产生一个多余的匿名类。

对于eat这个特殊的接口,称之为:函数式接口

lamda的优点

  • 代码缩减

  • Option的使用简化代码

    假如我们有个方法,能够产生一个Option对象std

Option<Person> std=getStudent();

1、是否为空的判断

2、返回不为空的对象

3、多重if else的简化

函数式接口

什么是函数式接口?这个是我们理解Lambda表达式的重点,也是产生lambda表达式的“母体”,这里我们引用一个比较容易理解的说法:

函数式接口是 一个只有一个抽象方法(不包含object中的方法)的接口。

这个需要说明一点,就是在Java中任何一个对象都来自Object 所有接口中自然会继承自Object中的方法,但在判断是否是函数式接口的时候要排除Object中的方法,下面举几个例子如下:

//这个是函数式接口
interface eat
{
   void eatFood();
}

//这个不是是函数式接口
interface eat
{
  default void eatFood()
  {
     System.out.println("hello");
  };
}

//这个是一个函数式接口
interface eat
{
 void eatFood();
 String toString();
}

对于是否是函数式接口,java8中也提供了一个专用的注解:@FunctionalInterface。通过这个注解,可以确定是否是函数式接口:

//此处会报编译错误
@FunctionalInterface
interface eat
{
  default void eatFood()
  {
    System.out.println("hello");
  };
}

下面我们写一段lambda简单的代码,找出指定的身份证号码,并打印出来。

最终的调用:

对于上面的代码实现,在我们调用excutor方法前,并不知道findName的实现方法,直到在最后把一个方法作为参数传入到excutor方法中。

反思:函数式接口NameCheckInterface,是不是可以用来表示所有返回值为bool类型的,有两个形参(类型是passager 和String类型)的lambda表达式?

如果我们再配合泛型的话,是不是我们只需要定义一个通用的函数式接口?下面我们改写下代码:

@FunctionalInterface
public interface NameCheckInterface<T,T1,T2>
{
   T2 findName(T passager,T1 name);
}

@FunctionalInterface
public interface PrintInterface<T>
{
  void printName(T name);
}

private void excutor(List<passager> passagerList, NameCheckInterface<Boolean,passager,String> checker, PrintInterface<String> printer)
{
  for (passager p : passagerList) {
      if (checker.findName(p,"李四")){
        printer.printName(p.getPassagerNo());
      }
  }
}

对应的调用方法

@Test
public void simpTest()
{
  List<passager> passagerList = new ArrayList<>();
  passagerList.add(new passager("李四", "123456789"));
  passagerList.add(new passager("张三", "123456789"));
  passagerList.add(new passager("王二", "123456789"));

  excutor(passagerList,(p,str)->p.getName().equals(str),str-> System.out.println(str));
}

对于这段代码,可以得出lambda中的函数式接口是可以公用的,而jdk中也已经定义了很多通用的函数式接口。


常用的函数式接口

在jdk中通用的函数式接口如下(都在java.util.function包中):

Runnable r = () -> System.out.printf("say hello");//没有输入参数,也没有输出
Supplier<String> sp = () -> "hello";//只有输出消息,没有输入参数
Consumer<String> cp = r -> System.out.printf(r);//有一个输入参数,没有输出
Function<Integer, String> func = r -> String.valueOf(r);//有一个输入参数 有一个输出参数
BiFunction<Integer, Integer, String> biFunc = (a, b) -> String.valueOf(a + b);//有两个输入参数 有一个输出参数
BiConsumer<Integer, Integer> biCp = (a, b) -> System.out.printf(String.valueOf(a + b));//有两个输入参数 没有输出参数

PS:上面是基本的方法,其他的都是基于这几个扩展而来

如果上面的代码使用jdk中的函数式接口的话,就不用额外的定义NameCheckInterface和PrintInterface 接口了。根据上面的参数和返回值的形式,可以使用BiFunction和Consumer直接改写excutor方法:

private void excutor(List<passager> passagerList, BiFunction<passager,String,Boolean> checker, Consumer<String> printer) {
        for (passager p : passagerList) {
            if (checker.apply(p,"李四")){
                printer.accept(p.getPassagerNo());
            }
        }

    }

 

函数的引用

从上面的demo中,使用通用的函数表达式能够减少自定义函数式接口,为了进一步简化代码,lambda表达式可以改写成函数的引用的形式

函数的引用是lambda表达式的更简洁的一种写法,也是更能体现出函数式编程的一种形式,让我们更能理解lambda终归也是一个“函数的对象”。 下面我们改写一个例子:

Consumer<String> c1 = r -> System.out.printf(r);
 c1.accept("1");
 Consumer<String> c2 =System.out::printf;
 c1.accept("2");

在上面的demo中lambda表达式被我们改写成System.out::printf这个形式,等于我们把一个函数直接赋值给了一个c2对象,这里我们可以俗称(非官方)c2为java函数的一个对象,这个也结局填补了java中一个空白。


函数引用的规则

对于Java中lambda改成函数的引用要遵循一定的规则,具体可以分为下面的四种形式:

  1. 静态方法的引用

如果函数式接口的实现恰好可以通过调用一个静态方法来实现,那么就可以使用静态方法引用

Consumer<String> c1 = r -> Integer.parseInt(r);
     c1.accept("1");
     Consumer<String> c2 =Integer::parseInt;
     c1.accept("2");

2.实例方法引用

如果函数式接口的实现恰好可以通过调用一个实例方法来实现,那么就可以使用实例方法引用

 Consumer<String> ins1 = r -> System.out.print(r);
 c1.accept("1");
 Consumer<String> ins2 =System.out::print;
 c1.accept("2");

  

3.对象方法引用

抽象方法的第一个参数类型刚好是实例方法的类型,抽象方法剩余的参数恰好可以当做实例方法的参数。如果函数式接口的实现能由上面说的实例方法调用来实现的话,那么就可以使用对象方法的引用。

  Function<BigDecimal,Double> fuc1=t->t.doubleValue();
  fuc1.apply(new BigDecimal("1.025"));
  Function<BigDecimal,Double> fuc2=BigDecimal::doubleValue;
  fuc2.apply(new BigDecimal("1.025"));

  BiFunction<BigDecimal, BigDecimal, BigDecimal> func3 = (x, y) -> x.add(y);
  func3.apply(new BigDecimal("1.025"), new BigDecimal("1.254"));
  BiFunction<BigDecimal, BigDecimal, BigDecimal> func4 = BigDecimal::add;
  func4.apply(new BigDecimal("1.025"), new BigDecimal("1.254"));

4.构造方法引用

如果函数式接口的实现恰好可以通过调用一个类的构造方法来实现,那么就可以使用构造方法引用。

 Consumer<String> n1 = r ->new BigDecimal(r);
  c1.accept("1");
  Consumer<String> n2 =BigDecimal::new;
  c1.accept("2");

  

stream API的引用

Stream是处理数组和集合的API,Stream具有以下特点:

  • 不是数据结构,没有内部存储
  • 不支持索引访问
  • 延迟计算
  • 支持过滤,查找,转换,汇总等操作

对于StreamAPI的学习,首先需要弄清楚lambda的两个操作类型:中间操作和终止操作。 下面通过一个demo来认识下这个过程。

Stream st=Arrays.asList(1,2,3,4,5).stream().filter(x->{
           System.out.print(x);
           return  x>3;
       });

当我们执行这段代码的时候,发现并没有任何输出,这是因为lambda表达式需要一个终止操作来完成最后的动作。 我们修改代码:

Stream st=Arrays.asList(1,2,3,4,5).stream().filter(x->{
           System.out.print(x);
           return  x>3;
       });

   st.forEach(t-> System.out.print(t));

对应的输出结果是:

1234455

  

为什么会有这个输出呢?因为在filter函数的时候并没有真正的执行,在forEach的时候才开始执行整个lambda表达式,所以当执行到4的时候,filter输出之后,forEach也执行了,最终结果是1234455


对于Java中的lambda表达式的操作,可以归类和整理如下:

中间操作:

  • 过滤 filter
  • 去重 distinct
  • 排序 sorted
  • 截取 limit、skip
  • 转换 map/flatMap
  • 其他 peek

终止操作

  • 循环 forEach
  • 计算 min、max、count、 average
  • 匹配 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny
  • 汇聚 reduce
  • 收集器 toArray collect

常用的几个lambda

下面我们对这几个常用的lambda表达式写几个demo,首先定义公共的Student类:

public class Student {

      public Student(String stuName, int age, BigDecimal score, int clazz) {
          this.stuName = stuName;
          this.age = age;
          this.score = score;
          this.clazz = clazz;
      }

      private String stuName;
      private int age;
      private BigDecimal score;
      private int clazz;

      public String getStuName() {
          return stuName;
      }
      public void setStuName(String stuName) {
          this.stuName = stuName;
      }
      public int getAge() {
          return age;
      }
      public void setAge(int age) {
          this.age = age;
      }
      public BigDecimal getScore() {
          return score;
      }
      public void setScore(BigDecimal score) {
          this.score = score;
      }
      public int getClazz() {
          return clazz;
      }
      public void setClazz(int clazz) {
          this.clazz = clazz;
      }
  }

  List<Student> studentList = new ArrayList<>();
  studentList.add(new Student("黎  明", 20, new BigDecimal(80), 1));
  studentList.add(new Student("郭敬明", 22, new BigDecimal(90), 2));
  studentList.add(new Student("明  道", 21, new BigDecimal(65.5), 3));
  studentList.add(new Student("郭富城", 30, new BigDecimal(90.5), 4));
  studentList.add(new Student("刘诗诗", 20, new BigDecimal(75), 1));
  studentList.add(new Student("成  龙", 60, new BigDecimal(88), 5));
  studentList.add(new Student("郑伊健", 60, new BigDecimal(86), 1));
  studentList.add(new Student("刘德华", 40, new BigDecimal(81), 1));
  studentList.add(new Student("古天乐", 50, new BigDecimal(83), 2));
  studentList.add(new Student("赵文卓", 40, new BigDecimal(84), 2));
  studentList.add(new Student("吴奇隆", 30, new BigDecimal(86), 4));
  studentList.add(new Student("言承旭", 50, new BigDecimal(68), 1));
  studentList.add(new Student("郑伊健", 60, new BigDecimal(86), 1));
  studentList.add(new Student("黎  明", 20, new BigDecimal(80), 1));
  studentList.add(new Student("李连杰", 65, new BigDecimal(86), 4));
  studentList.add(new Student("周润发", 69, new BigDecimal(58), 1));
  studentList.add(new Student("徐若萱", 28, new BigDecimal(88), 6));
  studentList.add(new Student("许慧欣", 26, new BigDecimal(86), 8));
  studentList.add(new Student("陈慧琳", 35, new BigDecimal(64), 1));
  studentList.add(new Student("关之琳", 45, new BigDecimal(50), 9));
  studentList.add(new Student("温碧霞", 67, new BigDecimal(53), 2));
  studentList.add(new Student("林青霞", 22, new BigDecimal(56), 3));
  studentList.add(new Student("李嘉欣", 25, new BigDecimal(84), 1));
  studentList.add(new Student("彭佳慧", 26, new BigDecimal(82), 5));
  studentList.add(new Student("陈紫涵", 39, new BigDecimal(88), 1));
  studentList.add(new Student("张韶涵", 41, new BigDecimal(90), 6));
  studentList.add(new Student("梁朝伟", 58, new BigDecimal(74), 1));
  studentList.add(new Student("梁咏琪", 65, new BigDecimal(82), 7));
  studentList.add(new Student("范玮琪", 22, new BigDecimal(83), 1));    

  

forEach


forEach:代表循环当前的list ,下面的例子是循环打印出student的名字

studentList.stream().forEach(x -> System.out.println(x.getStuName()));

filter

根据条件过滤当前的数据,获得分数大于80的学生名称

studentList.stream().filter(t -> t.getScore().compareTo(new BigDecimal(80)) > 0).forEach(x -> System.out.println(x.getStuName()));

distinct、sorted 、group

 1.去除重复数据

studentList.stream().distinct().forEach(x -> System.out.println(x.getStuName()));

  

2.单条件排序和多条件排序

   studentList.stream().sorted(Comparator.comparing(Student::getScore)).forEach(x -> System.out.println(x.getStuName()));

   //多条件排序
  studentList.stream().sorted(Comparator.comparing(Student::getScore).thenComparing(Student::getStuName)).forEach(x -> System.out.println(x.getStuName()));

3.group 的使用

  System.out.println(studentList.stream().collect(Collectors.groupingBy(x->x.getAge(),Collectors.counting())));

limit、skip

跳过多少,取多少个元素,可以根据当前的数据进行分页

studentList.stream().skip(10).limit(5).forEach(x -> System.out.println(x.getStuName())); 

//具体的分页
int pageIndex=1;
int pageSize=5;
studentList.stream().skip((pageIndex-1)*pageSize).limit(pageSize).forEach(x -> System.out.println(x.getStuName()));

map/flatMap

map是一个转换的工具,提供很多转换的方法,mapToInt,mapToDouble

studentList.stream().map(Student::getScore).forEach(x -> System.out.println(x));

  

上面的结果是输出当前的所有同学的得分。

flatMap是一个可以把子数组的值放到数组里面, 下面的实例是把所有的名字都拆开成一个新的数组

studentList.stream().flatMap(x-> Arrays.stream(x.getStuName().split(""))).forEach(x -> System.out.println(x));

min、max、count、 average

一组常用的统计函数:

studentList.stream().max(Comparator.comparing(x -> x.getAge())).ifPresent(x-> System.out.println(x.getAge()));
  studentList.stream().min(Comparator.comparing(x -> x.getAge())).ifPresent(x-> System.out.println(x.getAge()));
  System.out.println(studentList.stream().count());
  studentList.stream().mapToDouble(x -> x.getScore().doubleValue()).average().ifPresent(x-> System.out.println(x));

  

anyMatch、noneMatch、 allMatch、 findFirst、 findAny

  • anyMatch: 操作用于判断Stream中的是否有满足指定条件的元素。如果最少有一个满足条件返回true,否则返回false。

  • noneMatch: 与anyMatch相反。allMatch是判断所有元素是不是都满足表达式。

  • findFirst: 操作用于获取含有Stream中的第一个元素的Optional,如果Stream为空,则返回一个空的Optional。若Stream并未排序,可能返回含有Stream中任意元素的Optional。

  • findAny: 操作用于获取含有Stream中的某个元素的Optional,如果Stream为空,则返回一个空的Optional。由于此操作的行动是不确定的,其会自由的选择Stream中的任何元素。在并行操作中,在同一个Stram中多次调用,可能会不同的结果。在串行调用时,都是获取的第一个元素, 默认的是获取第一个元素,并行是随机的返回。

 

System.out.println(studentList.stream().anyMatch(r -> r.getStuName().contains("伟")));
System.out.println(studentList.stream().allMatch(r -> r.getStuName().contains("伟")));
System.out.println(studentList.stream().noneMatch(r -> r.getStuName().contains("伟")));
System.out.println(studentList.stream().findFirst());
System.out.println(studentList.stream().findAny());

for (int i=0;i<10;i++)
{
   System.out.println(studentList.stream().parallel().findAny().get().getStuName());
}

reduce

对于reduce的使用,应该在js中也有接触到,但也是比较小众的功能,但使用起来功能却非常的强大,先看一个正常的demo:

Stream.of(1, 5, 10, 8).reduce((x, y) -> {
        System.out.println("x : " + x);
        System.out.println("y : " + y);
        System.out.println("x+y : " +x);

        System.out.println("--------");
        return x + y;
    });

打印结果:

x : 1
y : 5
x+y : 1
--------
x : 6
y : 10
x+y : 6
--------
x : 16
y : 8
x+y : 16
--------

可以看出:

  1. reduce是一个循环,有两个参数
  2. 第一次执行的时候x是第一个值,y是第二个值。
  3. 在第二次执行的时候,x是上次返回的值,y是第三个值
    …. 直到循环结束为止。

再修改代码如下:

//指定了初始值

Stream.of(1, 5, 10, 8).reduce(100,(x, y) -> {
	System.out.println("x : " + x);
	System.out.println("y : " + y);
	System.out.println("x+y : " +x);
	System.out.println("--------");
	return x + y;
});
x : 100
y : 1
x+y : 100
--------
x : 101
y : 5
x+y : 101
--------
x : 106
y : 10
x+y : 106
--------
x : 116
y : 8
x+y : 116
--------

  

toArray、collect

toArray和collect是两个收集器,toArray是把数据转换成数组,collect是转成其他的类型。这里就不在讨论了。

System.out.println(studentList.stream().collect(Collectors.groupingBy(x->x.getAge(),Collectors.counting())));

  

  

 


  

Java中lambda表达式详解的更多相关文章

  1. Java 8 Lambda 表达式详解

    一.Java 8 Lambda 表达式了解 参考:Java 8 Lambda 表达式 | 菜鸟教程 1.1 介绍: Lambda 表达式,也可称为闭包,是推动 Java 8 发布的最重要新特性. La ...

  2. JAVA8之lambda表达式详解

    原文:http://blog.csdn.net/jinzhencs/article/details/50748202 lambda表达式详解 一.问题 1.什么是lambda表达式? 2.lambda ...

  3. Java8 Lambda表达式详解手册及实例

    先贩卖一下焦虑,Java8发于2014年3月18日,距离现在已经快6年了,如果你对Java8的新特性还没有应用,甚至还一无所知,那你真得关注公众号"程序新视界",好好系列的学习一下 ...

  4. Lambda表达式详解(例子详解)(转自:http://blog.csdn.net/damon316/article/details/51734661)

    Lambda表达式详解(例子详解)     lambda简介 lambda运算符:所有的lambda表达式都是用新的lambda运算符 " => ",可以叫他,“转到”或者 ...

  5. 类型:.net;问题:C#lambda表达式;结果:Lambda表达式详解

    Lambda表达式详解   前言 1.天真热,程序员活着不易,星期天,也要顶着火辣辣的太阳,总结这些东西. 2.夸夸lambda吧:简化了匿名委托的使用,让你让代码更加简洁,优雅.据说它是微软自c#1 ...

  6. java中的注解详解和自定义注解

    一.java中的注解详解 1.什么是注解 用一个词就可以描述注解,那就是元数据,即一种描述数据的数据.所以,可以说注解就是源代码的元数据.比如,下面这段代码: @Override public Str ...

  7. Java中dimension类详解

    Java中dimension类详解 https://blog.csdn.net/hrw1234567890/article/details/81217788

  8. [转载]java中import作用详解

    [转载]java中import作用详解 来源: https://blog.csdn.net/qq_25665807/article/details/74747868 这篇博客讲的真的很清楚,这个作者很 ...

  9. Java中日志组件详解

    avalon-logkit Java中日志组件详解 lanhy 发布于 2020-9-1 11:35 224浏览 0收藏 作为开发人员,我相信您对日志记录工具并不陌生. Java还具有功能强大且功能强 ...

随机推荐

  1. 简析TCP的三次握手与四次分手(TCP协议头部的格式,数据从应用层发下来,会在每一层都会加上头部信息,进行封装,然后再发送到数据接收端)good

    2014-10-30 分类:理论基础 / 网络开发 阅读(4127) 评论(29)  TCP是什么? 具体的关于TCP是什么,我不打算详细的说了:当你看到这篇文章时,我想你也知道TCP的概念了,想要更 ...

  2. Dependency Injection 筆記 (4)

    续上集未完的相关设计模式... (本文摘自電子書:<.NET 依賴注入> Composite 模式 延续先前的电器比喻.现在,如果希望 UPS 不只接计算机,还要接电风扇.除湿机,可是 U ...

  3. .NET错误:未找到类型或命名空间名称

    现象:编译项目时提示未找到类型或命名空间名称"... " 解决方法:如果是未找到类型,检查是否引用了类型所在的命名空间,使用using指令:如果是未找到命名空间,那么检查是否引用了 ...

  4. 从零开始的Wordpress个人博客搭建

    0x00前言 在博客园写了有一年的博客了,也想换换新口味,wordpress的众多的主题和个性化设置非常符合我的喜好,所以捣鼓了一天也算是把它搭好了. 直接在服务器上搭建wordpress还需要配置m ...

  5. asp.net core 系列之Response caching 之 Distributed caching(3)

    这篇文章讲解分布式缓存,即 Distributed caching in ASP.NET Core Distributed caching in ASP.NET Core 分布式缓存是可以在多个应用服 ...

  6. vim 列编辑模式

    vim 列编辑模式 标签: vim 视窗模式 列编辑模式 vim 列编辑模式 例子:给列批量添加前缀.后缀.修改字段 vim 列编辑模式 vim 有三种编辑模式,命令模式.输入模式.视窗模式,我们常用 ...

  7. http协议内容展示以及如何用telnet发送请求

    1.http协议组成: 报文首部:状态行(请求行) 请求首部字段 通用字段 其他信息 空行 报文主体 GET请求头: GET /test.php?a=1 HTTP/1.1 Host: localhos ...

  8. Storm 学习之路(二)—— Storm核心概念详解

    一.Storm核心概念 1.1 Topologies(拓扑) 一个完整的Storm流处理程序被称为Storm topology(拓扑).它是一个是由Spouts 和Bolts通过Stream连接起来的 ...

  9. php7中异常

    php7中新增异常错误处理 在PHP7之前的版本,对于一些错误异常是没有办法捕获的. php7中新增throwable接口,可以用来捕获一些错误 Exception,Error这实现了Throwabl ...

  10. PATB 1015. 德才论 (25)

    1015. 德才论 (25) 比较函数折腾好久,最后还因为cout,printf的区别而超时,超时是因为cout输出效率低. 时间限制 200 ms 内存限制 65536 kB 代码长度限制 8000 ...