写点什么

从 Groovy 到 Java 8

  • 2013-09-18
  • 本文字数:5084 字

    阅读完需:约 17 分钟

Groovy 开发人员早已熟知 Java 8 中新引入的概念和新的语言结构了。在 Java 新版本即将推出的增强特性中,有很多是 Groovy 在几年前就已经提供了的。从用于函数式编程风格的新语法,到 lambdas 表达式、collection streaming 和要把方法引用作为一等公民,Groovy 开发人员在未来编写 Java 代码时具有先天性优势。本文将重点关注 Groovy 和 Java 8 的共同点,并阐述了 Java 8 如何解读 Groovy 中那些熟悉的概念。

我们先来讨论一下函数式编程风格,目前在 Groovy 中如何使用函数式编程,Java 8 的概念如何提供更好的函数式编程风格。

闭包(Closures)也许是 Groovy 中最好的函数式编程实例了。从内部结构来看,Groovy 中的 closure 只是一个函数式接口实现。函数式接口是指任意只需要实现一个方法的接口。默认情况下,Groovy 的 closure 实现了一个名为“Callable”的函数式接口,实现了这个接口的“call”方法。

复制代码
def closure = {
"called"
}
assert closure instanceofjava.util.concurrent.Callable
assert closure() == "called"

通过转换 closure 的类型,我们可以让 Groovy 实现其他函数式接口。

复制代码
public interface Function {
def apply();
}
def closure = {
"applied"
} as Function
assert closure instanceof Function
assert closure.apply() == "applied"

在 Java 8 中很好地引入了闭包和函数式编程的思想。在 Java 即将发布的版本中函数式接口极为重要,因为在 Java 8 中针对新引入的 Lambda 函数式接口提供了隐含的实现。

我们可以把 Lambda 函数当成 Groovy 中的闭包那样去理解和使用。在 Java 8 中实现 callable 接口像 Groovy 中的闭包一样简单。

复制代码
Callable callable = () -> "called";
assert callable.call() == "called";

你需要特别注意是,Java 8 为单行的 lambda 函数提供了隐含的返回语句,后来 Groovy 也借鉴了这个概念。将来,Groovy 也会为单个抽象方法提供隐含实现(类似于 Java 8 提供的那些实现)。这个特性使你不必完全派生出 closures 的具体子类对象就可以使用实例的属性和方法。

复制代码
abstract class WebFlowScope {
private static final Map scopeMap = [:]
abstractdefgetAttribute(def name);
publicdef put(key, val) {
scopeMap[key] = val
getAttribute(key)
}
protected Map getScope() {
scopeMap
}
}
WebFlowScope closure = { name ->
"edited_${scope[name]}"
}
assert closure instanceofWebFlowScope
assert closure.put("attribute", "val") == "edited_val"

Java 8 针对带有接口默认方法的函数式接口提出了一个类似的概念,即 Java 的新概念“接口默认方法”。他们希望借此概念在不违反接口实现规约(在 Java 之前的版本中建立的实现规约)的前提下改进核心的 API。

当把 Lambda 函数强制转型为接口时,它们也可以使用接口的默认方法。也就是说在接口中可以内置健壮的 API,使开发人员不必改变类型的种类或规约就可以使用这些 API。

复制代码
public interface WebFlowScope {
static final Map scopeMap = new HashMap();
Object getAttribute(Object key);
default public Object put(Object key, Object val) {
scopeMap.put(key, val);
return getAttribute(key);
}
default Map getScope() {
return scopeMap;
}
}
static final WebFlowScope scope = (Object key) ->
"edited_" + scope.getScope().get(key);
assert scope.put("attribute", "val") == "val";

Java 8 中的接口默认方法还可以帮我们实现像 memoization 和 trampolining 这样的 Groovy 特性。你可以很简单就实现 memoization 特性,只需要创建一个带有接口默认方法的函数式接口,并实现这个默认方法让它从缓存中确定估算结果或返回结果就可以了。

复制代码
public interface MemoizedFunction<T, R> {
static final Map cache = new HashMap();
R calc(T t);
public default R apply(T t) {
if (!cache.containsKey(t)) {
cache.put(t, calc(t));
}
return (R)cache.get(t);
}
}
static final MemoizedFunction<Integer, Integer> fib
= (Integer n) -> {
if (n == 0 || n == 1) return n;
return fib.apply(n - 1)+fib.apply(n-2);
};
assert fib.apply(20) == 6765;

同样,我们还可以使用 Java 8 的接口默认方法开发 Trampoline 的实现。Trampoline 是 Groovy 的一种递归策略,这个特性非常适用于深度递归,而不可能取代 Java 的调用栈。

复制代码
interfaceTrampolineFunction<T, R> {
R apply(T...obj);
public default Object trampoline(T...objs) {
Object result = apply(objs);
if (!(result instanceofTrampolineFunction)) {
return result;
} else {
return this;
}
}
}
// Wrap the call in a TrampolineFunction so that
we can avoid StackOverflowError
static TrampolineFunction<Integer, Object>
fibTrampoline = (Integer...objs) -> {
Integer n = objs[0];
Integer a = objs.length>= 2 ? objs[1] : 0;
Integer b = objs.length>= 3 ? objs[2] : 1;
if (n == 0) return a;
else return fibTrampoline.trampoline(n-1, b, a+b);
};

除了 closures 的基本特性以及那些 Memoization 和 Trampolining 的高级特性,Groovy 还为 Collections API 提供了一些有巨大实用价值的语言扩展。我们在使用 Groovy 时可以充分利用这些扩展点,比如用 list 的“each”方法非常简捷地完成写操作。

复制代码
def list = [1, 2, 3, 4]
list.each { item ->
println item
}

Java 8 针对集合的迭代引入了一种与 Groovy 类似的概念,提供了一个与“each”相似的“forEach”方法,可以用它取代 list 传统的迭代方式。

复制代码
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.forEach( (Integer item) ->System.out.println(item); );

除了简化 list 的迭代,Groovy 还为应用开发人员提供了各种快捷写法以简化各类 list 操作。比如“collect”方法,你用这个方法可以将 list 元素快速映射为新的类型(或新的值),然后把结果放入新的 list 里。

复制代码
def list = [1, 2, 3, 4]
defnewList = list.collect { n -> n * 5 }
assert newList == [5, 10, 15, 20]

在 Groovy 中“collect”的实现比较简单,你只需要把映射当作一个参数传递给“collect”方法。但是,Java 8 的实现就稍微有点复杂了,开发人员可以使用 Java 8 的 StreamAPI 实现同样的映射和收集策略,实现时要调用“list”的“stream”组件的“map”方法,然后再调用“map”方法返回的“stream”的“collect”方法。开发人员可以这样连续使用 Stream API 完成 list 一连串的操作。

复制代码
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
List<Integer>newList = list.stream().map((Integer n) -> n * 5).collect(Collectors<br></br>.toList());
assert newList.get(0) == 5 &&newList.get(1) == 10
&&newList.get(2) == 15 &&newList.get(3) == 20;
{1}

Groovy 还能让开发人员使用“findAll”方法简捷地筛选 list。

复制代码
def emails = ['danielpwoods@gmail.com', 'nemnesic@gmail.com',
'daniel.woods@objectpartners.com', 'nemnesic@nemnesic.com']
defgmails = emails.findAll { it.endsWith('@gmail.com') }
assert gmails = ['danielpwoods@gmail.com', 'nemnesic@gmail.com']

同样地,Java 8 开发人员可以使用 Stream API 筛选 list。

复制代码
List<String> emails = new ArrayList<>();
emails.add("danielpwoods@gmail.com");
emails.add("nemnesic@gmail.com");
emails.add("daniel.woods@objectpartners.com");
emails.add("nemnesic@nemnesic.com");
List<String>gmails = emails.stream().filter(
(String email) ->email.endsWith("@gmail.com") ).collect(Collectors.toList());
assert gmails.get(0) == "danielpwoods@gmail.com"
&&gmails.get(1) == "nemnesic@gmail.com";

Groovy Collections API 扩展还提供了一个“sort”方法,你使用这个方法可以简单地完成对 list 的排序。“sort”方法还可以接受闭包参数,你可以在闭包中实现所需的特定排序逻辑,闭包会被转为比较器后完成对 list 的排序。另外,如果只需要对 list 进行简单地逆序排序,可以调用“reverse”方法反转 list 的顺序。

复制代码
def list = [2, 3, 4, 1]
assert list.sort() == [1, 2, 3, 4]
assert list.sort { a, b -> a-b <=> b } == [1, 4, 3, 2]
assert list.reverse() == [2, 3, 4, 1]

再来看 Java 8 的 Stream API,我们可以使用“sorted”方法对 list 排序,然后用“toList”方法收集排序结果。“sorted”方法也支持自定义的比较器,它有一个可选的函数式参数(比如 Lambda 函数),你可以将自定义的比较器作为参数传给方法,就可以很容易地实现特定的排序逻辑和反转 list 条目的操作了。

复制代码
List<Integer> list = new ArrayList<>();
list.add(2);
list.add(3);
list.add(4);
list.add(1);
list = list.stream().sorted().collect(Collectors.toList());
assert list.get(0) == 1 &&list.get(3) == 4;
list = list.stream().sorted((Integer a, Integer b) <br/>->Integer.valueOf(a-
b).compareTo(b)).collect(Collectors.toList());
assert list.get(0) == 1 &&list.get(1) == 4 &&list.<br/>get(2) == 3 &&list.get(3) == 2;
list = list.stream().sorted((Integer a, Integer b) <br/>->b.compareTo(a)).collect<br></br>(Collectors.toList());
assert list.get(0) == 2 &&list.get(3) == 1;

如果你试图在一个闭包或 Lambda 函数内完成所有的处理而连续调用 API(比如 list streaming),那么很快就会使代码难以维护。换一个角度来看,如果你要委托相应工作单元特定的方法完成特定的处理,那么这种用法就是一个不错的选择了。

我们使用 Groovy 时,把方法引用传给函数也可以实现上面所说的目标。你只要使用“.&”操作符去引用方法,就可以把该方法强制转型为闭包传给另一个方法了。由于可以从外部源码引入过程代码,就从本质上提高了实现的灵活性。这样,开发人员就可以在逻辑上组织处理方法,完成更易维护、可持续演进的应用架构了。

复制代码
def modifier(String item) {
"edited_${item}"
}
def list = ['item1', 'item2', 'item3']
assert list.collect(this.&modifier) == ['edited_item1'
, 'edited_item2', 'edited_item3']

Java 8 也为开发人员提供了同样的灵活性,使开发人员可以使用“::”操作符获得方法的引用。

复制代码
List<String> strings = new ArrayList<>();
strings.add("item1");
strings.add("item2");
strings.add("item3");
strings = strings.stream().map(Helpers::modifier).
collect(Collectors.toList());
assert "edited_item1".equals(strings.get(0));
assert "edited_item2".equals(strings.get(1));
assert "edited_item3".equals(strings.get(2));

你可以把方法引用传给任意以函数式接口为形参的方法。那么,这个方法就会被转型为函数式接口,作为函数式接口执行。

复制代码
public interface MyFunctionalInterface {
boolean apply();
}
void caller(MyFunctionalInterfacefunctionalInterface) {
assert functionalInterface.apply();
}
booleanmyTrueMethod() {
return true;
}
caller(Streaming::myTrueMethod);

在 Java 8 里,如果类库开发人员修改了接口规约,那么这些接口的使用者不必为了这些变更去修改那些使用了这个类库的接口。

这些概念和编程风格的无缝转化是从 Groovy 到 Java 8 的一次具有重要意义的过渡。Groovy 为了提高内部灵活性和改进 Java 原有的 API,使用了大量的 JVM 空间。随着这些改进在 Java 8 里生根发芽,意味着两种语言将有更多的相同点、而不同点会越来越少,事实上这正是本文要介绍的主要内容。当学习和使用这些新 API、新特性和新概念时(从 Java 8 引入到 Java 生态系统中),熟练的 Groovy 开发人员只需要更短的学习曲线。

本文作者

Daniel Woods**** 是Object Partners 有限公司的一名高级顾问。他专门从事于 Groovy 和 Grails 应用架构的研究,对 Java 和其他基于 JVM 的语言一直抱有浓厚的兴趣。它是一名开源贡献者,并出席了 Gr8Conf 和 SpringOne 2GX 的本年度年会。可以通过电子邮件( danielpwoods@gmail.com )或 Twitter (@danveloper)与 Daniel 取得联系。

查看英文原文: From Groovy to Java 8


感谢侯伯薇对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2013-09-18 04:547099

评论

发布
暂无评论
发现更多内容

福昕软件与北信源达成战略合作,构建PDF文档安全新格局

新消费日报

2023-06-26:在大小为 n x n 的网格 grid 上,每个单元格都有一盏灯,最初灯都处于 关闭 状态 给你一个由灯的位置组成的二维数组 lamps 其中 lamps[i] = [rowi,

福大大架构师每日一题

Go 算法 rust 福大大架构师每日一题

云游世界NFT卷轴游戏dapp系统开发合约定制

开发微hkkf5566

【FAQ】关于华为地图服务定位存在偏差的原因及解决办法(二)——仅适用于Location 6.7.0.300及之后的版本

HarmonyOS SDK

HMS Core

一文梳理清楚低代码开发平台

高端章鱼哥

低代码 低代码开发平台 可视化开发

聊聊信仰的力量

互联网工科生

程序员 信仰

使用 ProcessBuilder API 优化你的流程

快乐非自愿限量之名

数据库 框架开发 开发语言 process

[Paddle] ERNIE-Layout 使用测试 - 文心多语言跨模态布局增强文档智能大模型

alexgaoyh

paddle engine-layout 电子文档 提取

软件测试/测试开发丨Git常用命令学习笔记

测试人

git 程序员 软件测试 测试开发

纵横贯通的全面预算管理体系,从这里开始!

用友BIP

全面预算

低代码开发平台:解锁无限创新,演绎科技新奇

EquatorCoco

低代码 科技创新

焱融构建多云数据存储平台 重塑云中数据活力

焱融科技

文件存储 高性能存储 #分布式文件存储

手把手教你实战TDD

快乐非自愿限量之名

TDD 开发框架

云原生时代数据治理的变革与创新

酷克数据HashData

软件开发不需要会编码,谈谈我的开发捷径

互联网工科生

软件开发 低代码 开发

从2PC和容错共识算法讨论zookeeper中的Create请求 | 京东云技术团队

京东科技开发者

zookeeper 分布式事务 zookeeper分布式锁 企业号 6 月 PK 榜 容错共识算法

6个常见的IB网络不通问题

华为云开发者联盟

后端 开发 华为云 华为云开发者联盟 企业号 6 月 PK 榜

JavaScript 实践+理论(总结篇):作用域、闭包、this、对象原型

Immerse

闭包 原型链 作用域 this 原型模式

【直播预告】HarmonyOS极客松赋能直播第二期:数据库与网络连接开发

HarmonyOS开发者

HarmonyOS

Web3架构之思 - Direc Exhcange Mesh 项目介绍(一)

Joe

Web 3.0 分布式结构 多运行时

看数智底座如何助力大型车企向世界一流汽车企业迈进

用友BIP

数智底座 Pass平台

这问题巧了,SpringMVC 不同参数处理机制引发的思考 | 京东云技术团队

京东科技开发者

spring springmvc HTTP 请求方式 企业号 6 月 PK 榜

报名开启!2023大模型应用创新挑战赛来啦

飞桨PaddlePaddle

人工智能 百度 paddle

聊聊缓存世界的扫地僧

勇哥java实战分享

修改SD WebUI为kitchen-theme

IT蜗壳-Tango

前端文件上传的几种交互造轮子 | 京东云技术团队

京东科技开发者

前端 Vue3 文件上传 文件上传下载 企业号 6 月 PK 榜

低代码平台的核心能力有哪些?

力软低代码开发平台

用代码玩转迷你图:手把手教你用编程语言打造简洁易读的数据图表!

不在线第一只蜗牛

代码 框架开发

千亿资产去哪了,资产管理如何摸清家底?

用友BIP

资产云

一套管理工具构建敏捷、多维、高效的全面预算管理

用友BIP

全面预算

从Groovy到Java 8_Java_Dan Woods_InfoQ精选文章