简介

lambda表达式,又称闭包(Closure)或称匿名方法(anonymous method)。将Lambda表达式引入JAVA中的动机源于一个叫“行为参数”的模式。这种模式能够解决需求变化带来的问题,使代码变得更加灵活。在JAVA8之前,参数模式十分啰嗦。Lambda表达式通过精简的方式使用行为模式克服了这个缺点

解决什么问题

  • 传递行为。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理,变成了一等公民。解决重复的代码片段代码包裹问题。
  • 内置抽象行为。把常见的行为定义成接口,可以直接使用,减少重复思考和代码。都在java.util.function包里
  • 更少的代码。通过类型推断,方法引用,可以让代码更优雅。

背后思想

  • 函数式编程

内容说明

作用域

this

在内部类中,this指向当前内部类对象自己,而在lambda表达式中,this指向的是表达式外部的类对象。

public class ScopeTest {
@Test
public void test_scope(){ Runnable runnable = () -> {
this.print();
};
runnable.run(); }
private void print(){
System.out.println("I can print");
}
}

final

labmda表达式使用外部的变量时,不可修改,默认定义成final

函数式接口

只有一个抽象方法的接口我们就称之为功能性接口,又简称 SAM 类型,即 Simple Abstract Method。会写上注释

@FunctionalInterface
//用这个注解来表示功能性接口
public interface Consumer<T> { /**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}

常见的内置函数如下:

name function
java.lang.Runnable 执行动作
java.util.function.Predicate <T> 接收T对象并返回boolean
java.util.function.Consumer<T> 接收T对象,不返回值
java.util.function.Function<T,R> 接收T对象,返回R对象
java.util.function.Supplier<T> 提供T对象(例如工厂),不接收值

方法引用

方法引用有很多种,它们的语法如下:

  • 静态方法引用:ClassName::methodName
  • 实例上的实例方法引用:instanceReference::methodName
  • 超类上的实例方法引用:super::methodName
  • 类型上的实例方法引用:ClassName::methodName
  • 构造方法引用:Class::new
  • 数组构造方法引用:TypeName[]::new
    @Test
public void test_instance(){
Set<String> girls = new HashSet<>(); Set<String> names = new HashSet<>();
names.stream()
//实例::methodName
.filter(girls::contains)
.collect(Collectors.toList());
}
@Test
public void test_this(){
Set<String> names = new HashSet<>();
names.stream()
//this::methodName
.filter(this::hasAuth)
.collect(Collectors.toList());
} private boolean hasAuth(String authKey){
return true;
}

类型推断

    Map<String,Person> map = new HashMap<>();

    map.forEach((String name,Person person)->{
person.fly();
System.out.println(name+":"+person);
}); map.forEach((name,person)->{
// 无须判断类型,自动根据上下文推断
person.fly();
System.out.println(name+":"+person);
});

实践

最佳实践

消灭代码片段

    /**
* 正常的代码
*/
@Test
public void test_person(){
CnResult<Person> result = null;
try {
// 只有这里取值是不同的,其他的处理是一样的,包括try,catch,打日志,定义异常等
Person entity = this.getPerson(); result = CnResult.success(entity); }catch (CnException e){
logger.error(e.getMessage(),e);
result = CnResult.error(e.getErrorCode(),e.getMessage()); }
catch (Exception e) {
logger.error(e.getMessage(),e);
result = CnResult.error("1-1-1-1",e.getMessage());
}
Assert.assertNotNull(result);
} @Test
public void test_animal(){
CnResult<Animal> result = null;
try {
// 只有这里取值是不同的,其他的处理是一样的,包括try,catch,打日志,定义异常等
Animal entity = this.getAnimal(); result = CnResult.success(entity); }catch (CnException e){
logger.error(e.getMessage(),e);
result = CnResult.error(e.getErrorCode(),e.getMessage()); }
catch (Exception e) {
logger.error(e.getMessage(),e);
result = CnResult.error("1-1-1-1",e.getMessage());
}
Assert.assertNotNull(result);
}
    /**
* lambda代码
*/
@Test
public void test_lambda(){
//屏蔽所有细节,只把获取对象的逻辑传递进去
CnResult<Person> person = WapperUtils.wapper(this::getPerson);
Assert.assertNotNull(person); CnResult<Animal> animal = WapperUtils.wapper(this::getAnimal);
Assert.assertNotNull(animal);
} public class WapperUtils {
private static final Logger logger = LoggerFactory.getLogger(WapperUtils.class); /**
* 包裹
*
* @param supplier
* @param <T>
* @return
*/
public static <T> CnResult<T> wapper(Supplier<T> supplier){
try {
T entity = supplier.get();
CnResult<T> cnResult = CnResult.success(entity);
return cnResult;
}catch (CnException e){
logger.error(e.getMessage(),e);
return CnResult.error(e.getErrorCode(),e.getMessage());
}
catch (Exception e) {
logger.error(e.getMessage(),e);
return CnResult.error("1-1-1-1",e.getMessage());
}
}
}
  • 只要是代码片段,找到变点化,抽象成lambda,把固定的代码统一封装,就可以使用行为的复用,减少代码片段和代码包裹。

明确语义

    @Test
public void test_first() {
List<Person> persons = new ArrayList<>();
persons.stream()
/**
* 没有明确的语义。需要根据过程计算推理得出结果,也不清楚背后的业务和场景
*
*/
.filter(person -> person.getAge() >= 18)
.collect(Collectors.toList());
} @Test
public void test_second() {
List<Person> persons = new ArrayList<>();
persons.stream()
.filter(person -> {
/**
* 如果职责变更,得修改代码结构,而且代码越来越难理解
*/
if (person.getAge() >= 18) {
return true;
} if (person.getName().startsWith("庄")) {
return true;
}
return false; })
.collect(Collectors.toList());
} @Test
public void test_third() {
List<Person> persons = new ArrayList<>();
persons.stream()
/**
* 随着业务变更需要不断添加filter
*/
.filter(person -> person.getAge() >= 18)
.filter(person -> person.getName().startsWith("庄"))
.collect(Collectors.toList());
} @Test
public void test_fourth() {
List<Person> persons = new ArrayList<>();
persons.stream()
/**
* 随着业务变更需要不断添加filter
*/
.filter(Person::isAdult)
.filter(Person::belongToZhuang)
.collect(Collectors.toList());
} @Test
public void test_final() {
List<Person> persons = new ArrayList<>();
/**
* 有明确的语义,不用再面向细节加工一下,而且也方便后面的扩展
*/ persons.stream()
.filter(Person::hasAuth)
.collect(Collectors.toList());
}

最佳反例

随意取名

    @Test
public void test_name_bad(){
List<Person> persons = new ArrayList<>();
/**
* lambda一多的时候,没有明确的参数,计算和理解起来非常吃力。
*/
persons.stream()
.filter(e->e.getAge()>18)
.map(e->e.getName())
.filter(e->e.startsWith("庄"))
.collect(Collectors.toList());
}
@Test
public void test_name(){
List<Person> persons = new ArrayList<>();
persons.stream()
.filter(person->person.getAge()>18)
.map(person->person.getName())
.filter(name->name.startsWith("庄"))
.collect(Collectors.toList());
}
  • 参数需要有明确语义

逻辑复杂

@Test
public void test_bad() {
List<Integer> values = new ArrayList<>(); int result = values.stream().mapToInt(e -> {
int sum = 0;
for (int i = 0; i < e; i++) {
if (e % i == 0) {
sum += i;
}
}
return sum;
}).sum(); System.out.println(result);
} @Test
public void test_() {
List<Integer> values = new ArrayList<>(); int result = values.stream().mapToInt(this::toInt).sum(); System.out.println(result);
} private Integer toInt(int e) {
int sum = 0;
for (int i = 0; i < e; i++) {
if (e % i == 0) {
sum += i;
}
}
return sum;
}
  • 难以读懂
  • 用途不明
  • 难以测试
  • 难以复用

建议把多行lambda表达式主体转移到一个命名函数中,然后使用方法引用

思考

  • 和内部类的区别
  • 和AOP的区别

JDK8漫谈——代码更优雅的更多相关文章

  1. JAVA8-让代码更优雅之List排序

    先定义一个实体类 @Data @AllArgsConstructor @NoArgsConstructor public class Human { private String name; priv ...

  2. Lambda表达式, 可以让我们的代码更优雅.

    在C#中, 适当地使用Lambda表达式, 可以让我们的代码更优雅. 通过lambda表达式, 我们可以很方便地创建一个delegate: 下面两个语句是等价的 Code highlighting p ...

  3. CSS 黑魔法小技巧,让你少写不必要的JS,代码更优雅

    首页   登录注册         CSS 黑魔法小技巧,让你少写不必要的JS,代码更优雅 阅读 8113 收藏 927 2017-09-26 原文链接:github.com 腾讯云容器服务CSS,立 ...

  4. 用Assert(断言)封装异常,让代码更优雅(附项目源码)

    有关Assert断言大家并不陌生,我们在做单元测试的时候,看业务事务复合预期,我们可以通过断言来校验,断言常用的方法如下: public class Assert { /** * 结果 = 预期 则正 ...

  5. 【原创】基于.NET的轻量级高性能 ORM - TZM.XFramework 之让代码更优雅

    [前言] 大家好,我是TANZAME.出乎意料的,我们在立冬的前一天又见面了,天气慢慢转凉,朋友们注意添衣保暖,愉快撸码.距离 TZM.XFramework 的首秀已数月有余,期间收到不少朋友的鼓励. ...

  6. JDK8漫谈——集合更强大

    解决什么问题 集合计算不足 解决重复代码 背后思想 管道 封装 数据处理 内容说明 是什么 计算担当.集合用于数据存储,流用于数据计算,不会修改原始数据 内置循环.高级迭代器,内置循环和计算 单向.数 ...

  7. 使用Object#tap使代码更优雅

    今天看spree源码的时候经常看到Object#tap方法.以前只知道有这个方法,而且感觉这个方法调试的作用大于实际,今日看来以前的理解应该不够准确. 先看下官方文档上tap的例子 Yields se ...

  8. 【转】Lombok:让JAVA代码更优雅

    原文地址:http://blog.didispace.com/java-lombok-1/ 关于Lombok,其实在网上可以找到很多如何使用的文章,但是很少能找到比较齐全的整理.我也一直寻思着想写一篇 ...

  9. 分享几个简单的技巧让你的 vue.js 代码更优雅

    1. watch 与 computed 的巧妙结合 一个简单的列表页面. 你可能会这么做: created(){ this.fetchData() }, watch: { keyword(){ thi ...

随机推荐

  1. python网络编程:socket、服务端、客户端

    本文内容: socket介绍 TCP: 服务端 客户端 UDP: 服务端 客户端 首发时间:2018-02-08 01:14 修改: 2018-03-20 :重置了布局,增加了UDP 什么是socke ...

  2. [20171205]uniq命令的输入输出.txt

    [20171205]uniq命令的输入输出.txt --//前几天遇到XXD与通配符问题,链接http://blog.itpub.net/267265/viewspace-2147702/--//今天 ...

  3. 前端限制input输入框(只能输入正整数)

    <input onkeyup="if(this.value.length==1){this.value=this.value.replace(/[^1-9]/g,'')}else{th ...

  4. 数据层的多租户浅谈(SAAS多租户数据库设计)

    在上一篇“浅析多租户在 Java 平台和某些 PaaS 上的实现”中我们谈到了应用层面的多租户架构,涉及到 PaaS.JVM.OS 等,与之相应的是数据层也有多租户的支持. 数据层的多租户综述 多租户 ...

  5. python第七十七天---HTML

    HTML5 :规则, 浏览器的通用规则 1.规则, 浏览器的通用规则 2.开发者: 学习html 规则 开发后台程序 - 写html文件 (当作模板) - 数据库获取数据,替换到指定的HTML文件中的 ...

  6. January 15th, 2018 Week 03rd Monday

    We got things to do. Places to go. People to see. Futures to make. 我们有很多事情要做,有很多地方要去,有很多人要见,有很多美好的未来 ...

  7. Arduino IDE for ESP8266 (0) 官方API

    http://arduino-esp8266.readthedocs.io/en/latest/esp8266wifi/readme.html 0 简单的连接到WIFI #include <ES ...

  8. Vxlan学习笔记——原理(转)

    文章转自http://www.cnblogs.com/hbgzy/p/5279269.html 1. 为什么需要Vxlan 普通的VLAN数量只有4096个,无法满足大规模云计算IDC的需求,而IDC ...

  9. Jenkins持续集成之小试牛刀

    关于Jenkins的安装,大家可以参考我的这两篇文章: Ubuntu16.04环境安装jenkins docker安装jenkins及其相关问题解决 之前没有好好研究过Jenkins,只是简单学会怎么 ...

  10. Electron 发生错误 "Cannot find module app"的解决方案

    运行一个electron小demo出现的一个错误信息:Cannot find module app 原代码如下所示: var app = require('app'); var BrowserWind ...