2025上半年,最新 AI实践都在这!20+ 应用案例,任听一场议题就值回票价 了解详情
写点什么

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

评论

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

清华大学AutoDroid-V2,软件测试行业将如何发展

测试人

软件测试

集成指南 | 如何使用融云鸿蒙 IMKit 高效开发社交模块

融云 RongCloud

为什么说ERP做不好MES的功能?

万界星空科技

ERP mes 万界星空科技 生产管理软件 小工单

强大的电池管理软件AlDente Pro Mac激活版

小玖_苹果Mac软件

EloqKV---一款全新架构的高性能分布式KV数据库

晨章数据

数据库、

豆包语音大模型首家引领级通过中国信通院语音大模型评估

极客天地

区块链智能合约外包开发流程

北京木奇移动技术有限公司

智能合约 区块链技术 软件外包公司

淘宝店铺详情API接口的开发、应用与收益

科普小能手

数据挖掘 数据分析 淘宝 电商 API 接口

区块链智能合约开发需要注意的问题

北京木奇移动技术有限公司

区块链技术 智能合约开发 软件外包公司

DNS DDoS攻击是怎么回事?怎么预防?

国科云

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题

HarmonyOS开发者

DevEco Studio

《CPython Internals》阅读笔记:p250-p284

codists

CPython Internals

Grafana 统一可视化了,告警如何统一?

巴辉特

FlashDuty 统一告警

融云鸿蒙 IMKit 集成指南

融云 RongCloud

PDF编辑阅读转换器PDF Expert for Mac中文激活版

小玖_苹果Mac软件

AI应用专家的实战经验:如何通过智能工具实现博客的高效数字化转型——慢慢学AI140

AI决策者洞察

人工智能’ Prompt

充电桩测试系统(源码+文档+部署+讲解)

深圳亥时科技

用DevEco Studio性能分析工具 高效解决鸿蒙原生应用内存问题

HarmonyOS开发者

DevEco Studio

档案管理系统(源码+文档+部署+讲解)

深圳亥时科技

产品溯源管理系统(源码+文档+部署+讲解)

深圳亥时科技

AI助力中小微外贸企业腾飞!XTransfer荣膺2024 AIGC赋能行业创新引领者!

XTransfer技术

数智化风控最佳实践!XTransfer获数智技术领航企业奖

XTransfer技术

从Groovy到Java 8_Java_Dan Woods_InfoQ精选文章