一、函数式编程思想概述

  在数学中,函数就是有输入量、输出量的一套计算方案,就是“拿什么东西做什么事情”。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法—— 强调做什么,而不是以什么形式做。

  面向对象的思想:做一件事情,找一个能解决这个事情的对象,调用对象的方法完成事情。

  函数式编程思想:只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程。

二、冗余的 Runnable 代码

  传统写法

    当需要启动一个线程去完成任务时,通常会通过  java.lang.Runnable  接口来定义任务内容,并使用 java.lang.Thread 类来启动线程。

    Demo:

 public class Demo01Runnable {
public static void main(String[] args) {
// 匿名内部类
Runnable task = new Runnable() {
@Override
public void run() { // 覆盖重写抽象方法
System.out.println("多线程任务执行!");
}
};
new Thread(task).start(); // 启动线程
}
}

  面向对象的思想:首先创建一个 Runnable 接口的匿名内部类对象来指定任务内容,再将其交给一个线程来启动。

  代码分析

    对于 Runnable 的匿名内部类用法:可以分析出几点内容:

      •  Thread 类需要 Runnable 接口作为参数,其中的抽象方法 run 方法是用来指定线程任务内容的核心
      • 为了指定 run 的方法体,必须需要 Runnable 接口的实现类;
      • 为了省去定义一个 Runnable 实现类的麻烦,必须使用匿名内部类
      • 必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值必须再写一遍,且不能写错
      • 实际上,似乎只有方法体才是关键所在

三、编程思想转换

  对于上面的案例来说,我们真的希望创建一个匿名内部类对象吗?并不需要,只是为了做这件事情不得不创建一个对象。

  真正希望做的是:将 run 方法体内的代码传递给 Thread 类。

  传递一段代码——这才是真正的目的。而创建对象只是受限于面向对象而不得不采取的一种手段方式。

  如果我们将关注点从“怎么做”回归到“做什么” 的本质上,就会发现只要能够更好地达到母的,过程与形式其实并不重要。

四、Lambda 的更优写法

  JDK8 之后,有了全新的语法,上面的 Runnable 接口的匿名内部类写法可以通过更简单的 Lambda 表达式达到等效:

public class Demo02LambdaRunnable {
public static void main(String[] args) {
new Thread(() ‐> System.out.println("多线程任务执行!")).start(); // 启动线程
}
}

  这段代码和上面的执行效果是一样的。从代码的语义上可以看出:启动了一个线程,而线程任务的内容以一种更简洁的形式被指定。

  优点:不再有“必须创建接口对象” 的束缚,不再有 “抽象方法覆盖重写” 的负担。

五、匿名内部类

  Lambda 表达式是怎么做到的呢?核心代码如下:

() ‐> System.out.println("多线程任务执行!")

   为了理解 Lambda 的语义,先来看一下以前的实现方式。

  使用实现类

    要启动一个线程,需要创建一个 Thread 类的对象并调用 start 方法,而为了指定线程执行的内容,需要调用 thread 类的构造方法:

public Thread(Runnable target)

     为了获取 Runnable 接口的实现对象,可以为该接口定义一个实现类 RunnableImplement:

 public class RunnableImpl implements Runnable {
@Override
public void run() {
System.out.println("多线程任务执行!");
}
}

    然后创建该实现类的对象作为 Thread 类的构造参数:

 public class Demo03ThreadInitParam {
public static void main(String[] args) {
Runnable task = new RunnableImpl();
new Thread(task).start();
}
}

  使用匿名内部类

    这个 RunnableImpl 类只是为了实现 Runnable 接口存在的,而且仅被使用了唯一一次,所以使用匿名内部类的语法即可省去该类的单独定义,即匿名内部类:

 public class Demo04ThreadNameless {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程任务执行!");
}
}).start();
}
}

  匿名内部类的好处与弊端

    好处:匿名内部类可以省去实现类的定义;

    缺点:匿名内部类语法复杂

  语义分析:

    经分析上述代码中的语义,Runnable 接口只有一个 run 方法的定义:

public abstract void run();

    即制定了一种做事情的方案(本质为一个函数):

      •  无参数:不需要任何条件即可执行该方案
      • 无返回值:该方案不产生任何结果
      • 代码块(方法体):该方案的具体执行步骤

    同样的语义体现在 Lambda 语法中,会更加简单:

() ‐> System.out.println("多线程任务执行!")
    •   前面的一对小括号即 run 方法的参数(无),代表不需要任何条件;
    • 中间的一个箭头代表前面的参数传递给后面的代码
    • 后面的输出语句即业务逻辑代码

六、Lambda 的标准格式

  Lambda 省去面向对象的繁琐,格式由 三个部分 组成:

    •  一些参数
    • 一个箭头
    • 一段代码

  Lambda 表达式的标准格式为:

(参数类型 参数名称) ‐> { 代码语句 }

  格式说明

    •  小括号内的语法与传统方法参数列表一致;无参数则留空;多个参数则用逗号分隔。
    • -> 是新引入的语法格式,代表指向动作。
    • 大括号的语法与传统方法要求基本一致。

  Lambda 表达式:是可推导,可以省略,凡是根据上下文推导出来的内容,都可以省略书写。

  可以省略的内容:

    1. (参数列表):括号中参数列表的数据类型,可以省略不写

    2. (参数列表):括号中的参数如果只有一个,那么类型和()都可以省略

    3. {一些代码}:如果{}中的代码只有一行,无论是否有返回值,都可以省略({},return,分号)

      注意:要省略{},return,分号必须一起省略

七、Lambda 的使用前提

  Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:
  1. 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。 

    无论是JDK内置的 Runnable 、 Comparator 接口还是自定义的接口,只有当接口中的抽象方法存在且唯
时,才可以使用Lambda。

  2. 使用Lambda必须具有上下文推断
    也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。

  Tips:有且仅有一个抽象方法的接口,称为“函数式接口”。

八、Lambda 的练习

  1、使用 Lambda 的标准格式(无参无返回值)

    题目:给定一个厨子 Cook 接口,内含唯一的抽象方法 makeFood ,且无参数、无返回值。如下:

 public interface Cook {
void makeFood();
}

    传统写法

  public static void main(String[] args) {
//调用invokeCook方法,参数是Cook接口,传递Cook接口的匿名内部类对象
invokeCook(new Cook() {
@Override
public void makeFood() {
System.out.println("吃饭了");
}
}); //定义一个方法,参数传递Cook接口,方法内部调用Cook接口中的方法makeFood
public static void invokeCook(Cook cook){
cook.makeFood();
}

    使用 Lambda 表达式:

  public static void main(String[] args) {

         //使用Lambda表达式,简化匿名内部类的书写
invokeCook(()->{
System.out.println("吃饭了");
}); //优化省略Lambda
invokeCook(()-> System.out.println("吃饭了"));
} //定义一个方法,参数传递Cook接口,方法内部调用Cook接口中的方法makeFood
public static void invokeCook(Cook cook){
cook.makeFood();
}

  2、Lambda 的参数和返回值

    题目:使用数组存储多个 Person 对象对数组中的Person对象使用Arrays的sort方法通过年龄进行升序排序。

    下面演示  java.util.Comparator<T> 接口的使用场景代码,其中的抽象方法定义为:

public abstract int compare(T o1, T o2);

    当需要对一个对象数组进行排序时, Arrays.sort 方法需要一个 Comparator 接口实例来指定排序的规则。假设有一个 Person 类,含有 String name 和 int age 两个成员变量:

 public class Person {
private String name;
private int age;
// 省略构造器、toString方法与Getter Setter
}

    传统写法

 public class Demo06Comparator {
public static void main(String[] args) {
// 本来年龄乱序的对象数组
Person[] array = {
new Person("A", 19),
new Person("B", 18),
new Person("C", 20) };
// 匿名内部类
Comparator<Person> comp = new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() ‐ o2.getAge();
}
};
Arrays.sort(array, comp); // 第二个参数为排序规则,即Comparator接口实例
for (Person person : array) {
System.out.println(person);
}
}
}

   Lambda 写法:

 public class Demo07ComparatorLambda {
public static void main(String[] args) {
Person[] array = {
new Person("A", 19),
new Person("B", 18),
new Person("C", 20) };
Arrays.sort(array, (Person a, Person b) ‐> {
return a.getAge() ‐ b.getAge();
});
for (Person person : array) {
System.out.println(person);
}
}
}

  3、使用 Lambda 标准格式(有参有返回值)

    题目:给定一个计算器 Calculator 接口,内含抽象方法 calc 可以将两个int数字相加得到和值:

 public interface Calculator {
int calc(int a, int b);
}

    标准格式:

 public class DemoCalculator {
public static void main(String[] args) {
//调用invokeCalc方法,方法的参数是一个接口,可以使用匿名内部类
invokeCalc(10, 20, new Calculator() {
@Override
public int calc(int a, int b) {
return a+b;
}
}); /*
定义一个方法
参数传递两个int类型的整数
参数传递Calculator接口
方法内部调用Calculator中的方法calc计算两个整数的和
*/
public static void invokeCalc(int a,int b,Calculator c){
int sum = c.calc(a,b);
System.out.println(sum);
}
}

    Lambda 写法:

 public class DemoCalculator {
public static void main(String[] args) { //使用Lambda表达式简化匿名内部类的书写
invokeCalc(120,130,(int a,int b)->{
return a + b;
}); //优化省略Lambda
invokeCalc(120,130,(a,b)-> a + b);
} /*
定义一个方法
参数传递两个int类型的整数
参数传递Calculator接口
方法内部调用Calculator中的方法calc计算两个整数的和
*/
public static void invokeCalc(int a,int b,Calculator c){
int sum = c.calc(a,b);
System.out.println(sum);
}
}

Java 之 lambda 表达式的更多相关文章

  1. Java 8 Lambda表达式

    Java 8 Lambda表达式探险 http://www.cnblogs.com/feichexia/archive/2012/11/15/Java8_LambdaExpression.html 为 ...

  2. 深入浅出 Java 8 Lambda 表达式

    摘要:此篇文章主要介绍 Java8 Lambda 表达式产生的背景和用法,以及 Lambda 表达式与匿名类的不同等.本文系 OneAPM 工程师编译整理. Java 是一流的面向对象语言,除了部分简 ...

  3. Java 8 Lambda表达式10个示例【存】

    PS:不能完全参考文章的代码,请参考这个文件http://files.cnblogs.com/files/AIThink/Test01.zip 在Java 8之前,如果想将行为传入函数,仅有的选择就是 ...

  4. Java 8 Lambda 表达式

    Lambda 是啥玩意 简单来说,Lambda 就是一个匿名的方法,就这样,没啥特别的.它采用一种非常简洁的方式来定义方法.当你想传递可复用的方法片段时,匿名方法非常有用.例如,将一个方法传递给另外一 ...

  5. Java 8 lambda表达式示例

    例1.用lambda表达式实现Runnable 我开始使用Java 8时,首先做的就是使用lambda表达式替换匿名类,而实现Runnable接口是匿名类的最好示例.看一下Java 8之前的runna ...

  6. Java 8 Lambda 表达式详解

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

  7. 用Java 8 Lambda表达式实现设计模式:命令模式

    在这篇博客里,我将说明如何在使用 Java 8 Lambda表达式 的函数式编程方式 时实现 命令 设计模式 .命令模式的目标是将请求封装成一个对象,从对客户端的不同类型请求,例如队列或日志请求参数化 ...

  8. Java基础学习总结(44)——10个Java 8 Lambda表达式经典示例

    Java 8 刚于几周前发布,日期是2014年3月18日,这次开创性的发布在Java社区引发了不少讨论,并让大家感到激动.特性之一便是随同发布的lambda表达式,它将允许我们将行为传到函数里.在Ja ...

  9. 02、Java的lambda表达式和JavaScript的箭头函数

    前言 在JDK8和ES6的语言发展中,在Java的lambda表达式和JavaScript的箭头函数这两者有着千丝万缕的联系:本次试图通过这篇文章弄懂上面的两个"语法糖". 简介 ...

  10. Java 8 Lambda表达式学习和理解

    Java 8 Lambda表达式和理解 说明:部分资料来源于网络 时间:20190704 Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性.Lambda 允许把函数作为一 ...

随机推荐

  1. JPA 基础

    JPA 基础 数据库驱动 ==> JDBC 规范 ==> ORM 框架 ==> JPA 规范 ==> spring-data-jpa ORM 思想 JPA 的使用步骤 jpa ...

  2. Layer获取iframe的dom元素及调用iframe页的js方法

    1. 父页面点击第一个按钮触发,获取子页面中的body元素,调用子页面中定义的js方法 yes : function(index,layero){ //获取iframe的body元素 var body ...

  3. MacOS更改启动台(launchpad)图标大小

    原文链接:https://www.jianshu.com/p/18224d191bd2作者:麦壳君 小麦有点强迫症,觉得 Mac 启动台(Launchpad)里面系统默认的应用程序图标太大,所以想让它 ...

  4. java 8 学习二(Lambda表达式)

    粗略的概括:lambda表达式主要用来实现“函数接口”中“唯一”的抽象方法用的. 他的特殊版有 方法引用,构造函数引用,用对应的接口实例接收即可. 可以把Lambda表达式理解为简洁地表示可传递的匿名 ...

  5. ESA2GJK1DH1K基础篇: 阿里云物联网平台: 云平台显示单片机采集的温湿度数据,控制设备继电器(基于GPRS模块,AT指令TCP_MQTT通信)

    实现的功能 上一节是使用的Wi-Fi模块连接的阿里云平台,这节呢咱用GPRS连接. 阿里云的设备配置还是按照上一节的,有个温度,有个湿度. 测试 一,打开单片机程序 二,根据自己的修改,然后下载进去单 ...

  6. T2:中间值(median)———2019.10.15

    代码: #include <bits/stdc++.h> int ri() { , f = ; ; ) + (x << ) - ' + c; return x * f; } ; ...

  7. 常用STL使用指北

    常用STL使用指北 set和multiset set和multiset都是基于红黑树(显然是一个二叉搜索树)的STL. 定义 我们可以使用(multi)set<元素类型>名称来定义一个(m ...

  8. 分布式事务解决方案(一) 2阶段提交 & 3阶段提交 & TCC

    参考文档:http://blog.jobbole.com/95632/https://yq.aliyun.com/articles/582282?spm=a2c4e.11163080.searchbl ...

  9. 2018-2019-2 网络对抗技术 20165230 Exp7 网络欺诈防范

    实验目的 本实践的目标理解常用网络欺诈背后的原理,以提高防范意识,并提出具体防范方法. 实验内容 简单应用SET工具建立冒名网站 ettercap DNS spoof 结合应用两种技术,用DNS sp ...

  10. 第十五节:Asp.Net Core中的各种过滤器(授权、资源、操作、结果、异常)

    一. 简介 1. 说明 提到过滤器,通常是指请求处理管道中特定阶段之前或之后的代码,可以处理:授权.响应缓存(对请求管道进行短路,以便返回缓存的响应). 防盗链.本地化国际化等,过滤器用于横向处理业务 ...