Java中的Lambda

一、概述

大家应该都知道面向对象的特性(抽象、封装、继承、多态)。其实在 Java8 出现之前,我们关注的往往是某一类对象应该具有什么样的属性,当然这也是面向对象的核心–对数据进行抽象。但是java8出现以后,这一点开始出现变化,在某种场景下,更加关注某一类共有的行为(这与之前的接口有些类似),这也就是java8提出函数式编程的目的。

面向对象编程到面向行为编程的变化

在这里插入图片描述

二、什么是 Lambda

我们知道,对于一个Java变量,我们可以赋给其一个**“值”**。

在这里插入图片描述

如果你想把**“一块代码”**赋给一个Java变量,应该怎么做呢?

比如,我想把右边那块代码,赋给一个叫做aBlockOfCodeJava变量:

在这里插入图片描述

Java 8之前,这个是做不到的。但是Java 8问世之后,利用Lambda特性,就可以做到了。

在这里插入图片描述

当然,这个并不是一个很简洁的写法。所以,为了使这个赋值操作更加elegant, 我们可以移除一些没用的声明。

在这里插入图片描述

这样,我们就成功的非常优雅的把“一块代码”赋给了一个变量。而“这块代码”,或者说“这个被赋给一个变量的函数”,就是一个Lambda表达式

但是这里仍然有一个问题,就是变量aBlockOfCode的类型应该是什么?

Java 8里面,**所有的Lambda的类型都是一个接口,而Lambda表达式本身,也就是”那段代码“,需要是这个接口的实现。**这是我认为理解Lambda的一个关键所在,简而言之就是,Lambda表达式本身就是一个接口的实现。直接这样说可能还是有点让人困扰,我们继续看看例子。我们给上面的aBlockOfCode加上一个类型:

在这里插入图片描述

这种只有**一个接口函数需要被实现的接口类型,我们叫它”函数式接口“。**为了避免后来的人在这个接口中增加接口函数导致其有多个接口函数需要被实现,变成"非函数接口”,我们可以在这个上面加上一个声明@FunctionalInterface, 这样别人就无法在里面添加新的接口函数了:

在这里插入图片描述

这样,我们就得到了一个完整的Lambda表达式声明:

在这里插入图片描述

三、Lambda 表达式作用

最直观的作用就是使得代码变得异常简洁。

我们可以对比一下Lambda表达式和传统的Java对同一个接口的实现:

在这里插入图片描述

这两种写法本质上是等价的。但是显然,Java 8中的写法更加优雅简洁。并且,由于Lambda可以直接赋值给一个变量,我们就可以直接把Lambda作为参数传给函数, 而传统的Java必须有明确的接口实现的定义,初始化才行:

在这里插入图片描述

有些情况下,这个接口实现只需要用到一次。传统的Java 7必须要求你定义一个“污染环境”的接口实现MyInterfaceImpl,而相较之下Java 8Lambda, 就显得干净很多。

四、Lambda 结合新特性

Lambda结合FunctionalInterface Lib, forEach, stream()method reference等新特性可以使代码变的更加简洁!

直接上例子。

假设Person的定义和List<Person>的值都给定。

在这里插入图片描述

现在需要你打印出guiltyPersons List里面所有LastName"Z"开头的人的FirstName

原生态Lambda写法:定义两个函数式接口,定义一个静态函数,调用静态函数并给参数赋值Lambda表达式。

在这里插入图片描述

这个代码实际上已经比较简洁了,但是我们还可以更简洁么?

当然可以。在Java 8中有一个函数式接口的包,里面定义了大量可能用到的函数式接口(java.util.function (Java Platform SE 8 ))。所以,我们在这里压根都不需要定义NameCheckerExecutor这两个函数式接口,直接用Java 8函数式接口包里的Predicate<T>Consumer<T>就可以了——因为他们这一对的接口定义和NameChecker/Executor其实是一样的。

在这里插入图片描述

第一步简化 - 利用函数式接口包:

在这里插入图片描述

静态函数里面的for each循环其实是非常碍眼的。这里可以利用Iterable自带的forEach()来替代。forEach()本身可以接受一个Consumer<T> 参数。

第二步简化 - 用Iterable.forEach()取代foreach loop:

在这里插入图片描述

由于静态函数其实只是对List进行了一通操作,这里我们可以甩掉静态函数,直接使用stream()特性来完成。stream()的几个方法都是接受Predicate<T>Consumer<T>等参数的(java.util.stream (Java Platform SE 8 ))。你理解了上面的内容,stream()这里就非常好理解了,并不需要多做解释。

第三步简化 - 利用stream()替代静态函数:

在这里插入图片描述

对比最开始的Lambda写法,这里已经非常非常简洁了。但是如果,我们的要求变一下,变成print这个人的全部信息,及p -> System.out.println(p); 那么还可以利用Method reference来继续简化。所谓Method reference, 就是用已经写好的别的Object/Classmethod来代替Lambda expression。格式如下:

在这里插入图片描述

第四步简化 - 如果是println§,则可以利用Method reference代替forEach中的Lambda表达式:

在这里插入图片描述

这基本上就是能写的最简洁的版本了。

五、Lambda 配合 Optional

Lambda配合Optional<T>可以使Java对于null的处理变的异常优雅

这里假设我们有一个person object,以及一个 person objectOptional wrapper

在这里插入图片描述

Optional<T>如果不结合Lambda使用的话,并不能使原来繁琐的null check变的简单。

在这里插入图片描述

只有当Optional<T>结合Lambda一起使用的时候,才能发挥出其真正的威力!

我们现在就来对比一下下面四种常见的null处理中,Java 8Lambda+Optional<T>和传统Java两者之间对于null的处理差异。

1、ifPresent

值不为null,则执行方法。

在这里插入图片描述

2、orElse

值不为null则返回值,否则返回自定义的值。

在这里插入图片描述

3、orElseGet

值不为null则返回值,否则由函数产生返回值。

在这里插入图片描述

4、map

连续判断非空。

在这里插入图片描述

由上述四种情况可以清楚地看到,Optional<T>+Lambda可以让我们少写很多ifElse块。尤其是对于情况四那种夺命连环null检查,传统java的写法显得冗长难懂,而新的Optional<T>+Lambda则清新脱俗,清楚简洁。

关于JavaLambda, 还有东西需要讨论和学习。比如如何handle lambda exception,如何利用Lambda的特性来进行parallel processing等。

参考链接:

https://www.zhihu.com/question/20125256/answer/324121308

六、函数式接口

关于接口的变动,Java8中新定义了一种接口类型,函数式接口,与其他接口的区别就是:

  • 函数式接口中只能有一个抽象方法(我们在这里不包括与Object的方法重名的方法);
  • 可以有从Object继承过来的抽象方法,因为所有类的最终父类都是Object;
  • 接口中唯一抽象方法的命名并不重要,因为函数式接口就是对某一行为进行抽象,主要目的就是支持Lambda表达式。

Java8之前已经存在的函数式接口有:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.security.PrivilegedAction
  • java.util.Comparator
  • java.io.FileFilter
  • java.nio.file.PathMatcher
  • java.lang.reflect.InvocationHandler
  • java.beans.PropertyChangeListener
  • java.awt.event.ActionListener
  • javax.swing.event.ChangeListener

另外,Java8还提供了@FunctionalInterface注解来帮助我们标识函数式接口。另外需要注意的是函数式接口的目的是对某一个行为进行封装,某些接口可能只是巧合符合函数式接口的定义。

如图所示,为java8Function包的结构(即新引入的函数式接口),图中绿色表示主要引入的新接口,其他接口基本上都是为了支持基本类型而添加的接口,方法的具体作用图中有具体说明。

在这里插入图片描述

关于更多的Lambda表达式如何让使用这里就不做过多介绍,可以参考**菜鸟编程**。

下面来说说Function接口。

在JDK8中共提供43个现有的函数接口,可以满足基本的开发需要,如果需要特别的函数接口可以自定义,使用@FunctionalInterface注解。

总共分为6大类:

  1. Function:(函数)代表其参数与返回类型不一致的函数(17个接口)。Lambda中Function函数接口(二)——Function系列接口
  2. UnaryOperator:(一元操作)表示结果和参数类型一致的函数(4个)。Lambda中Function函数接口(三)——Operator系列接口
  3. BinaryOperator:(二元操作)表示结果和参数类型一致的函数(4个)。Lambda中Function函数接口(三)——Operator系列接口
  4. Predicate:(断言)该接口代表传入一个参数并返回boolean的函数(5个)。Lambda中Function函数接口(四)——Predicate系列接口
  5. Supplier:(供应者)表示没有输入参数,但返回一个值(5个)。Lambda中Function函数接口(五)——Supplier系列接口
  6. Consumer:(消费者)表示传入一个参数,但不返回任何值(8个)。Lambda中Function函数接口(六)——Consumer系列接口

JDK8中所有函数式接口

FunctionUnaryOperatorBinaryOperatorPredicateSupplierConsumer
BiFunctionIntUnaryOperatorIntBinaryOperatorBiPredicateIntSupplierBiConsumer
IntFunctionLongUnaryOperatorLongBinaryOperatorIntPredicateLongSupplierIntConsumer
LongFunctionDoubleUnaryOperatorDoubleBinaryOperatorLongPredicateDoubleSupplierLongConsumer
DoubleFunctionDoublePredicateBooleanSupplierDoubleConsumer
IntToLongFunctionObjIntConsumer
IntToDoubleFunctionObjLongConsumer
LongToIntFunctionObjDoubleConsumer
LongToDoubleFunction
DoubleToIntFunction
DoubleToLongFunction
ToIntFunction
ToIntBiFunction
ToLongFunction
ToLongBiFunction
ToDoubleFunction
ToDoubleBiFunction

七、案例

1、排序

//1、单条件升序:
list.sort(Comparator.comparing(User::getId);
list.sort(Comparator.comparing(m-> (int)m.get("age")-(int)m.get("age")));
list.sort((m1, m2) -> (int) m1.get("age") - (int) m2.get("age"));
//2、降序:
list.sort(Comparator.comparing(User::getId).reversed());
//3、多条件升序排序:
list.sort(Comparator.comparing(User::getId).thenComparing(User::getName));
//4、用 Collections.reverse 反转一个 List
list.sort(Comparator.comparing(User::getId);
Collections.reverse(list);
//等价于
list.sort(Comparator.comparing(User::getId).reversed());

2、合并 Map.merge()

(1)说明

1、当map中不存在指定的key时,便将传入的newValue设置为key的值。
2、当key存在值时,则执行一个方法;该方法有2个参数,分别为key对应的旧值传入的新值
3、执行完自定义的方法,将返回值设置为key的值

(2)案例

Map map = new HashMap();
map.put("id", 1);
map.put("score", 80);
int key = 2;
int newValue = 90;
map.merge(key, newValue, (oldValue, newValue) -> {return 99;});

(3)源码

default V merge(K key, V value,
                BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
    Objects.requireNonNull(remappingFunction);
    Objects.requireNonNull(value);
    V oldValue = get(key);
    V newValue = (oldValue == null) ? value :
    remappingFunction.apply(oldValue, value);
    if(newValue == null) {
        remove(key);
    } else {
        put(key, newValue);
    }
    return newValue;
}

(4)使用场景

现在有一个学生各科成绩的集合,要统计每个学生的总成绩

public static Map<Integer, Integer> sum2(List<StudentScore> list) {
    Map<Integer, Integer> map = new HashMap<>();
    list.stream().forEach(studentScore -> map.merge(studentScore.getSid()
                                                    , studentScore.getScore(), Integer::sum));
    return map;
}

3、合并map

// 使用concat合并
Map<Object, Object> map = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream()).collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue(), (v1, v2) -> v1));

// 使用flatMap合并流
Map<String, Object> map = Stream.of(map1, map2).flatMap(m -> m.entrySet().stream()).collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue(), (v1, v2) -> v1));

// 利用自定义map,合并
Map<String, Object> map = map1.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue(), (v1, v2) -> v1, () -> {
    return map2;
}));
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Lambda表达式是Java 8引入的一个新特性,它可以被视为一种匿名函数,它没有名称,但有参数列表、函数体和可能的返回类型。Lambda表达式是一种简便的语法形式,用于表示Java的函数式接口。 Lambda表达式的语法形式如下: ``` (parameters) -> expression ``` 或者 ``` (parameters) -> { statements; } ``` 其,parameters表示参数列表,可以为空或包含一个或多个参数;箭头->用于分隔参数列表和Lambda表达式的主体;expression或statements表示Lambda表达式的主体,可以是一个表达式或一组语句。 以下是一些Lambda表达式的示例: ``` // 无参数的Lambda表达式 () -> System.out.println("Hello, world!"); // 一个参数的Lambda表达式 (x) -> x * x // 多个参数的Lambda表达式 (x, y) -> x + y // 包含多条语句的Lambda表达式 (x, y) -> { int sum = x + y; System.out.println("The sum is " + sum); return sum; }; ``` Lambda表达式通常与Java的函数式接口一起使用,函数式接口是只有一个抽象方法的接口。例如,以下是一个函数式接口及其使用的示例: ``` @FunctionalInterface interface MyFunction { int apply(int x, int y); } MyFunction add = (x, y) -> x + y; MyFunction subtract = (x, y) -> x - y; int result1 = add.apply(2, 3); // result1 = 5 int result2 = subtract.apply(5, 2); // result2 = 3 ``` 在此示例,我们定义了一个带有一个抽象方法apply的函数式接口MyFunction,并使用Lambda表达式来实现该接口的单个方法。然后,我们创建了两个MyFunction实例来执行加法和减法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值