目录
一. 行为参数化
二. lambda表达式定义
三. 依托于函数式接口使用lambda表达式
自定义函数式接口
jdk自带的函数式接口
使用过程中需要注意的一些事情
四. 方法引用
首页 Java java教程 Java8 新特性之 Lambda 表达式

Java8 新特性之 Lambda 表达式

Feb 23, 2017 am 10:34 AM
java8 lambda 新特性

摘要: lambda表达式是Java8给我们带来的几个重量级新特性之一,借用lambda表达式,可以让我们的Java程序设计更加简洁。本文是Java8新特性的第一篇,将探讨行为参数化、lambda表达式,以及方法引用。

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

一. 行为参数化

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

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

/**
 * 苹果实体
 *
 * @author zhenchao.wang 2016-09-17 12:49
 * @version 1.0.0
 */
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
}
登录后复制

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

/**
 * 筛选绿苹果
 *
 * @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.Comparator
登录后复制

java.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 -> booleanIntPredicate, LongPredicate, DoublePredicate
Consumer<T>T -> voidIntConsumer, LongConsumer, DoubleConsumer
FuncationT -> RIntFuncation, IntToDoubleFunction, IntToLongFunction, LongFuncation…
Supplier() -> TBooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier
UnaryOperatorT -> TIntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator
BinaryOperator(T, T) -> TIntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator
BiPredicate(L, R) -> boolean
BiConsumer(T, U) -> void
BiFunction(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 
 * @return
 */
public  List filter(List list, Predicate<T> predicate) {
    List newList = new ArrayList();
    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 
 */
public  void filter(List 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 
 * @param 
 * @return
 */
public  List filter(List list, Function<T, R> function) {
    List newList = new ArrayList();
    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 表达式 的内容,更多相关内容请关注PHP中文网(www.php.cn)!



本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

lambda 表达式在 C++ 中如何处理异常? lambda 表达式在 C++ 中如何处理异常? Apr 17, 2024 pm 12:42 PM

在C++中,使用Lambda表达式处理异常有两种方法:使用try-catch块捕获异常,并在catch块中处理或重新抛出异常。使用std::function类型的包装函数,其try_emplace方法可以捕获Lambda表达式中的异常。

C++ lambda 表达式中闭包的含义是什么? C++ lambda 表达式中闭包的含义是什么? Apr 17, 2024 pm 06:15 PM

在C++中,闭包是能够访问外部变量的lambda表达式。要创建闭包,请捕获lambda表达式中的外部变量。闭包提供可复用性、信息隐藏和延迟求值等优势。它们在事件处理程序等实际情况中很有用,其中即使外部变量被销毁,闭包仍然可以访问它们。

用 C++ lambda 表达式实现多线程编程的优势是什么? 用 C++ lambda 表达式实现多线程编程的优势是什么? Apr 17, 2024 pm 05:24 PM

lambda表达式在C++多线程编程中的优势包括:简洁性、灵活性、易于传参和并行性。实战案例:使用lambda表达式创建多线程​​,在不同线程中打印线程ID,展示了该方法的简洁和易用性。

C++ Lambda 表达式如何实现闭包? C++ Lambda 表达式如何实现闭包? Jun 01, 2024 pm 05:50 PM

C++Lambda表达式支持闭包,即保存函数作用域变量并供函数访问。语法为[capture-list](parameters)->return-type{function-body}。capture-list定义要捕获的变量,可以使用[=]按值捕获所有局部变量,[&]按引用捕获所有局部变量,或[variable1,variable2,...]捕获特定变量。Lambda表达式只能访问捕获的变量,但无法修改原始值。

PHP 8.3发布:新特性一览 PHP 8.3发布:新特性一览 Nov 27, 2023 pm 12:52 PM

PHP8.3发布:新特性一览随着技术的不断发展和需求的不断变化,编程语言也在不断更新和改进。作为一种广泛应用于网络开发的脚本语言,PHP一直在不断进步,为开发者提供更强大和高效的工具。最近发布的PHP8.3版本带来了许多期待已久的新特性和改进,下面让我们来看一下这些新特性的一览。非空属性的初始化在过去的PHP版本中,如果一个类的属性没有被明确赋值,它的值

C++ lambda 表达式如何捕获外部变量? C++ lambda 表达式如何捕获外部变量? Apr 17, 2024 pm 04:39 PM

在C++中捕获外部变量的lambda表达式有三种方法:按值捕获:创建一个变量副本。按引用捕获:获得变量引用。同时按值和引用捕获:允许捕获多个变量,按值或按引用。

C++ 函数调用 Lambda 表达式:参数传递和返回值的回调优化 C++ 函数调用 Lambda 表达式:参数传递和返回值的回调优化 May 03, 2024 pm 12:12 PM

在C++中,可以使用Lambda表达式作为函数参数,实现回调函数的灵活性。具体而言:参数传递:通过std::function包装Lambda表达式,以函数指针形式传递给函数。返回值处理:使用std::function声明回调函数指针时指定返回值类型。实战案例:优化GUI事件处理中的回调,避免创建不必要的对象或函数指针,提高代码简洁性和可维护性。

如何使用 C++ lambda 表达式执行延迟求值? 如何使用 C++ lambda 表达式执行延迟求值? Apr 17, 2024 pm 12:36 PM

如何使用C++lambda表达式执行延迟求值?使用lambda表达式创建延迟求值的函数对象。延迟计算推迟到需要时才执行。仅当需要时才计算结果,提高性能。

See all articles