阅读更多

4顶
0踩

编程语言

转载新闻 为什么 Java 8 存在接口污染?

2014-05-04 12:24 by 副主编 WnouM 评论(0) 有7935人浏览

Java 8的缺陷》作者Lukas Edger在文中提到,在JDK8中函数类型不是简单地被称作函数类型,这一点显得很糟糕。举个例子,C#有一系列预先定义的函数类型,它们接受任意个数的参数和一个可选返回类型(FuncAction函数都有高达16个不同类型的参数T1,T2,……T16)。但是在JDK 8中,有一系列带有不同的名称、方法名称的函数式接口,它们的抽象方法代表着人们熟知函数签名的子集(像nullary, unary, binary, ternary等)。

类型擦除问题

因此在某种程度上,两种语言都受害于接口污染(或是C#中的代理污染)。唯一的不同点是在C#中它们有同样的名称。不幸的是,由于Java中的类型擦除使得Function<T1,T2> , Function<T1,T2,T3> ,Function<T1,T2,T3,…Tn>之间没有差异,所以,显然,我们不能简单地以同一个名称来命名它们,不得不为函数组合的所有可能类型想出有创意的名字。

不要认为专家组没有纠结过这个问题。作者Brian Goetz在lambda邮件列表中说道:

引用
……作为一个单独的例子,让我们考虑下函数类型。在devoxx大会上的lambda strawman提案(有人译作lambda稻草人提案,译者注)是支持函数类型的。我坚持删掉它们,这使我不受欢迎。不过,我对函数类型的异议并不意味着我不喜欢函数类型 — 我爱函数类型 –但是函数类型与Java类型体系中已存在的类型擦除发生严重冲突。擦除函数类型在二者存在的地方是最糟糕的。所以我们把它从设计中移除掉。

但我不会说的“Java永远不会有函数类型”(虽然我承认,Java的可能永远不会有函数类型)。为了支持函数类型,我认为必须首先处理好类型擦除。这件事可能做好,也可能无法完成。但在具体化结构类型的世界中,函数类型开始变得更有意义……



那么,它如何影响到我们这些开发人员呢?下面是JDK8中一些最重要的新函数式接口(包括一些旧的)的分类,这些类目通过函数的返回类型和接口方法的预期参数数目组织起来的。

返回类型为void的函数

在返回类型为void的函数中,如下:

函数类型
LAMBDA表达式
已知函数接口
Nullary () -> doSomething() Runnable
Unary foo  -> System.out.println(foo)

Consumer

IntConsumer

LongConsumer

DoubleConsumer

Binary (console,text) -> console.print(text)

BiConsumer

ObjIntConsumer

ObjLongConsumer

ObjDoubleConsumer

n-ary (sender,host,text) -> sender.send(host, text) Define your own



返回类型为T的函数

在返回类型为T的函数中,如下:

函数类型
LAMBDA表达式
已知函数接口
Nullary () -> ”Hello World”

Callable

Supplier

BooleanSupplier

IntSupplier

LongSupplier

DoubleSupplier

Unary n -> n + 1 n -> n >= 0

Function

IntFunction

LongFunction

DoubleFunction

IntToLongFunction

IntToDoubleFunction

LongToIntFunction

LongToDoubleFunction

DoubleToIntFunction

DoubleToLongFunction

UnaryOperator

IntUnaryOperator

LongUnaryOperator

DoubleUnaryOperator

Predicate

IntPredicate

LongPredicate

DoublePredicate

Binary (a,b) -> a > b ? 1 : 0(x,y) -> x + y (x,y) -> x % y == 0

Comparator

BiFunction

ToIntBiFunction

ToLongBiFunction

ToDoubleBiFunction

BinaryOperator

IntBinaryOperator

LongBinaryOperator

DoubleBinaryOperator

BiPredicate

n-ary (x,y,z) -> 2 * x + Math.sqrt(y) – z Define your own



这种方式的优点是,我们可以定义自己的接口类型,让函数接受尽可能多的参数。需要时,我们可以用它们来创建的lambda表达式和方法引用。换句话说,我们能够利用更多的新函数式接口污染整个程序。此外,我们甚至可以在早期版本JDK的接口,或更早版本的自定义的单抽象函数类型的API中创建lambda表达式。所以,现在我们可以把Runnable的和Callable 接口当作函数式接口使用。

然而,这些接口变得更加难以记忆,因为它们都具有不同的名称和方法。

不过,作为质疑者中的一员,我对他们为什么不像Scala定义接口为Function0,Function1,Function2,…,FunctionN那样解决这种问题感到疑惑。或许,我唯一能想出解释上述问题的原因是,他们希望最大可能地,为较早版本APIs的接口定义如前所述的lambda表达式。

缺少值类型

所以在这里,类型擦除显然是个驱动力。但如果你想知道为什么我们还需要这些附加的函数式接口—具有类似名称和方法签名,其唯一区别是使用基本类型,那么我来告诉你:Java缺乏像C#中的值类型。这意味着我们在泛型类中使用的泛型只能是引用类型,而不能是基本类型。

换句话说,我们不能这样做:

List<int> numbers = asList(1,2,3,4,5);



但我们确实可以这样做:

List<Integer> numbers = asList(1,2,3,4,5);



虽然,第二个例子说明了包装类和基本类型来回装箱、拆箱的成本。这说明在集合中处理基本类型值所付出代价是昂贵的。因此,专家组决定创造大量的接口来应对不同的场景。为了让事情“不太坏”,他们决定只处理三种基本类型:int,long和double。

引用自lamda邮件列表中Brian Goetz的话:

引用
更普遍地,具备专门的基本数据流(例如,IntStream)背后的哲学充满了艰难的权衡。一方面,它存在大量丑陋的重复代码,接口污染等;另一方面,在任何一种算术在装箱操作方面表现得很烂,对整数无能为力是可怕的。因此,我们正处在一个艰难的境地,正努力使情况不变得更糟。

为了不使事情变得更糟的招数1:我们没有顾及所有的八种基本类型。我们正在改善int,long和double,其他类型可模仿这三种类型。可以说,我们本来也可以不把int纳入考虑之中,但我们认为大多数Java开发人员还没准备好。是的,会有人呼吁加入Character,得到的答案是“把它看做int”(每个特定的类型预计占〜100K到JRE内存)。

招数2:我们正在使用的基本数据流,把基本过程(排序,约简)中的事情做到最好,而不是试图在装箱过程中的复制所有东西。举例来说,正如Aleksey指出不存在IntStream.into()函数。(如果有,下面的问题将演变成“IntCollection在哪?IntArrayList呢?IntConcurrentSkipListMap呢?)这样做的目的是:许多流可能开始作为引用流,最后作为基本数据流,而不是相反的。这是确定的,并且这减少了需要转换的数量(例如,没有重载int – > T 的map,没有转换int – > T的特定函数,等等)



我们可以看到:对于专家小组来,这说是一个艰难的决定。我认为很少人同意这是很酷的,但我们大多数人最有可能会同意这是必要的。

检查异常问题

这可能是使事情变得更糟的第三驱动力。众所周知,Java支持2类异常:受检查异常和运行时异常。编译器要求我们处理或显式声明受检查异常,但它对运行时异常毫无办法。所以,这引发一个有趣的问题,原因是大部分函数式接口的方法签名不声明抛出任何异常。故举例来说,下面代码是行不通的:

Writer out = new StringWriter();Consumer<String> printer = s -> out.write(s); //oops! compiler error



不能这样做,因为写操作抛出受检查异常(即IOException异常),但 Consumer方法签名根本不声明抛出任何异常。所以这个问题的唯一解决办法是:创造出大量的接口,其中一些声明异常,另一些不声明(或为异常透明(exception transparency)提出了一种在语言层次上的机制)。同样,为了让事情“不太坏”,专家小组决定在这种情况下不做任何处理。

Brian Goetz在lambda邮件列表的话:

引用
是的,你自己必须提供声明异常的单抽象函数。随后的lambda转换将正常工作。

专家组(EG:expert group,译者注)讨论了使用额外的语言和库来解决这个问题,但最终认为这是一个糟糕的成本/效益权衡。

以库为基础的解决方案导致单抽象函数类型(声明异常vs.不声明异常)指数地激增,这与现有原始类型组合式增长的步调不一致。

以语言为基础的可用解决方案在复杂度/效用之间没有好的折中。尽管有一些代替性解决方案,我们将继续摸索,但肯定不会在Java 8中出现,也可能不会出现在Java 9中。

在这期间,你有工具来做你想做的。我确定你会喜欢我们提供最终解决方案(其次,你对于“你们为什么不干脆放弃受检查异常呢”这样的要求,算是显而易见的一个),但我认为目前状态能够让完成你的工作。



因此,在个案中,使用甚至更多的接口来处理这些事情,由我们开发人员决定。

interface IOConsumer<T> {
void accept(T t) throws IOException;
}
static<T> Consumer<T> exceptionWrappingBlock(IOConsumer<T> b) {
return e -> {
try { b.accept(e); }
catch (Exception ex) { throw new RuntimeException(ex); }
};
}



为了这么做:

Writer out = new StringWriter();
Consumer<String> printer = exceptionWrappingBlock(s -> out.write(s));



也许,等到未来(也许JDK9)Java支持值类型和Reification(新的Java泛型处理方式,译者注)的时候,我们将能够摆脱(或者至少不再需要使用)这些多重接口。

综上所述,我们可以看到,专家组在几个设计问题上花费了心机。保持向后兼容的需要、要求或约束使得事情更加困难,接着出现这些重要的情况:缺少值类型,类型擦除和受检查异常等。如果Java有了值类型而缺少其他两个,那么JDK8的设计可能会有所不同。因此,我们必须明白:这些都是棘手的问题,要经过多次权衡。专家组不得不划清问题界限并做出决定。

所以,当我们发现身受Java 8缺陷的影响时,也许我们需要提醒自己:为什么JDK有这些不优雅的设计?

原文链接: dzone 翻译: ImportNew.com - era_misa
译文链接: http://www.importnew.com/11087.html

 

来自: importnew
4
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 可能是把 Java 接口讲得最通俗的一篇文章

    读者春夏秋冬在抽象类的那篇文章中留言,“二哥,面试官最喜欢问的一个问题就是,‘兄弟,说说抽象类和接口之间的区别?’,啥时候讲讲接口呗!” 对于面向对象编程来说,抽象是...当然了,在没有搞清楚接口到底是什么

  • 为什么要 Java 集成测试呢?

    为什么要 Java 集成测试呢? 何为集成测试? 首先我们先了解什么是集成测试? 集成测试,也叫组装测试或联合测试。在单元测试的基础上,将所有模块按照设计要求(如根据结构图)组装成为子系统或系统,进行集成测试...

  • 《Java8实战》读书笔记08:接口的默认方法

    书中举例:所有的 Collection 类都实现了名为 java.util.Collection 的接口。通过向此接口中添加实现统一向所有集合类添加 方法。 9.3 默认方法的使用模式 9.3.1 可选方法 有些方法是可以忽略的,以前每个实现类都要...

  • Baeldung Java 周评 | 第十四弹(关键词:Java 8 接口污染、不再需要 ORM、JUnit BDD 测试、Java 垃圾回收优化、Java 8 并发基础、Spring 零停机部署)

    Java 8 里为何会存在接口污染 很好地说明了 Java 8 中不同的设计决策和权衡取舍。该语言具有很大的历史意义,为了开发出一致的 Lambda 解决方案而付出的努力无疑是令人印象深刻的。 Java 8 周五:不再需要 ORM 这将...

  • Java项目的程序里为什么老用注解?注解有哪些作用

    }复制代码使用 @interface 关键字来声明一个注解,注解的声明有点类似于接口声明,其中的每一个方法实际上是声明了一个注解的元素。方法的名称就是元素的名称,返回值类型就是元素的值类型(返回值类型只能是基本...

  • 万恶的空指针Java8怎么处理的?一文让你减少空指针的出现

    当前系列:Java8 新特性 系列 源代码 git 仓库 代码Git 仓库地址 Optional类 ​ 到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。 以前,为了解决空指针异常,Google公司著名的Guava项目...

  • Java常见设计模式总结

    装饰器模式详情:Java设计模式之结构型:装饰器模式 8、结构型-代理模式: 代理模式的设计动机是通过代理对象来访问真实对象,通过建立一个对象代理类,由代理对象控制原对象的引用,从而实现对真实对象的操作。...

  • java接口防重提交如何处理

    在一定的时间内多次请求同一接口,同一参数。由于请求是健康请求,会执行正常的业务逻辑,从而产生大量的废数据。

  • java8(jdk1.8)都有哪些新功能?

    什么是java8? Java8又称为jdk1.8,是Java语言开发的一个主要版本,这个版本包含语言、编译器、库、工具和JVM等方面的十多个新特性。Oracle公司于2014年3月18日发布Java 8版本,它支持函数式编程,新的JavaScript...

  • Java8新特性全面

    Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性。 速度更快 代码更少(增加了新的语法:Lambda 表达式) 强大的 Stream API 便于并行 最大化减少空指针异常:Optional Nashorn引擎,允许...

  • Java 8新特性

    主要涉及Java8的新特性,Java8新增加的类还有接口的新增加的规则没有编写。

  • 高薪程序员&面试题精讲系列25之你了解哪些Java新特性?你们公司使用哪个JDK版本?Java11了解过吗?

    一. 面试题及剖析 1. 今日面试题 在之前的几个章节中,壹哥 带各位复习的知识点与Java版本没有特别大的关系,...那么面试官为什么要考察我们对Java新特性的掌握程度呢? 其实之所以问这样的问题,一方面是因为现在大

  • 如何处理java接口防重提交

    ,在不污染源代码的情况下,进行增强功能,切入到要防重的接口上,实现统一防重处理、业务解耦。第一种:局限性太高,前台必须传递一个唯一值,就算请求到达指定后台服务,写一个拦截器,需要配置太多不需要拦截的...

  • 走进 Java 接口测试之简单解决写接口脏数据问题

    “脏”数据指数据在被实际使用前,已经被进行了非预期的修改:比如,我们在登录接口中使用事先创建好的用户进行测试,但这个用户的密码被之前的测试无意中修改了,导致测试用例执行时登录失败,也就不能顺利完成测试...

  • 从Java8到Java21各版本新特性详解

    上面这张图是 Oracle 官方给出的 Oracle JDK ...从JDK诞生到现在,仅有几个版本得到了长期支持,主要包括JDK 7、JDK 8、JDK 11以及即将发布的JDK 17,它将是继Java 8之后最重要的LTS版本,是Java社区八年努力的成果。

  • Java的数据结构有那些?

    1.线性表(ArrayList) 一个线性表(Linear List)是由n(n≥0)个数据元素(结点,它可以是一个字母,数字,记录或更复杂的信息)所构成的...java中以ArrayList为例 数组扩容:ArrayList的底层是Object类的数组,默认

  • Java9发布回顾Java 8的十大新特性

    今天,我们先来一起复习一下2014年发布的Java 8的十大新特性。先来喝杯java~~~ 按照java升级的传统,偶数版的(468)改动较小,奇数版的(579)都是大改动。但对于java8而言是一次变化巨大的更新,耗费了工程师...

  • java8新特性十大特性详解

    越来越多的项目已经使用 Java 8 了,毫无疑问,Java 8 是Java自Java 5(发布于2004年)之后的最重要的版本。这个版本包含语言、编译器、库、工具和 JVM 等方面的十多个新特性。在本文中我们将学习这些新特性,并用...

  • 从 Java 8 到 Java 18 的新语言特性

    而且 2022 年发布的 Spring Framework 6.0、SpringBoot 3.0 所支持的最低 java 版本为 java 17,所以今年升级 JDK 版本还是一个不错的选择的,之后我们也会升级来体验一下 JDK 17。今天先来看下有哪些更新。

  • java+sql server项目之科帮网计算机配件报价系统源代码.zip

    sql server+java项目之科帮网计算机配件报价系统源代码

Global site tag (gtag.js) - Google Analytics