问题

Java 8的新特性lambda表达式是否比匿名内部类具有更好的可读性?

回答
Lambda 表达式是否比匿名内部类更具可读性,这确实是一个值得深入探讨的问题,而且答案并非一概而论,而是取决于具体的场景和使用者的熟悉程度。不过,在大多数情况下,特别是在处理函数式接口(functional interfaces)的时候,Lambda 表达式确实能够以一种更简洁、更直观的方式来表达意图,从而提升代码的可读性。

让我们先回顾一下匿名内部类。在 Java 8 之前,如果你想传递一个行为,例如作为回调函数、事件监听器,或者仅仅是实现一个接口中的一个方法,你通常需要创建一个匿名内部类。例如,如果你想在一个集合上执行一个操作,比如过滤出偶数,使用匿名内部类可能是这样的:

```java
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List evenNumbers = new ArrayList<>();

Collections.sort(numbers, new Comparator() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
});

// 或者更常见的过滤场景
List evenNumbers = new ArrayList<>();
for (Integer number : numbers) {
if (number % 2 == 0) {
evenNumbers.add(number);
}
}
```

你看,即使是这样一个简单的过滤操作,使用匿名内部类也需要不少“样板代码”:`new Comparator() { ... }`、`@Override` 注解、方法签名 `public int compare(Integer o1, Integer o2)`,然后才是实际的比较逻辑 `return o1.compareTo(o2);`。这些结构化的代码,虽然提供了明确的类型和方法信息,但在你只想表达“比较这两个数”这个核心意图时,显得有些冗余。

现在,我们来看看 Lambda 表达式如何处理同样的情况。Lambda 表达式的核心思想是将行为本身作为参数传递,而不是一个实现某个接口的对象。对于上面的 `Comparator` 例子,使用 Lambda 表达式会是这样:

```java
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

Collections.sort(numbers, (o1, o2) > o1.compareTo(o2));

// 或者更简洁的
Collections.sort(numbers, Integer::compareTo);
```

这里,`(o1, o2) > o1.compareTo(o2)` 就是一个 Lambda 表达式。它直接描述了“接收两个整数 `o1` 和 `o2`,然后返回它们的比较结果”。相比于匿名内部类,它省去了接口声明、方法声明、`@Override` 等,只保留了最核心的逻辑。这种“去噪”处理,使得代码更加聚焦于实际要执行的任务。

再举一个更常见的集合操作场景,比如使用 Stream API 来过滤偶数:

匿名内部类风格(假设有一个 `Predicate` 接口):

```java
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List evenNumbers = new ArrayList<>();

// 假设有一个类似 stream().filter() 的方法,需要一个 Predicate
// List evenNumbers = numbers.filter(new Predicate() {
// @Override
// public boolean test(Integer number) {
// return number % 2 == 0;
// }
// });
```
(注:`java.util.function.Predicate` 是JDK1.8引入的,上面的示例是为了说明概念,如果是在Java 8之前,实现过滤会更复杂,例如直接使用循环。)

Lambda 表达式风格:

```java
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List evenNumbers = numbers.stream()
.filter(number > number % 2 == 0)
.collect(Collectors.toList());
```
在这里,`number > number % 2 == 0` 是一个 Lambda 表达式。它直观地表达了“对于每一个 `number`,如果它能被 2 整除,则保留”。这种声明式的写法,比起描述如何一步步判断和添加到新列表,更容易理解“我们想要偶数”这个目标。

为什么 Lambda 表达式在这些场景下可读性更强?

1. 聚焦核心逻辑: Lambda 表达式极大地减少了实现接口方法所需的“仪式感”代码。它将你的注意力直接引向你真正关心的行为本身,而不是围绕该行为的类结构。当你只需要表达“做什么”的时候,Lambda 让你直接说“做什么”,而不是先说“我要创建一个能够做什么的对象,它有个方法叫做 XXX,它会执行 YYY”。

2. 更少的“噪音”: 匿名内部类,特别是那些只有一个方法的接口,其声明方式会带来很多“噪音”。比如接口名、方法名、参数名、返回值类型、`@Override` 注解,这些信息对于理解“做什么”而言,很多时候是多余的。Lambda 表达式通过简洁的语法糖,剥离了这些非必要的信息,让代码更紧凑,更容易在短时间内抓住关键。

3. 更自然的函数式风格: Java 8 引入 Lambda 表达式,就是为了更好地支持函数式编程范式。在函数式编程中,函数(或行为)是第一等公民,可以直接传递和操作。Lambda 表达式是这种传递的直接体现。当你习惯了这种风格,你会发现它更符合“传递一段可执行的代码”的直觉。

4. 提高代码的流畅性和表达力: 尤其是与 Stream API 结合使用时,Lambda 表达式能够形成一种非常流畅、描述性的代码风格。例如 `stream().filter(...).map(...).reduce(...)` 这样的链式调用,每一个环节都用 Lambda 表达式清晰地描述了数据的转换过程,如同一个自然语言的流水线描述。

什么情况下 Lambda 表达式的可读性可能不如匿名内部类?

当然,事无绝对。在某些复杂场景下,如果 Lambda 表达式写得过于冗长,或者包含了复杂的逻辑,那么它可能就会牺牲可读性。

1. 复杂逻辑: 如果 Lambda 表达式内部的代码块非常长,包含多个语句、循环、条件判断,那么将其写成一个匿名内部类,并为方法命名,可能会更清晰。例如:

```java
// 匿名内部类风格
service.doSomething(new Task() {
@Override
public void execute() {
// 复杂的初始化逻辑
init();
// 多个条件分支
if (condition1) {
processA();
} else if (condition2) {
processB();
}
// 循环处理
for (Item item : items) {
item.update();
}
// 清理工作
cleanup();
}
});

// Lambda 风格(可能就很难看)
// service.doSomething(() > {
// init();
// if (condition1) { processA(); }
// else if (condition2) { processB(); }
// for (Item item : items) { item.update(); }
// cleanup();
// });
```
在这种情况下,匿名内部类通过 `execute()` 这个方法名,提供了一个语义化的入口,帮助读者理解这整个代码块的作用。而 Lambda 表达式则直接将复杂的实现塞在一起,可读性就大打折扣。

2. 需要访问外部局部变量(非 `final` 或 `effectively final`): Lambda 表达式捕获的变量必须是 `final` 或 `effectively final` 的。如果需要修改外部的局部变量,匿名内部类可以做到(虽然不推荐)。当你需要这种修改外部状态的行为时,匿名内部类反而提供了一种(虽然不纯粹)的实现方式,而 Lambda 表达式则会报错,迫使你思考更函数式的处理方式,或者选择其他设计。

3. 对新手不友好: 对于不熟悉 Lambda 表达式语法或者函数式编程概念的开发者来说,一开始接触 Lambda 可能会觉得有些陌生,需要一个适应过程。而匿名内部类,作为 JavaOOP 的一种传统表达方式,对很多开发者来说更加熟悉。

总结来说,Lambda 表达式在处理函数式接口、进行集合操作(特别是与 Stream API 结合)等场景时,通过大幅减少样板代码,让代码更聚焦于核心逻辑,从而在可读性上往往优于匿名内部类。它提供了一种更简洁、更声明式的表达方式,能够提升代码的流畅性和表达力。

但是,当行为的逻辑变得复杂,需要多个语句、循环、复杂的条件判断,或者需要修改外部状态时,过长的 Lambda 表达式可能会牺牲可读性,此时,结构清晰的匿名内部类,甚至命名的方法(如果可以的话),可能成为更好的选择。

最终,哪种方式“更好”,还需要结合具体的代码场景、开发团队的熟悉程度以及对“可读性”的定义来判断。但就现代 Java 开发中常见的函数式操作而言,Lambda 表达式无疑是一个强大的工具,能够显著提升代码的优雅度和效率。

网友意见

user avatar

题主问的是“lambda表达式”与“匿名内部类”相比,那当然是大大改善了可读性。

在Java语言层面还没有lambda表达式的时代,经过FP思想开化的程序员们早就开始用匿名内部类来做类似的事情了。但就是略别扭。

例如我以前在淘宝写Java代码经常会用google-collections / Guava,就看Guava官方文档的一段例子吧:

code.google.com/p/guava
       Multiset<Integer> lengths = HashMultiset.create(   FluentIterable.from(strings)     .filter(new Predicate<String>() {        public boolean apply(String string) {          return CharMatcher.JAVA_UPPER_CASE.matchesAllOf(string);        }      })     .transform(new Function<String, Integer>() {        public Integer apply(String string) {          return string.length();        }      }));      

这就是用匿名内部类来当FP风格的回调用。

如果用Java 8的话,这段代码可以直译为:

       Multiset<Integer> lengths = HashMultiset.create(   FluentIterable.from(strings)     .filter(string -> CharMatcher.JAVA_UPPER_CASE.matchesAllOf(string))     .transform(string -> string.length()));      

这好歹一眼就能看出来程序的意图是什么,而不会被匿名内部类的“类型一行、方法名一行,真正方法内容一行”的这种仪式给干扰到。

而完全融合进Java 8风格的API的话,上面代码会变成类似这样:

       Multiset<Integer> lengths =   strings.stream()     .filter(string -> CharMatcher.JAVA_UPPER_CASE.matchesAllOf(string))     .map(string -> string.length())     .toHashMultiSet();     

整个代码顺序就跟数据流动的顺序一致,意图也就更明确了。

至于这种适用lambda表达式 / 匿名内部类的风格的API好不好,那是另一个问题。

而lambda表达式会隐藏类型这点,不喜欢的话可以自己把类型全部写上去;lambda表达式的类型推导只是给了Java程序员一个选择,萝卜青菜反正自己挑。

前面

@戴威

的回答里的代码例子:

       File[] pics = folder.listFiles(   f ->      !f.isDirectory() &&      PICTURE_SUFFIX_LIST.stream().filter(       s ->          f.getName().toUpperCase().endsWith(s)).count() > 0);      

其实重新格式化并适用更合适的API来写的话,可以是:

       // File[] pics = folder.listFiles(   file -> !file.isDirectory() &&           PICTURE_SUFFIX_LIST.stream()           .anyMatch(suffix -> file.getName().toUpperCase().endsWith(suffix)) );     

至少对我来说它的意图非常明确,比人肉循环filter好多了。

user avatar

可读性主要取决于写代码的人,而非语言特性。

就拿上面的例子来说:

       File[] pics = folder.listFiles(   f ->      !f.isDirectory() &&      PICTURE_SUFFIX_LIST.stream().filter(       s ->          f.getName().toUpperCase().endsWith(s)).count() > 0);      

我对这里面出现的Java的库函数一窍不通,但是我基本上还是能看明白这段代码在干什么。

唯一要费脑子的地方还是这段代码的缩进导致的问题,一般来说,我会写成这样:

       File[] pics = folder   .listFiles( f -> !f.isDirectory() &&      PICTURE_SUFFIX_LIST     .stream()     .filter( s -> f.getName().toUpperCase().endsWith(s) )     .count() > 0   );      

很明显这段代码的意图是非常清楚的

listFIles猜测为列出文件,f是文件,isDirectory是判断是否是目录,后面的stream.filter.count其实就是个Any。

所以意图就非常明确了,取出所有文件,这个文件不是目录,并且以PICTURE_SUFFIX_LIST里面某个字符串结尾。

RFX大大指出,Java也有anyMatch,所以这里可以进一步改写为:

       File[] pics = folder   .listFiles( f -> !f.isDirectory() &&      PICTURE_SUFFIX_LIST.stream()     .anyMatch( s -> f.getName().toUpperCase().endsWith(s) )   );      


事实上这段代码我在整理格式后已经用大脑直接转换成C#样式了:

       File[] pics = folder   .listFiles( f => f.IsDirectory == false &&      PICTURE_SUFFIX_LIST.Any( s => f.Name.EndsWith( s, StringComparision.IgnoreCaseOrdinal ) )    );      


而通常这种逻辑我会写成:

       var pics = from file in folder.EnumerateFiles()            where PictureFileExtensions.Contains( file.Extension )            select file;      

或者:

       var pics = folder.EnumerateFiles()            .Where( file => PictureFileExtensions.Contains( file.Extension );      

类似的话题

  • 回答
    Lambda 表达式是否比匿名内部类更具可读性,这确实是一个值得深入探讨的问题,而且答案并非一概而论,而是取决于具体的场景和使用者的熟悉程度。不过,在大多数情况下,特别是在处理函数式接口(functional interfaces)的时候,Lambda 表达式确实能够以一种更简洁、更直观的方式来表达.............
  • 回答
    理解Java 8 Stream API和C LINQ在性能上的差异,关键在于它们的底层实现机制和设计哲学。简单地说,不存在绝对的“哪个更慢”,而是取决于具体的应用场景、数据规模以及开发者如何使用它们。 但如果非要进行一个概括性的对比,可以从以下几个角度深入剖析:1. 底层实现与抽象级别: Jav.............
  • 回答
    这则消息,“8 万行的 Python 程序用 4 万行 Java 重写了”,乍看之下,似乎在说 Java 的效率更高,或者 Python 的代码“膨胀”了。但实际上,它背后可能隐藏着更复杂、更值得深思的几个层面的信息:1. 语言特性与表达力差异的直观体现:最直接的理解是,Java 在某些场景下,能够.............
  • 回答
    你这个问题问得非常到位,而且触及到了计算机底层表示浮点数的一个核心概念。说 C++ 的 `double` 类型存不下 3.1415926,其实是一种误解,或者说表述不够准确。更准确的说法应该是:C++ (和 Java 的) `double` 类型,虽然是 8 个字节(64 位),但由于浮点数在计算机.............
  • 回答
    Java 8 引入了接口的 `default` 方法,这确实是一个革命性的变化,让接口的功能变得更加强大。很多人因此产生一个疑问:有了 `default` 方法,我们是不是就可以彻底告别抽象类了?我的回答是:不完全是,但绝大多数情况下,接口的 `default` 方法可以很好地替代抽象类,并且在某些.............
  • 回答
    朋友,你这个问题问得相当到位,可以说是触及了软件开发领域一个非常普遍但又值得深思的现象。Java 18 离我们并不算远,但 1.8 依然活跃在无数的生产环境中,这背后可不是三言两语能说清的。这背后牵扯到的不仅仅是技术本身,还有历史、商业、团队协作、风险控制等等方方面面。咱们就来掰扯掰扯,为什么都快 .............
  • 回答
    你好!首先恭喜你顺利毕业并开启了职业生涯!你目前遇到的情况,即从 Java 培训到前端工作,再到转做项目接口人,这在IT行业并不少见,尤其是在职业初期。感到迷茫是完全正常的,这说明你在认真思考自己的职业发展方向。下面我将为你详细分析你的情况,并提供一些建议,希望能够帮助你理清思路。 1. 理解你的背.............
  • 回答
    在 Java 中,当一个线程调用了 `Thread.interrupt()` 方法时,这并不是像直接终止线程那样强制停止它。相反,它是一个通知机制,用于向目标线程发出一个“中断请求”。这个请求会标记目标线程为“中断状态”,并根据目标线程当前所处的状态,可能会触发一些特定的行为。下面我将详细解释 `T.............
  • 回答
    Java 平台中的 JVM (Java Virtual Machine) 和 .NET 平台下的 CLR (Common Language Runtime) 是各自平台的核心组件,负责托管和执行代码。它们都是复杂的软件系统,通常会使用多种编程语言来构建,以充分发挥不同语言的优势。下面将详细介绍 JV.............
  • 回答
    Java 官方一直以来都坚持不在函数中提供直接的“传址调用”(Pass by Address)机制,这背后有深刻的设计哲学和技术考量。理解这一点,需要从Java的核心设计理念以及它所解决的问题出发。以下是对这个问题的详细阐述: 1. Java 的核心设计理念:简洁、安全、面向对象Java 在设计之初.............
  • 回答
    Java 的 `private` 关键字:隐藏的守护者想象一下,你在经营一家精心制作的糕点店。店里最美味的招牌蛋糕,其配方是成功的关键,你自然不会轻易公开给竞争对手,对吧?你只希望自己信任的糕点师知道如何制作,并且知道在什么时候、以什么样的方式使用这些食材。这就是 `private` 关键字在 Ja.............
  • 回答
    Java 在引入泛型时,虽然极大地提升了代码的类型安全和可读性,但严格来说,它并没有实现我们通常理解的“真正意义上的”泛型(相对于一些其他语言,比如 C++ 的模板)。这其中的核心原因可以追溯到 Java 的设计理念和对向后兼容性的考量,具体可以从以下几个方面来详细阐述:1. 类型擦除 (Type .............
  • 回答
    这个问题很有意思!“360 垃圾清理”这个概念,如果用在 Java 的世界里,就好像是问:“为什么 Java 的垃圾回收机制,不像我们电脑上安装的 360 软件那样,主动去到处扫描、删除那些我们认为‘没用’的文件?”要弄明白这个,咱们得先聊聊 Java 的垃圾回收,它其实是个非常聪明且有组织的过程,.............
  • 回答
    好的,咱们来聊聊 Java 内存模型(JMM)和 Java 内存区域(Java Memory Areas)这两个既熟悉又容易混淆的概念。别担心,我会尽量用大白话讲明白,就像跟朋友聊天一样,不搞那些虚头巴脑的术语。想象一下,咱们写 Java 代码,就像是在指挥一个庞大的工厂生产零件。这个工厂有很多车间.............
  • 回答
    在 Java 泛型中,`` 和 `` 语法看起来相似,但它们代表的是截然不同的类型关系和使用场景。理解它们之间的差异,关键在于把握 Java 泛型中的“生产者消费者模型”以及它们对类型参数的“协变性”和“逆变性”的支持。我们一步一步来拆解,让你彻底明白 `super` 的含义,以及它与 `exten.............
  • 回答
    想知道 Java 学到什么程度才算精通,这确实是个挺实在的问题,也挺难有个标准答案。不过,咱可以从几个维度来聊聊,看看什么样的人,在别人看来算是玩明白了 Java。首先,得承认,所谓的“精通”这词儿,多少有点玄乎。没人敢说自己是绝对的精通,毕竟技术发展那么快,总有新鲜玩意儿冒出来。但如果说你能把 J.............
  • 回答
    作为一名Java程序员,想要在职业生涯中走得更远,确实需要掌握那些真正核心、最常用的技术。这就像学武功,要先练好基本功,才能去钻研那些花哨的招式。我个人在多年的开发实践中,总结出了一套“二八定律”式的技术认知,下面我就把这些我认为最关键的20%技术,尽可能详实地分享给大家,力求让这篇文章充满实在的干.............
  • 回答
    想要转战 Android 开发,对于 Java 的掌握程度,我更倾向于从“能解决实际问题”的角度来看待,而不是一个死板的“级别”。你想啊,我们做开发最终目的都是为了产出有价值的东西,而不是为了考一个 Java 等级证书。所以,如果非要给一个大致的界定,我认为你可以开始准备转战 Android 了,当.............
  • 回答
    好,咱就掰扯掰扯java为啥对泛型数组这事儿这么“矫情”,不直接给你整明白。这事儿啊,说起来也算是一段公案,得从java这门语言设计之初,以及它如何处理类型安全这件大事儿上头说起。核心矛盾:类型擦除与运行时类型检查的冲突你得明白java的泛型,尤其是泛型数组这块儿,最大的“绊脚石”就是它的类型擦除(.............
  • 回答
    Java 分布式应用入门指南:从零开始构建稳健的系统想要踏入 Java 分布式应用开发的大门?别担心,这并非遥不可及的挑战。相反,它是一个充满机遇和成长的领域。本文将带你系统地梳理分布式应用的核心概念,并为你推荐一系列实用的学习资料,帮助你从新手蜕变为一名合格的分布式开发者。 一、 理解分布式应用的.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有