Effective Java(3rd)-Item44 支持使用标准功能接口

emsp;emsp;现在Java有了lamdba,编写api的最佳实践已经发生了很大的变化。比如, 模板方法模式 [Gamma95],其中子类覆盖原语方法以专门化其超类的行为,则吸引力要小得多。现代替代方法是提供一个静态工厂或构造方法来接受一个函数对象来实现相同的效果。更一般地,您将编写更多的构造函数和方法,这些构造函数和方法将函数对象作为参数。选择正确的函数参数类型需要谨慎。
emsp;emsp;考虑LinkedHashMap。你可以使用这个类重写它的protected removeEldestEntry方法来作为一个缓存,该方法在每次向映射添加新键时由put调用。当这个方法返回true的时候,map移除了它最老的条目,传入这个方法。下面的覆盖允许映射增长到100个条目,然后在每次添加一个新键时删除最老的条目,维护最近的100个条目:

image.png

emsp;emsp;这个技术工作正常,但是你可以用lambda做得更好。如果LinkedHashMap在今天编写,它将有一个静态工厂或构造方法传入函数对象。看removeEldestEntry的声明,你可能会认为方法对象应该传入Map.Entry<K,V>并返回一个boolean值,但这并不意味着:removeEldestEntry方法调用size()来获取映射中的条目数量,这是因为removeEldestEntry是映射上的一个实例方法。传递给构造函数的函数对象不是映射上的实例方法,因此无法捕获它,因为在调用其工厂或构造函数时映射还不存在,因此,映射必须将自己传递给函数对象,函数对象因此必须在输入时接收映射及其最老的条目。如果您要声明这样一个函数接口,它应该是这样的:

image.png

emsp;emsp;这个接口能正常工作,但是你不应该去使用它,因为你不需要为这个目的声明一个新的接口。java.util.function包提供了一个标准函数接口的大集合来给你使用。 如果标准函数式接口中的一个做了这个工作,你通常应该使用它而不是使用一个特定的函数式接口。这将使你的API更容易学习,通过减少其概念表面积,并将提供重要的互操作性好处,因为许多标准功能接口提供了有用的默认方法。Predicate接口,为实例而用,提供方法来连接判断。在我们的LinkedHashMap例子中的情况,标准BiPredicate<Map<K,V>,Map.Entry<K,V>接口应该比自定义的EldestEntryRemovalFunction接口更好。
emsp;emsp;在java.util.Function中有四十三个接口。你不可能期望能记住所有的它们,但是如果你六个基本接口,你就可以在需要的时候获取其他。基本接口在对象引用类型上操作。Operator接口表示结果和参数类型相同的函数。Predicate接口表示接受参数并返回布尔值的函数。谓词接口表示接受参数并返回布尔值的函数,Function接口表示参数和返回类型不同的函数.Supplier接口表示不接受参数并返回(或“提供”)值的函数。最后,接口表示不接受参数并返回(或“提供”)值的函数。Consumer表示接受参数但不返回任何值的函数,本质上是消费它的参数。这六个基本函数接口总结如下:

image.png

emsp;emsp;在对基本类型int、long和double进行操作的六个基本接口中,每个接口都有三个变体。它们的名称由基本接口派生而来,方法是在它们前面加上一个基本类型前缀。例如,接受int的谓词是IntPredicate,接受两个long值并返回long的二进制操作符是LongBinaryOperator。除了由返回类型参数化的函数变量之外,这些变量类型都没有参数化。例如,LongFunction<int[]>接受long并返回int[]。
emsp;emsp;函数接口还有9个附加变体,用于结果类型为基本类型时使用。源类型和结果类型总是不同的,因为从类型到自身的函数都是UnaryOperator。如果源类型和结果类型都是基本类型,则使用SrcToResult作为前缀函数,例如LongToIntFunction(6变种)。如果源是基元,结果是对象引用,则使用前缀函数具有<Src>ToObj,例如DoubleToObjFunction(三个变体)。
emsp;emsp;有两个参数版本的三个基本功能接口是有意义的:BiPredicate<T,U>,BiPredicate<T,U,R>,和BiPredicate<T,U>。还有返回三个相关原始类型的双函数变体:ToIntBiFunction<T、U>、ToLongBiFunction<T、U>和ToDoubleBiFunction < T U >。Consumer有两个参数变体,它们接受一个对象引用和一个基本类型:ObjDoubleConsumer<T>,ObjIntConsumer < T >, ObjLongConsumer < T >。总的来说,基本接口有9个两个参数版本。
emsp;emsp;最后,还有BooleanSupplier接口,它是Supplier的变体,返回布尔值。这是标准函数接口名中唯一明确提到布尔类型的地方,但是通过Predicate及其四种变体形式支持布尔返回值。BooleanSupplier接口和前面段落中描述的42个接口构成了所有43个标准功能接口。诚然,这是一件难以接受的事情,而且并不完全正交。另一方面,您将需要的大部分功能接口都是为您编写的,它们的名称非常规则,因此您在需要时应该不会遇到太多麻烦。
emsp;emsp;大多数标准功能接口的存在只是为了提供对基本类型的支持。 不要试图使用带有装箱基元的基本函数接口而不是基元函数接口。虽然它能正常工作,但它违反了item61的建议,”相比较装箱原生类型,最好使用原生类型“。在批量操作中使用装箱原语的性能后果可能是致命的。
emsp;emsp;现在你知道了你应该使用标准函数式接口而不是编写你自己的。但是什么时候你应该编写自己的呢?当然是如果标准库中没有你想要的你就需要去编写你自己的。,比如,如果你需要一个断言,传递三个挖参数,或者其中一个抛出一个检查异常,但是,有时您应该编写自己的函数接口,即使其中一个标准接口在结构上是相同的。
emsp;emsp;考虑到我们的老朋友 Comparator<T>,与ToIntBiFunction<T,T>接口结构类似。即使在前者被添加到标准库的时候后者接口早已存在,但使用它是错误的。这里有一些原因说明ToIntBiFunction值得它作为一个自有的接口。首先,它的名字每次在API中使用中提供了极好的文档,并且它使用很多。第二,Comparator接口对有效实例的构成有严格的要求,有效实例包括它的一般契约。通过实现这个接口,你就承诺遵守它的契约。第三,该接口大量配备了有用的缺省方法来转换和组合比较器。
emsp;emsp;如果你需要与Comparator共享一个或多个以下特性的功能接口,你应该认真考虑编写一个专用的功能接口,而不是使用标准接口:

  • 它将被广泛使用,并且可以从描述性名称中获益
  • 它与之有很强的联系
  • 它将受益于自定义默认方法
    emsp;emsp;如果你选择去编写你自己的函数式接口,记住他是一个接口并且在设计的时候要很小心( item21

emsp;emsp;注意到EldestEntryRemovalFunction接口被打上了 @FunctionalInterface注解的标签。这个注解类型在本质上与@Override类似。这是一个程序员意图的声明,有三个目的:它告诉类及其文档的读者,该接口是为了启用lambdas而设计的;它让你保持诚实,因为接口不会编译,除非它有一个抽象的方法;它还可以防止维护人员在接口发展过程中意外地向接口添加抽象方法。经常在你的函数式接口上注解@FunctionalInterface 注解
emsp;emsp;应该指出的最后一点是关于函数接口的使用api。不要提供具有多个重载的方法,如果该方法可能在客户机中造成歧义,则该方法将在相同的参数位置上使用不同的功能接口。这不只是一个理论问题。ExecutorService的submit方法可以调用Callable<T>或者一个Runnable,也可以编写一个客户机程序,该程序需要强制转换来指示正确的重载( item52 )。最简单的方法来避免这个问题就是不要编写可以重载的函数式接口在同样参数的情况下。这是item52的特别建议。谨慎使用重载。

emsp;emsp;总之,现在Java有了lambda,在你的API的设计中使用lambda是有必要的。在输入接受函数式接口类型在输出返回它们。通常使用标准接口提供的java.util,function.Function是最好的,但是请注意相对少见的情况,在这种情况下,您最好编写自己的函数接口。
本文写于2019.7.12,历时1天

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容