当前位置: 首页 > news >正文

Lambda表达式(Java)

我们都知道Java 8 支持Lambda表达式,但是平时开发中也很难用到这个东东,但是作为专业的程序员,技多不压身(其实我是在学Kotlin中,发现里面大量的运用到了Lambda表达式,看的我一脸懵逼,所以只好来学习学习,不然怎么出去装逼,怎么骚浪贱)。好,收,让我们来看看Lambda的前世今生。

一、Lambda到底是什么鬼?

说到Lambda,不得不说到函数式编程,说到函数式编程不得不说到λ演算,λ演算是数学家Church提出的。λ演算中最关键的要素就是函数被当作变量处理能够参与运算。(看到这里我社会大佬,才是这些数学家好嘛,打call,打call)。

继续继续,我们来看看Lambda的官方定义。

A lambda expression is like a method: it provides a list of formal parameters and a body - an expression or block - expressed in terms of those parameters. 翻译如下:Lambda表达式类似于一种方法:它提供了一个形参列表和一个表达式或用这些形参的代码块。

我去。反正官方的解释我是一点都没有看懂。个人觉得Lambda表达式更像是“语法糖”, 不仅使代码变的更加简洁,还是代码更有阅读性。说了这么多,无码无真相。让我们来探讨探讨Lambda表达式。开车开车,请上车~~~

1.1 Lambda表达式分析

那在Java中如果我们要给一个变量赋值,我们一般的操作如下:

 int a = 1;
 String b = "abc";
 boolean c = true;
复制代码

但是如果我们根据λ演算要素,将函数当做变量处理,将函数赋值给一个变量。

 addMethod = public void add(int a,int b){
	 return a+b;
 }
复制代码

哇,这代码真是蛇皮,这与我们实际编码中书写的Lambda表达式代码完全不一样好嘛,对啊!作为一个程序员,我们万事都要讲究一个优雅好嘛,不优雅,你就是要我命,你造否?所以为了代码简洁与优雅。Sun公司的大佬们移除了一些不必要的声明。我们一步一步的来分析。

1.1.1 去除函数修饰符
addMethod = void add(int a,int b){
	return a+b;
}
复制代码

上面操作,我们发现了我们移除了public 修饰符,设想,如果我们需要将一个函数赋值给一个变量,那么起访问权限控制的,肯定是变量的修饰符,所以说函数的修饰符可以移除。我们继续往下走。

1.1.2 去除函数名称
addMethod = void (int a,int b){
	return a+b;
}
复制代码

去除函数名称,主要的原因是,既然我已将函数赋值给变量了。那所有的操作都是与这个变量相关,所以函数的名称是可以不要的。对实际的操作没有影响。

1.1.3 去除函数返回类型
addMethod = (int a,int b){
	return a+b;
}
复制代码

为什么会去掉函数返回类型呢?函数内部到底有没有返回值,你觉得编译器心里难道没有一点B数嘛,所以我们完全可以省略这个返回类型。

1.1.4 去除参数类型
addMethod = (a, b){
	return a+b;
}
复制代码

去掉参数类型,同1.1.2原因,编译器知道传入的参数类型。所以可以省略。

1.1.5 参数与函数体区分
addMethod = (a, b)->{
	return a+b;
}
复制代码

为了更好的将参数与函数体分开。Java中的语法是使用**"->"**来区分,这些代码看起来更简洁。

1.2 Lambda表达式对应变量类型

到现在为止,我们已经成功的将一个函数赋值给一个变量。这个时候我们需要理解的是这个变量的具体类型是什么,我们都知道我们声明变量的时候,前方都会声明其类型。那这个变量的类型到底是什么呢?

在Java8中,Lambda表达式所对应的类型都是接口,Lambda所表达的就是那个接口对应函数的具体代码。可能大家这里不是很清楚。概念还是很模糊。我们看一下下面的例子,我相信大家就能清楚的明白了。

 //声明一个Person接口
  public interface Person {
        void smile(int time);
    }
 //在java8以前我们实现接口
 Person p = new Person() {
            @Override
            public void smile(int time) {
                System.out.println("我笑了" + time + "秒");
            }
        };
 //通过Lambda实现接口
Person p = time -> System.out.println("我笑了" + time + "秒");
复制代码

观察上述代码,我们发现。通过Lambda表达式来实现接口,相比之前的实现接口的方式,Lambda表达式是代码更加清晰,且代码量较少。

1.2.1 Lambda表达式表达的方法个数。

说到这里,我相信大家都有一个疑问,就是假如Person接口中不止有一个smile方法,还有其他方法呢?那这个时候我该怎么用Lambda表达式来表示呢? 这个问题其实在最初的Lambda表达式分析那里已经侧面表现了。既然是将函数赋值给变量,那么一个变量对应的函数就只能有且只有一个,不然怎么知道你在调用的时候具体调用的哪个函数呢?你说呢,兄die.

说到这里,有的兄die就会说,我怎么才能让我的接口有且只有一个方法呢?,那就引出我们的**@FunctionalInterface**注解,直接通过英文直译的话意思是“函数式接口”。我去,什么鬼,不了解其意思,我们就直接看源码吧。

An informative annotation type used to indicate that an interface
 * type declaration is intended to be a <i>functional interface</i> as
 * defined by the Java Language Specification.
 *
 * Conceptually, a functional interface has exactly one abstract
 * method.  Since {@linkplain java.lang.reflect.Method#isDefault()
 * default methods} have an implementation, they are not abstract.  If
 * an interface declares an abstract method overriding one of the
 * public methods of {@code java.lang.Object}, that also does
 * <em>not</em> count toward the interface's abstract method count
 * since any implementation of the interface will have an
 * implementation from {@code java.lang.Object} or elsewhere.
 
 
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
复制代码

这里我截取了部分FunctionalInterface的部分注释,@FunctionalInterface 注解主要是用于修饰接口。且修饰的接口中有且只有一个方法,到这里我们就明白了,那么我可以用**@FunctionalInterface**注解来修饰我们的接口啦。这个接口是不是很牛逼啊。

二、Lambda表达式语法

在了解Lambda的由来,以及表现形式后,我们现在可以来了解一下Lambda的语法啦,知道原理,当然也必须知道怎么使用,对吧。(授人鱼不如授人以渔,你说是不是这个道理,哈哈哈,秀秀我的语文功底)。

  • Lambad表达式总是在花括号中;
  • 其参数(如果有的话)在 —>之前声明(参数类型可以省略);
  • 函数体(如果存在的话)在—>之后声明;

基本语法:

(formal parameter list) -> { expression or statements}
复制代码

2.1 Lambda表达式例子

Lambda 表达式实际上是一种匿名方法实现(有的人说是匿名内部类实现,看个人怎么理解了),Lambda表达式的求值不会导致函数体的执行,而是在调用该方法后发生,下面是一些Lambda表达式的例子:

	    () -> {}                //无参,空的函数体⑤
        () -> 42                //无参,函数体返回42⑥
        () -> null              // 无参,函数体返回null⑦
        () -> { return 42; }    //  无参,函数体返回42
        () -> { System.gc(); }  // 无参,执行语句,函数体返Void
        () ->  System.gc()  // 无参,执行语句,函数体返Void

        () -> {                 // 带返回语句的完整函数体
            if (true) return 12;
            else {
                int result = 15;
                for (int i = 1; i < 10; i++)
                result *= i;
                return result;
            }
        }

        (int x) -> x+1              // 声明类型的单个参数⑧
        (int x) -> { return x+1; }  // 声明类型的单个参数
        (x) -> x+1                  // 隐藏参数类型的单个参数③
        x -> x+1                    // 花括号可选④
        

        (String s) -> s.length()      // 声明类型的单个参数
        (Thread t) -> { t.start(); }  // 声明类型的单个参数
        s -> s.length()               // 隐藏参数类型的单个参数
        t -> { t.start(); }           // 隐藏参数类型的单个参数⑩

        (int x, int y) -> x+y  // 声明参数类型的多个参数①
        (x, y) -> x+y          // 隐藏参数类型的多个参数②
        (x, int y) -> x+y    // 错误:不能同时单个的声明类型或隐藏类型
        (x, final y) -> x+y  // 错误:没有推断类型的修饰符
复制代码

相信大家从上面例子中可以看出一下语法规则,那我们将这语法规则分为两个部分Lambda参数规则lambda函数体规则

2.2 Lambda参数规则

  • 参数列表必须是以逗号分隔的形式。见①
  • 参数列表的参数类型是可选的,如果未指定参数类型,将从上下文进行推断。见②
  • 参数列表参数的个数大于2则必须用小括号括起来 。见②
  • 参数列表参数的个数为1,则小括号可以省略。见⑩
  • 如果函数体中,没有使用参数,那么必须指定空括号。见⑤⑥⑦

2.3 Lambda函数体规则

  • 如果函数体只包含单条表达式或语句,就不需要使用大括号。见⑧⑨

三、 Lambda使用注意事项

现在我们基本了解了Lambda的使用方式,已经使用场景,那么现在我们看看在Lambda使用中需要注意的问题。

3.1 Lambda访问变量为final类型

我们都知道在匿名内部类中访问变量时,需要将变量修饰为final类型。因为如果该该内部类运行在另一线程中,必将会出现线程安全的问题。(这里肯定有很多读者就会提出这样一个问题,那这个内部类修饰变量为final和我们Lambda表达式有毛关系啊?)请听我细细道来。

如果你仔细阅读了该文,你应该知道Lambda表达式相当于声明匿名内部类。只是换了一种表达方式而已。那么本质是一样的。所以在访问变量的时候我们也需要注意这些问题。看下面代码。

    public interface A {
        void fuck();
    }

	public void test() {
        int count = 10;
        A a = () -> { count++};//错误 count 类型为final类型。
    }
复制代码

观察上述代码,大家肯定会疑惑,为什么我这里没有申明count为final类型。因为在java8中,在编译器编译的时候会将Lambda表达式访问的变量,自动声明为final类型。有点语法糖的味道。

总结

通过上文分析。我们发现Lambda表达式不仅使程序变得更加简洁。还能节省程序员的代码量,节省了很多码代码的时间。再有一个我觉得更重要的是,这样代码看起来很骚,你说是吗?

参考

站在巨人的肩膀上。可以看得更远。感谢下列博主,和官方文档。人类的知识是大家的。

Lambda 表达式有何用处?如何使用

Oracle Lambda官方文档

最后,附上我写的一个基于Kotlin 仿开眼的项目SimpleEyes(ps: 其实在我之前,已经有很多小朋友开始仿这款应用了,但是我觉得要做就做好。所以我的项目和其他的人应该不同,不仅仅是简单的一个应用。但是,但是。但是。重要的话说三遍。还在开发阶段,不要打我),欢迎大家follow和start

相关文章:

  • 区块链将会怎样颠覆Google、Amazon、Facebook和Apple?
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • Windows Server 2012的服务管理自动化 -启动类型设置,手动启动还是自动启动
  • JVM 组成以及各部分作用
  • PHP 500报错的快速解决方法
  • windows网络模型之完成端口(CompletionPort)详解 (转)
  • [转]区块链代码快速学习实践
  • 《王牌特工2》情景再现,Youbionic推出可穿戴式机械手
  • 扩展GenericServlet实现Servlet程序 学习笔记
  • HTTP协议-HTTP响应报文
  • 《以太坊白皮书》笔记(3)—— 以太坊介绍. 下
  • zookeeper的开启启动
  • js拦截全局ajax请求
  • Redis Exception: Exceeded timeout of 00:00:03
  • oracle安装注意
  • [译] 怎样写一个基础的编译器
  • [译]CSS 居中(Center)方法大合集
  • css系列之关于字体的事
  • JS笔记四:作用域、变量(函数)提升
  • mysql 数据库四种事务隔离级别
  • Promise面试题2实现异步串行执行
  • Python 基础起步 (十) 什么叫函数?
  • ucore操作系统实验笔记 - 重新理解中断
  • use Google search engine
  • 阿里云爬虫风险管理产品商业化,为云端流量保驾护航
  • 安装python包到指定虚拟环境
  • 从重复到重用
  • 诡异!React stopPropagation失灵
  • 跨域
  • 判断客户端类型,Android,iOS,PC
  • 前端 CSS : 5# 纯 CSS 实现24小时超市
  • 嵌入式文件系统
  • 融云开发漫谈:你是否了解Go语言并发编程的第一要义?
  • 找一份好的前端工作,起点很重要
  • 从如何停掉 Promise 链说起
  • ​LeetCode解法汇总2670. 找出不同元素数目差数组
  • #stm32驱动外设模块总结w5500模块
  • (+3)1.3敏捷宣言与敏捷过程的特点
  • (14)学习笔记:动手深度学习(Pytorch神经网络基础)
  • (3)选择元素——(14)接触DOM元素(Accessing DOM elements)
  • (4)STL算法之比较
  • (附源码)ssm捐赠救助系统 毕业设计 060945
  • (收藏)Git和Repo扫盲——如何取得Android源代码
  • (正则)提取页面里的img标签
  • (转)C#调用WebService 基础
  • (总结)Linux下的暴力密码在线破解工具Hydra详解
  • ***微信公众号支付+微信H5支付+微信扫码支付+小程序支付+APP微信支付解决方案总结...
  • .[hudsonL@cock.li].mkp勒索加密数据库完美恢复---惜分飞
  • .NET 6 Mysql Canal (CDC 增量同步,捕获变更数据) 案例版
  • .NET CORE使用Redis分布式锁续命(续期)问题
  • .NET gRPC 和RESTful简单对比
  • .net refrector
  • /bin、/sbin、/usr/bin、/usr/sbin
  • @LoadBalanced 和 @RefreshScope 同时使用,负载均衡失效分析
  • [ IOS ] iOS-控制器View的创建和生命周期