lambda表达式是java8给我们带来的几个重量级新特性之一,借用lambda表达式,可以让我们的java程序设计更加简洁。最近新的项目摒弃了1.6的版本,全面基于java8进行开发,本文是java8新特性的第一篇,将探讨行为参数化、lambda表达式,以及方法引用。

一. 行为参数化

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

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

package com.lance.code.generation.java8;

import java.awt.*;

/**
* Created by wangwei on 2016/11/2.
*/
public class Apple { /** 编号 */
private long id; /** 颜色 */
private Color color; /** 重量 */
private float weight; /** 产地 */
private String origin; public Apple() {
} @Override
public String toString() {
return "Apple{" +
"id=" + id +
", color=" + color +
", weight=" + weight +
", origin='" + origin + '\'' +
'}';
} public Apple(long id, Color color, float weight, String origin) {
this.id = id;
this.color = color;
this.weight = weight;
this.origin = origin;
} public long getId() {
return id;
} public void setId(long id) {
this.id = id;
} public Color getColor() {
return color;
} public void setColor(Color color) {
this.color = color;
} public float getWeight() {
return weight;
} public void setWeight(float weight) {
this.weight = weight;
} public String getOrigin() {
return origin;
} public void setOrigin(String origin) {
this.origin = origin;
}
}

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

/**
* 筛选绿苹果
*
* @param apples
* @return
*/
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;
}

如果过了一段时间用户提出了新的需求,希望能够通过程序筛选出红色的苹果,于是我们又针对性的添加了筛选红色苹果的功能:

/**
* 筛选红苹果
*
* @param apples
* @return
*/
public static List<Apple> filterRedApples(List<Apple> apples) {
List<Apple> filterApples = new ArrayList<>();
for (final Apple apple : apples) {
if (Color.RED.equals(apple.getColor())) {
filterApples.add(apple);
}
}
return filterApples;
}

更好的实现是把颜色作为一个参数传递到函数中,这样就可以应对以后用户提出的各种颜色筛选请求了:

/**
* 自定义筛选颜色
*
* @param apples
* @param color
* @return
*/
public static List<Apple> filterApplesByColor(List<Apple> apples, Color color) {
List<Apple> filterApples = new ArrayList<>();
for (final Apple apple : apples) {
if (color.equals(apple.getColor())) {
filterApples.add(apple);
}
}
return filterApples;
}

这样设计了之后,再也不用担心用户的颜色筛选需求变化了,但是不幸的是,某一天用户提了一个需求要求能够选择重量达到某一标准的苹果,有了前面的教训,我们也把重量的标准作为参数传递给筛选函数,于是得到:

/**
* 筛选指定颜色,且重要符合要求
*
* @param apples
* @param color
* @param weight
* @return
*/
public static List<Apple> filterApplesByColorAndWeight(List<Apple> apples, Color color, float weight) {
List<Apple> filterApples = new ArrayList<>();
for (final Apple apple : apples) {
if (color.equals(apple.getColor()) && apple.getWeight() >= weight) {
filterApples.add(apple);
}
}
return filterApples;
}

这样通过传递参数的方式真的好吗?如果筛选条件越来越多,组合模式越来越复杂,我们是不是需要考虑到所有的情况,并针对每一种情况都有相应的应对策略呢,并且这些函数仅仅是筛选条件的部分不一样,其余部分都是相同的模板代码(遍历集合),这个时候我们就可以将行为 参数化 ,让函数仅保留模板代码,而把筛选条件抽离出来当做参数传递进来,在java8之前,我们通过定义一个过滤器接口来实现:

/**
* 苹果过滤接口
*
* @author zhenchao.wang 2016-09-17 14:21
* @version 1.0.0
*/
@FunctionalInterface
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.Comparatorjava.util.concurrent.Callable等,使用这一类接口的时候,我们都可以在具体调用的地方用过匿名类来指定函数的具体执行逻辑,不过从上面的代码块来看,虽然很极客,但是不够简洁,在java8中我们可以通过lambda来简化:

// 筛选苹果
List<Apple> filterApples = filterApplesByAppleFilter(apples,
(Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100);

通过lambda表达式极大的精简了代码,下面来学习java的lambda表达式吧~

二. 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):

/**
* 苹果过滤接口
*
* @author zhenchao.wang 2016-09-17 14:21
* @version 1.0.0
*/
@FunctionalInterface
public interface AppleFilter { /**
* 筛选条件抽象
*
* @param apple
* @return
*/
boolean accept(Apple apple); }

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

jdk自带的函数式接口

jdk为lambda表达式已经内置了丰富的函数式接口,如下表所示(仅列出部分):

函数式接口            函数描述符                    原始类型特化
Predicate<T> T -> boolean IntPredicate, LongPredicate, DoublePredicate
Consumer<T> T -> void IntConsumer, LongConsumer, DoubleConsumer
Funcation<T, R> T -> R IntFuncation<R>, IntToDoubleFunction, IntToLongFunction<R>, LongFuncation…
Supplier<T> () -> T BooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier
UnaryOperator<T> T -> T IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator
BinaryOperator<T> (T, T) -> T IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator
BiPredicate<L, R> (L, R) -> boolean
BiConsumer<T, U> (T, U) -> void
BiFunction<T, U, R> (T, U) -> R

下面分别就Predicate<T>Consumer<T>Function<T, R>的使用示例说明。

Predicate<T>

@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<T>

@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<T, R>

@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对集合进行转换:

/**
* 遍历集合,执行自定义转换操作
*
* @param list
* @param function
* @param <T>
* @param <R>
* @return
*/
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.RED.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)

参考:

Java8新特性之Lambda表达式的更多相关文章

  1. java8新特性: lambda表达式:直接获得某个list/array/对象里面的字段集合

    java8新特性: lambda表达式:直接获得某个list/array/对象里面的字段集合 比如,我有一张表: entity Category.java service CategoryServic ...

  2. Java8 新特性学习 Lambda表达式 和 Stream 用法案例

    Java8 新特性学习 Lambda表达式 和 Stream 用法案例 学习参考文章: https://www.cnblogs.com/coprince/p/8692972.html 1.使用lamb ...

  3. Java8 新特性之Lambda表达式

    1. Lambda 表达式概述 Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递); Lambda 表达式可以写出更简洁,更灵活的代码 ...

  4. 【Java8新特性】Lambda表达式基础语法,都在这儿了!!

    写在前面 前面积极响应读者的需求,写了两篇Java新特性的文章.有小伙伴留言说:感觉Lambda表达式很强大啊!一行代码就能够搞定那么多功能!我想学习下Lambda表达式的语法,可以吗?我的回答是:没 ...

  5. 【Java8新特性】- Lambda表达式

    Java8新特性 - Lambda表达式 生命不息,写作不止 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长! ...

  6. 夯实Java基础(二十二)——Java8新特性之Lambda表达式

    1.前言 Java 8于14年发布到现在已经有5年时间了,经过时间的磨练,毫无疑问,Java 8是继Java 5(发布于2004年)之后的又一个非常最重要的版本.因为Java 8里面出现了非常多新的特 ...

  7. java8新特性之——lambda表达式的使用

    lambda表达式简介 个人理解,lambda表达式就是一种新的语法,没有什么新奇的,简化了开发者的编码,其实底层还是一些常规的代码.Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解 ...

  8. 【Java8新特性】Lambda表达式

    一.Lambda 表达式 是什么? Lambda读音:拉姆达. Lambda是一个匿名函数,匿名函数就是一个没有名字的函数. Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中). ...

  9. Java8 新特性(一)- Lambda 表达式

    2014年3月18日发布了JavaSE 8 不追求技术的新,追求技术的稳定 本质:Lambda 表达式是一个匿名函数 作用:简化代码,增强代码的表达力 Lambda 语法格式 // 格式1:无参无返回 ...

随机推荐

  1. PHP对HTML代码尸体编码2个函数

    1.htmlspecialchars() 函数把一些预定义的字符转换为 HTML 实体. 2.htmlentities() 函数把字符转换为 HTML 实体. 记录下

  2. symfony window下的安装 安装时候出现的问题以及解决方案

    1. cmd进入DOS  , cd 到 php.exe 的目录下 2.         php -r "readfile('http://symfony.com/installer');&q ...

  3. Linux下profile与bashrc的区别

    /etc/profile./etc/bashrc.~/.bash_profile.~/.bashrc很容易混淆,他们之间有什么区别?它们的作用到底是什么?/etc/profile: 用来设置系统环境参 ...

  4. 【MacOS】brew-python3

    mkdir homebrew && curl -L https://github.com/Homebrew/brew/tarball/master | tar xz --strip 1 ...

  5. 【安全开发】IOS安全编码规范

    申明:本文非笔者原创,原文转载自:https://github.com/SecurityPaper/SecurityPaper-web/blob/master/_posts/2.SDL%E8%A7%8 ...

  6. Ubuntu 13.10 下安装搜狗输入法

    1.卸载ibus输入法: sudo apt-get remove ibus     sudo为取得root权限的意思,Ubuntu系统默认root账户关闭,很多操作需要取得root     权限才可以 ...

  7. psutil的使用

    psutil是Python中广泛使用的开源项目,其提供了非常多的便利函数来获取操作系统的信息. 此外,还提供了许多命令行工具提供的功能,如ps,top,kill.free,iostat,iotop,p ...

  8. N76E003的定时器/计数器 0和1

    定时器/计数器 0和1N76E003系列定时器/计数器 0和1是2个16位定时器/计数器.每个都是由两个8位的寄存器组成的16位计数寄存器. 对于定时器/计数器0,高8位寄存器是TH0. 低8位寄存器 ...

  9. thinkjs中修改默认主键

    报错信息: { Error: ER_BAD_FIELD_ERROR: Unknown column 'a_role.id' in 'field list' 还原场景: a_role这张表没有自增的id ...

  10. CentoOS6.6安装netcat

    http://blog.csdn.net/u013673976/article/details/47084841 CentOS下安装netcat 使用zookeeper过程中,需要监控集群状态.在使用 ...