深入浅析JDK8新特性之Lambda表达式

第一次是接触Lambda表达式是在TypeScript中(JavaScript的超集中),当时是为了让TypeScript的this方法外而不是本方法内所使用的。使用过后突然想到Lambda不是JDK8的重量级新特性么?于是感觉查阅相关资料并记录下来:

一. 行为参数化

行为参数化简单的说就是函数的主体仅包含模板类通用代码,而一些会随着业务场景而变化的逻辑则以参数的形式传递到函数之中,采用行为参数化可以让程序更加的通用,以应对频繁变更的需求。

考虑一个业务场景,假设我们需要通过程序对苹果进行筛选,我们先定义一个苹果的实体:

public class Apple {
/** 编号 */
private long id;
/** 颜色 */
private Color color;
/** 重量 */
private float weight;
/** 产地 */
private String origin;
public Apple() {
}
public Apple(long id, Color color, float weight, String origin) {
this.id = id;
this.color = color;
this.weight = weight;
this.origin = origin;
}
// 省略getter和setter
}

用户最开始的需求可能只是简单的希望能够通过程序筛选出绿色的苹果,于是我们可以很快的通过程序实现:

public static List<Apple> filterGreenApples(List<Apple> apples) {
List<Apple> filterApples = new ArrayList<>();
for (final Apple apple : apples) {
if (Color.GREEN.equals(apple.getColor())) {
filterApples.add(apple);
}
}
return filterApples;
}

这段代码很简单,没有什么值得说的。但当如果用户需求变为绿色,看起来修改代码也很简单,无非是把判断条件的绿色改为红色而已。但我们需要考虑另外一个问题,如果变化条件频繁的改变这么办?如果只是颜色的改变,那好我们直接让用户把颜色的判断条件传递进来,判断方法的参数变”要判断的集合以及要筛选的颜色”。但如果用户不仅仅是判断颜色,还想判断重量呀,大小呀什么的,怎么办?你是不是觉得我们依次添加不同的参数来完成判断就可以了?但这样通过传递参数的方式真的好吗?如果筛选条件越来越多,组合模式越来越复杂,我们是不是需要考虑到所有的情况,并针对每一种情况都有相应的应对策略呢?这个时候我们就可以将行为参数化,筛选条件抽离出来当做参数传递进来,此时我们可以封装一个判断的接口出来:

public interface AppleFilter {
/**
* 筛选条件抽象
*
* @param apple
* @return
*/
boolean accept(Apple apple);
}
/**
* 将筛选条件封装成接口
*
* @param apples
* @param filter
* @return
*/
public static List<Apple> filterApplesByAppleFilter(List<Apple> apples, AppleFilter filter) {
List<Apple> filterApples = new ArrayList<>();
for (final Apple apple : apples) {
if (filter.accept(apple)) {
filterApples.add(apple);
}
}
return filterApples;
}

通过上面行为抽象化之后,我们可以在具体调用的地方设置筛选条件,并将条件作为参数传递到方法中,此时采用匿名内部类的方法:

public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
// 筛选苹果
List<Apple> filterApples = filterApplesByAppleFilter(apples, new AppleFilter() {
@Override
public boolean accept(Apple apple) {
// 筛选重量大于100g的红苹果
return Color.RED.equals(apple.getColor()) && apple.getWeight() > 100;
}
});
}

这样的设计在jdk内部也经常采用,比如Java.util.Comparator,java.util.concurrent.Callable等,使用这一类接口的时候,我们都可以在具体调用的地方用过匿名类来指定函数的具体执行逻辑,不过从上面的代码块来看,虽然很极客,但是不够简洁,在java8中我们可以通过lambda来简化:

// 筛选苹果
List<Apple> filterApples = filterApplesByAppleFilter(apples,
(Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100);
//()->xxx ()里面就是方法参数,xxx是方法实现

二. lambda表达式定义

我们可以将lambda表达式定义为一种 简洁、可传递的匿名函数,首先我们需要明确lambda表达式本质上是一个函数,虽然它不属于某个特定的类,但具备参数列表、函数主体、返回类型,以及能够抛出异常;其次它是匿名的,lambda表达式没有具体的函数名称;lambda表达式可以像参数一样进行传递,从而极大的简化代码的编写。格式定义如下:

格式一: 参数列表 -> 表达式

格式二: 参数列表 -> {表达式集合}

需要注意的是,lambda表达式隐含了return关键字,所以在单个的表达式中,我们无需显式的写return关键字,但是当表达式是一个语句集合的时候,则需要显式添加return,并用花括号{ }将多个表达式包围起来,下面看几个例子:

//返回给定字符串的长度,隐含return语句
(String s) -> s.length()
// 始终返回42的无参方法
() -> 42
// 包含多行表达式,则用花括号括起来
(int x, int y) -> {
int z = x * y;
return x + z;
}

三. 依托于函数式接口使用lambda表达式

lambda表达式的使用需要借助于函数式接口,也就是说只有函数式接口出现地方,我们才可以将其用lambda表达式进行简化。

自定义函数式接口:

函数式接口定义为只具备 一个抽象方法 的接口。java8在接口定义上的改进就是引入了默认方法,使得我们可以在接口中对方法提供默认的实现,但是不管存在多少个默认方法,只要具备一个且只有一个抽象方法,那么它就是函数式接口,如下(引用上面的AppleFilter):

/**
* 苹果过滤接口
*/
@FunctionalInterface
public interface AppleFilter {
/**
* 筛选条件抽象
*
* @param apple
* @return
*/
boolean accept(Apple apple);
}

AppleFilter仅包含一个抽象方法 accept(Apple apple),依照定义可以将其视为一个函数式接口,在定义时我们为该接口添加了@FunctionalInterface注解,用于标记该接口是函数式接口,不过这个接口是可选的,当添加了该接口之后,编译器就限制了该接口只允许有一个抽象方法,否则报错,所以推荐为函数式接口添加该注解。

jdk自带的函数式接口:

jdk为lambda表达式已经内置了丰富的函数式接口,下面分别就Predicate<T>、Consumer<T>、Function<T, R>的使用示例说明。

Predicate:

@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}

Predicate的功能类似于上面的AppleFilter,利用我们在外部设定的条件对于传入的参数进行校验,并返回验证结果boolean,下面利用Predicate对List集合的元素进行过滤:

/**
*
* @param list
* @param predicate
* @param <T>
* @return
*/
public <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> newList = new ArrayList<T>();
for (final T t : list) {
if (predicate.test(t)) {
newList.add(t);
}
}
return newList;
}

使用:

demo.filter(list, (String str) -> null != str && !str.isEmpty());

Consumer

@FunctionalInterface
public interface Consumer<T> {

/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}

Consumer提供了一个accept抽象函数,该函数接收参数,但不返回值,下面利用Consumer遍历集合.

/**
* 遍历集合,执行自定义行为
*
* @param list
* @param consumer
* @param <T>
*/
public <T> void filter(List<T> list, Consumer<T> consumer) {
for (final T t : list) {
consumer.accept(t);
}
}

利用上面的函数式接口,遍历字符串集合,并打印非空字符串:

demo.filter(list, (String str) -> {
if (StringUtils.isNotBlank(str)) {
System.out.println(str);
}
});

Function

@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}

Funcation执行转换操作,输入是类型T的数据,返回R类型的数据,下面利用Function对集合进行转换:

public <T, R> List<R> filter(List<T> list, Function<T, R> function) {
List<R> newList = new ArrayList<R>();
for (final T t : list) {
newList.add(function.apply(t));
}
return newList;
}

其他:

demo.filter(list, (String str) -> Integer.parseInt(str));

上面这些函数式接口还提供了一些逻辑操作的默认实现,留到后面介绍java8接口的默认方法时再讲吧~

使用过程中需要注意的一些事情:

类型推断:

在编码过程中,有时候可能会疑惑我们的调用代码会去具体匹配哪个函数式接口,实际上编译器会根据参数、返回类型、异常类型(如果存在)等做正确的判定。
在具体调用时,在一些时候可以省略参数的类型,从而进一步简化代码:

// 筛选苹果
List<Apple> filterApples = filterApplesByAppleFilter(apples,
(Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100);
// 某些情况下我们甚至可以省略参数类型,编译器会根据上下文正确判断
List<Apple> filterApples = filterApplesByAppleFilter(apples,
apple -> Color.R
ED.equals(apple.getColor()) && apple.getWeight() >= 100);

局部变量

上面所有例子我们的lambda表达式都是使用其主体参数,我们也可以在lambda中使用局部变量,如下

int weight = 100;
List<Apple> filterApples = filterApplesByAppleFilter(apples,
apple -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= weight);:

该例子中我们在lambda中使用了局部变量weight,不过在lambda中使用局部变量必须要求该变量 显式声明为final或事实上的final ,这主要是因为局部变量存储在栈上,lambda表达式则在另一个线程中运行,当该线程视图访问该局部变量的时候,该变量存在被更改或回收的可能性,所以用final修饰之后就不会存在线程安全的问题。

四. 方法引用

采用方法引用可以更近一步的简化代码,有时候这种简化让代码看上去更加的直观,先看一个例子:

/* ... 省略apples的初始化操作 */
// 采用lambda表达式
apples.sort((Apple a, Apple b) -> Float.compare(a.getWeight(), b.getWeight()));
// 采用方法引用
apples.sort(Comparator.comparing(Apple::getWeight));

方法引用通过::将方法隶属和方法自身连接起来,主要分为三类:

静态方法

(args) -> ClassName.staticMethod(args)

转换成

ClassName::staticMethod

参数的实例方法

(args) -> args.instanceMethod()

转换成

ClassName::instanceMethod // ClassName是args的类型

外部的实例方法

(args) -> ext.instanceMethod(args)

转换成

ext::instanceMethod(args)

参考:

http://www.codeceo.com/article/lambda-of-java-8.html

以上所述是小编给大家介绍的JDK8新特性之Lambda表达式,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • Java开发环境jdk 1.8安装配置方法(Win7 64位系统/windows server 2008)

    什么是jdk jdk是什么呢?jdk的是java development kit的缩写,意思是java程序开发的工具包.也可以说jdk是java的sdk. 目前的JDK大致分三个大版本: Java SE:Java Platform, Standard Edition,Java平台标准版,主要用来开发桌面程序 Java EE:Java Platform, Enterprise Edition,Java平台企业版,主要用来开发WEB程序 Java ME:Java Platform, Micro Ed

  • Java 高并发十: JDK8对并发的新支持详解

    1. LongAdder 和AtomicLong类似的使用方式,但是性能比AtomicLong更好. LongAdder与AtomicLong都是使用了原子操作来提高性能.但是LongAdder在AtomicLong的基础上进行了热点分离,热点分离类似于有锁操作中的减小锁粒度,将一个锁分离成若干个锁来提高性能.在无锁中,也可以用类似的方式来增加CAS的成功率,从而提高性能. LongAdder原理图: AtomicLong的实现方式是内部有个value 变量,当多线程并发自增,自减时,均通过CA

  • JDK 7U15在 Windows x86平台下的安装方法

    这里以JDK 7U15在 Windows x86平台下的安装为例,其他平台安装与此类同. 下载JDK请查看:JDK最新下载地址[含下载方法] ①JDK 7U15下载完成后,是一个可执行文件,如图所示: ②双击运行该安装程序,出现如下界面: ③点击"下一步",出现如下界面: ④可以看到,JDK包含了Java开发工具.源代码(基础类库)和公共JRE,这三项都是默认安装的,是Java开发所必须的,缺一不可.这里,可以更改JDK的安装目录.选择好安装目录,点击"下一步",进

  • Javaweb开发环境Myeclipse6.5 JDK1.6 Tomcat6.0 SVN1.8配置教程

    Javaweb开发环境的配置也是比较繁琐的一件事情,虽然理论上使用记事本,完全可以写出一个Javaweb工程, 但是在团队大型开发的Javaweb过程中,你必须配置好Eclipse.SVN.Tomcat与JDK1.6,当然还有Mysql数据库,不过数据库配置的事情属于后台内容,本文仅讨论前台开发环境的配置. 之所以选择Myeclipse6.5是因为其他高版本的Myeclipse实在是太卡,编辑小型程序还好,编辑大型程序完全吃不消,Eclipse的功能毕竟简陋,所以还是选择了旧版本.经典版本Mye

  • jdk8的datetime时间函数使用示例

    JDK8已发布,写了一个datetime时间函数使用方法的小示例 复制代码 代码如下: package datetime; import static java.time.temporal.TemporalAdjusters.lastDayOfMonth;import static java.time.temporal.TemporalAdjusters.previousOrSame; import java.time.DayOfWeek;import java.time.Duration;im

  • 深入浅析JDK8新特性之Lambda表达式

    第一次是接触Lambda表达式是在TypeScript中(JavaScript的超集中),当时是为了让TypeScript的this方法外而不是本方法内所使用的.使用过后突然想到Lambda不是JDK8的重量级新特性么?于是感觉查阅相关资料并记录下来: 一. 行为参数化 行为参数化简单的说就是函数的主体仅包含模板类通用代码,而一些会随着业务场景而变化的逻辑则以参数的形式传递到函数之中,采用行为参数化可以让程序更加的通用,以应对频繁变更的需求. 考虑一个业务场景,假设我们需要通过程序对苹果进行筛选

  • 浅析C++11新特性的Lambda表达式

    lambda简介 熟悉Python的程序员应该对lambda不陌生.简单来说,lambda就是一个匿名的可调用代码块.在C++11新标准中,lambda具有如下格式: [capture list] (parameter list) -> return type { function body } 可以看到,他有四个组成部分: 1.capture list: 捕获列表 2.parameter list: 参数列表 3.return type: 返回类型 4.function body: 执行代码

  • Java8新特性:Lambda表达式之方法引用详解

    1.方法引用简述 方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法.方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文.计算时,方法引用会创建函数式接口的一个实例. 当Lambda表达式中只是执行一个方法调用时,不用Lambda表达式,直接通过方法引用的形式可读性更高一些.方法引用是一种更简洁易懂的Lambda表达式. Lambda表达式全文详情地址:http://blog.csdn.net/sun_promise/article/details/

  • Java8新特性之Lambda表达式的使用

    1. lambda表达式介绍 lambda表达式是Java8提供的新特性之一,也可以称之为闭包:它支持Java能够进行简单的函数式编程,也就是说可以把一个匿名函数作为一个方法的参数进行传递:其格式分为三部分,第一部分为入参列表,第二部由->固定组成,第三部分为方法体: public class LambdaTest { public static void main(String[] args) { // 使用lambda表达式创建线程 Thread thread = new Thread(()

  • 简单易懂的java8新特性之lambda表达式知识总结

    一.概念 从本质上来说,它就是一个匿名函数,可以用来直接实现接口中的方法,从而简化代码.但是Lambda有一个限制,不能实现接口中的所有方法,所以Lambda表达式只能用于有且仅有一个必须需要实现的方法接口,这里需要注意必须需要实现这六个字. public interface Printer { //有一个需要实现的方法,可以使用Lambda表达式 void print(); } public interface Printer { //有一个需要实现的方法,可以使用Lambda表达式 void

  • 深入理解Java8新特性之Lambda表达式的基本语法和自定义函数式接口

    1.写在前面 目前我们学习Java主要用到的应该就是Java8了,或者说大部分企业当前使用的也是Java8.那么既然Java8的应用如此之广泛,一定有一些亮点所在: Lambda 表达式 函数式接口 方法引用与构造器引用 Stream API 接口中的默认方法与静态方法 新时间日期API 其他新特性 速度更快.代码更少(增加了新的语法 Lambda 表达式).强大的 Stream API.便于并行.最大化减少空指针异常 Optional. 2.为什么要使用Lambda表达式? Lambda 是一

  • Java8新特性之Lambda表达式浅析

    说到java 8,首先会想到lambda(闭包)以及虚拟扩展方法(default method),这个特性早已经被各大技术网站炒得沸沸扬扬了,也是我们java 8系列开篇要讲的第一特性(JEP126 http://openjdk.java.net/jeps/126),jdk8的一些库已经应用了lambda表达式重新设计了,理解他对学习java 8新特性有着重要的意义. 一.函数式接口 函数式接口(functional interface 也叫功能性接口,其实是同一个东西).简单来说,函数式接口是

  • 一文带你入门JDK8新特性——Lambda表达式

    Lambda简介 Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构. JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便.高效. 对接口的要求 虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现.Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法 jd

  • 浅析C# 9.0 新特性之 Lambda 弃元参数

    大家好,这是 C# 9.0 新特性短系列的第 5 篇文章. 弃元(Discards) 是在 C# 7.0 的时候开始支持的,它是一种人为丢弃不使用的临时虚拟变量.语法上它是用来赋值的,但它却不被分配存储空间,即没有值,所以不能从中读取值.弃元用 _(下划线) 表示,下划线是一个关键字,只能赋值,不能读取,例如: 在 C# 7.0 中,弃元的使用场景主要有下面四种: 元组和对象的解构 使用 is 和 switch 的模式匹配 对具有 out 参数的方法的调用 作用域内独立使用场景 针对这几个场景,

  • JDK14新特性之switch表达式的实现

    简介 switch的新特性可是源远流长,早在JDK 12就以预览功能被引入了,最终在JDK 14成为了正式版本的功能:JEP 361: Switch Expressions (Standard). 其实Switch新增的功能有两个,一个就是可以连写case,一个就是switch可以带返回值了. 写在前面 就在我兴致勃勃的想要创建一个以switch命名的package的时候,突然间发现在IDEA中居然创建不java类了. 经过我的再三尝试,反复改名,终于被我发现了隐藏在里面的小秘密: java k

随机推荐