写点什么

从 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:547004

评论

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

即时通讯技术文集(第10期):IM通信协议该选TCP还是UDP [共12篇]

JackJiang

人工智能打造充满创造力的新世界,华为云开发者日无锡站成功举办

Geek_2d6073

五分钟带你学会微服务熔断原理分析与源码解读

Java你猿哥

微服务 ssm 微服务实战 微服务熔断

三大升级!百度智能云加速文心一言产业化落地

百度开发者中心

#人工智能 文心一言

火山引擎DataTester:抖音的设计团队是如何用A/B测试实现高效优化的?

字节跳动数据平台

大数据 AB testing实战 抖音 A/B 测试 企业号 3 月 PK 榜

使用 Metabase 连接 Databend Cloud 实现大屏展示

Databend

视频编辑场景下的文字模版技术方案

百度Geek说

企业号 3 月 PK 榜 视频编辑 端渲染 富文本素材

【数仓运维实践】关于GaussDB(DWS)单SQL磁盘空间管控

华为云开发者联盟

数据库 后端 华为云 华为云开发者联盟 企业号 3 月 PK 榜

软件测试/测试开发丨app自动化测试之Appium WebView 技术原理

测试人

软件测试 自动化测试 测试开发 appium

软件测试/测试开发丨只懂黑盒测试也能学会的代码覆盖率及精准化测试

测试人

软件测试 自动化测试 精准测试 测试开发 代码覆盖率

C#/VB.NET:如何将PDF转为PDF/A

在下毛毛雨

C# .net PDF 文档转换 PDF/A

医疗卫生机构等保测评法律依据有哪些?多久要测评一次?

行云管家

医疗 等保

精品!阿里P7爆款《K8s+Jenkins》技术笔记,高质量干货必收藏

做梦都在改BUG

Java Kubernetes k8s jenkins

独家Java架构师题,面试再不过找我来要赔偿,谢谢

Java你猿哥

架构 面试 ssm 面经 java实战

JDK20正式发布了GA版本,短期维护支持,以及JDK21预览

小小怪下士

Java 程序员 jdk 后端

直播|SeaTunnel 与 StarRocks 生态融合--让大数据处理回归「简单」

StarRocks

数据库 数据库·

阿里是如何使用分布式架构的?阿里内部学习手册分享

Java你猿哥

分布式 ssm 分布式架构 分布式实战

狂刷《Java权威面试指南(阿里版)》,冲击“金三银四”有望了

做梦都在改BUG

Java java面试 Java八股文 Java面试题 Java面试八股文

使用 CnosDB 与 TensorFlow 进行时间序列预测

CnosDB

tensorflow 时序数据库 时间序列预测 CnosDB

大顶堆的实现(基于数组存储的完全二叉树)

Java你猿哥

Java 二叉树 ssm 实战

喜讯:祝贺行云绽放荣获深圳市专精特新企业称号

行云管家

云计算 深圳 专精特新

镜舟数据库与用友 YonBIP 完成兼容性认证,携手赋能企业数智化发展

镜舟科技

数据库

百度智能云将在3月27日发布系列文心一言云服务和应用产品

百度开发者中心

#人工智能 文心一言

捷讯!索信达中标光大银行“线上流量经营模型工厂”项目

索信达控股

MobPush Android SDK合规指南

MobTech袤博科技

模块9作业

梁山伯

云智一体,深入生命科学

百度开发者中心

云智一体 生命科学 #人工智能

软件工程中建模的底层逻辑

阿里技术

软件工程 建模

对话 BitSail Contributor | 吴畅:从好奇,到深入

字节跳动数据平台

大数据 开源 开发者 数据集成 企业号 3 月 PK 榜

知乎三天点击破亿!四天精通springcloud微服务架构

Java你猿哥

微服务 微服务架构 Spring Cloud 从分层架构到微服务架构

详解MyBatis加载映射文件和动态代理

做梦都在改BUG

Java mybatis

从Groovy到Java 8_Java_Dan Woods_InfoQ精选文章