在《Coders at Work》中,Simon Peyton Jones曾經提到:「純函數式領域中學到的觀念與想法,可能給主流領域帶來資訊,帶來啟發」,目前不少語言中實現的特性,確實都受到函數式的啟發,藉由探討Java 8中如何進行函式重用,這類啟發的樣貌,就能夠更具體地呈現。

從一級函式到高階函式

開發者對函式的傳統想法就是,封裝某個演算流程,以便後續重用該演算流程,在物件導向的世界中,操作與狀態結合在一起而成為物件,為了區別,此時,函式被稱為方法(Method),在JavaScript這類語言中,函式與方法這兩個名詞會混用,而在Java這類一開始沒有一級函式概念的語言中,通常只會使用方法這個名詞。這突顯了一個事實,無論是靜態方法或實例方法,在Java中定義好了,命運就是等待著被呼叫。

Java 8引入了Lambda特性,一級函式的概念因而進入Java之中,除了可將程式碼作為資料(Code as data)傳遞的Lambda語法之外,方法參考(Method reference)實際上也是一種函式重用的概念,這與JavaScript中定義了一個函式,就可以直接透過名稱引用該函式,彼此是類似的概念,也就是方法被定義之後,不再只是能被動地被呼叫,也能主動地進行傳遞。

Lambda語法與方法參考,可使得方法如同函式一樣地主動傳遞之後,衍生而出的就是高階函式的實現概念,也就是能接受函式作為引數,或者將函式當作傳回值,或者兩者皆有的函式,像是Comparator的comparing方法,實際上就是高階函式的概念,這是從既有函式產生新函式,也就是函式除了被定義呼叫、被傳遞之外,也可以在必要時用來產生新函式。

嚴格說來,在Java 8中實際上還是沒有函式,仍舊是只有物件與方法的世界,然而,Lambda特性的引入,讓方法的重用形式,有機會借鏡函數式設計中常見的函式重用模式,而使得方法與函式的界線模糊了。

既然如此,如果暫且直接將方法當成函式,進一步探討純函數式中的函式重用,如何能在Java中實現,就能發現更多的函式重用模式。

實現Curried函式與部份套用

在Haskell中,多參數函式其實是由單參數函式來組成的。

舉例來說,在Haskell中,要定義一個可給定三個邊長,判斷是否為直角三角形的函式,可以定義:isRightTri a b c = a ** 2 + b ** 2 == c ** 2。

實際上,這等同於使用Lambda語法定義為:isRightTri = \a -> \b -> \c -> a ** 2 + b ** 2 == c ** 2。

加上括號的話,成為:\a -> (\b -> (\c -> a ** 2 + b ** 2 == c ** 2))

如此一來,就可以看出,isRightTri是由三個單參數的Lambda函式組成,而isRightTri這樣的函式稱為Curried函式。

Curried函式可以進行部份套用,此時,isRightTri 3會傳回可繼續套用剩餘兩個引數的\b -> (\c -> 9 + b ** 2 == c ** 2),若是isRightTri 3 4,會傳回可繼續套用一個引數的\c -> 25 == c ** 2。

也就是說,在Haskell中,即使任何一個函式表面看來不接受函式,也沒傳回函式,事實上,也都是高階函式,可以隨時從既有函式產生新函式。

從另一個角度來看,如果已知一邊長為3,可以定義isRightTriWhenAThree b c = isRightTri 3 b c,此時兩邊參數同為b、c,由於函式可進行部份套用,不如直接定義isRightTriWhenAThree = isRightTri 3,形成Point free(或Pointless)風格,而greaterThanThree xs = filter (>3) xs,也可以定義為greaterThanThree = filter (>3)。

因此,上述這種風格突顯了函式的組合方式,而不是單純地根據引數來思考函式。

Java中確實也能實現出Curried函式的概念,可以使用Lambda語法定義a -> b -> c ->  a * a + b * b == c * c(雖然目標型態要用到醜陋的泛型),在必要時,也可以實現isRightTri(3)、isRightTri(3, 4)、isRightTri(3, 4, 5)的呼叫方式,實際上,這需要三個重載的isRightTri方法。

而如此的思考方式,也帶來一個可能性:如果確實經常需要filter(x -> x > 3)這樣的方式來產生Function實例,以便後續套用,那麼,可定義出:filter(Predicate p),令其傳回lt -> lt.stream().filter(p).collect(toList())。

實現函式合成

在Haskell中,若想將[10, -20, -30]轉換為["10", "20", "30"],可以使用map (\x -> show (abs x)) [10, -20, -30],因為沒有現成函式可以使用,或以部份套用而得到可用的新函式,所以,這邊直接使用了Lambda,而經過仔細觀察,你會發現:abs x的運算結果,是直接作為show函式的輸入──在這種情況下,可以直接寫為map (show . abs) [10, -20, -30],獲得比較好的可讀性。

而show . abs會產生新函式,作用等同於\x -> show (abs x),這是Haskell的函式合成(Function composition)語法,因為它跟數學上的函數合成很類似。根據維基百科Function composition條目所示,若有函數f:X->Y與g:Y->Z,那麼合成函式g(f(x)),就可以將X中的x對應至Z,合成的函式可標示為g . f:X->Z,定義就是對X中所有x,(g . f)(x) = g(f(x))。

接下來,我們可進一步來看函式合成的運用。

如果有個需求,是要加總某個List,然後取得絕對值,並轉為字串,可定義showAbsSumOf xs = show (abs (sum xs)),括號代表著前一函式的輸出會作為後續函式的輸入,因此,可改為showAbsSumOf xs = show . abs . sum xs,此時,兩邊都是xs,由於可以進行部份套用,不如直接定義showAbsSumOf = show . abs . sum,以Point free風格,來突顯函式的組合方式。

Java 8中代表函式型態的Function函式介面,實際上,它已經就提供了compose預設方法(Default method),而且,這可以用Function toString = Object::toString,而toString.compose(Math::abs)來實現show . abs的概念;或者,使用andThen預設方法,以另一方向的Function abs = Math::abs,還有abs.andThen(Object::toString)來實現。

函數式設計模式

從上面的舉例來看,你發現了什麼?在函數式程式設計世界中,重用總是圍繞著函式,像是程式碼作為資料傳遞、引用既有函式、從既有函式產生新函式、思考函式的組合方式,像是部份套用或函式合成,這些都是不斷出現的模式。

實際上,如果繼續探討下去,關於定義新的代數資料型態(Algebraic data type)、Monad等,也都會與函式有關,而這些圍繞著函式而出現的概念,就是函數式設計模式。

在物件導向世界中,談到重用,總是圍繞著物件,重視類別、介面,以及彼此之間的關係,因而有了物件導向設計模式。就像Scott Wlaschin則在〈Functional Programming Patterns〉的簡報中,就談到函數式設計模式是什麼?其中,第13頁他列出了FP pattern/principle,每一點都有Functions字眼。

如此的作法意謂著,在Java這類物件導向語言中,稍微將焦點從物件改變到函式,就有機會發現更多的可重用模式,獲得新的設計方向。

java函_Java 8的函式重用相关推荐

  1. 1.7 Java创建对象详解(显式创建和隐含创建)

    对象是对类的实例化.对象具有状态和行为,变量用来表明对象的状态,方法表明对象所具有的行为.Java 对象的生命周期包括创建.使用和清除,本文详细介绍对象的创建,在 Java 语言中创建对象分显式创建与 ...

  2. Cpp 对象模型探索 / 虚函数表和虚函数表指针的创建时机

    一.虚函数表 在编译期间创建.编译器会为每个类确定好虚函数表(vtbl)的内容. 二.虚函数表指针 虚函数表指针跟随着对象,在运行期间创建.由于在编译期间编译器为每个类创建好了 vtbl,并且编译器会 ...

  3. Java实现单例模式之饿汉式、懒汉式、枚举式,带测试。

    Java实现单例的3种普遍的模式,饿汉式.懒汉式.枚举式. 具体代码如下: package com.lcx.mode;/*** * 饿汉式单例,不管以后用不用这个对象,我们一开始就创建这个对象的实例, ...

  4. 【源码+教程】Java课设项目_12款最热最新Java游戏项目_Java游戏开发_Java小游戏_飞翔的小鸟_王者荣耀_超级玛丽_推箱子_黄金矿工_贪吃蛇

    马上就要期末了,同学们课设做的如何了呢?本篇为大家带来了12款热门Java小游戏项目的源码和教程,助力大家顺利迎接暑假![源码+教程]Java课设项目_12款最热最新Java游戏项目_Java游戏开发 ...

  5. 虚函数表 以及 虚函数表的继承过程

    目录 一.虚函数表 和 虚表继承 1.虚函数表 2.虚表继承 (1) 子类未重写父类虚函数 (2) 子类重写了父类虚函数 二.虚表的特点 1.同一个类的对象的虚表指针相同 2.多继承时子类中的两个父类 ...

  6. Java单例模式--------懒汉式和饿汉式

    Java单例模式--------懒汉式和饿汉式 单件模式用途: 单件模式属于工厂模式的特例,只是它不需要输入参数并且始终返回同一对象的引用. 单件模式能够保证某一类型对象在系统中的唯一性,即某类在系统 ...

  7. 黑马程序员全套Java教程_Java基础教程_异常(含扩展)(二十三)

    黑马程序员全套Java教程_Java基础教程_异常(含扩展)(二十三) 1.1 异常概述与异常体系结构 1.2 JVM遇到异常时的默认处理方案 1.3 异常处理 1.4 异常处理之try--catch ...

  8. java 虚函数表_虚函数表(vtable/virtual table/virtual method table)

    本文只是拿 Java 代码做说明的例子,不代表Java的vtable是这样实现的 虚函数表,又称 virtual method table (VMT), virtual function table, ...

  9. java链式编程_Java 中的链式编程

    前言 ​在写项目的时候,有一个实体类有好多个属性,new 出来之后需要不停的使用setXXX( )方法,效率低而且代码可读性差,查询了下发现可以实现实体类的链式编程. public class Use ...

最新文章

  1. 表单验证,添加动态class
  2. 杨辉三角形递归c语言,关于【杨辉三角】的递归解决方法,请教。有没有大神【【高手】】...
  3. 关联本地git仓库与Github仓库
  4. SAP UI5 oList.bindAggregation(item) will trigger odata request
  5. 简单小清新植物点缀绿色花边边框,圣诞节花环节日PNG素材
  6. 问题:自定义Appender输出DCMTK的oflog
  7. japidocs怎么设置参数必填_常用的3种拍摄模式,相机参数怎么设置?如何正确曝光?...
  8. 【干货】sql-labs、请求方式、注入类型、拼接方式
  9. PHP以指定字段为索引返回数组数据
  10. IO负载高的来源定位 IO系列
  11. Vertx与Spring配合完成DML操作
  12. java excel 导入试题
  13. python3视频教程哪个好_2020年5个经典python编程入门视频教程推荐学习
  14. linux2T硬盘分区命令,linux系统使用parted命令对大于2T的硬盘进行分区教程
  15. 印度文明的继承和交溶
  16. 技术人的充电时刻,200分钟QA交流,尽在SDCC 2017·深圳站
  17. Swift获取当前网络状态Wifi/5G/4G/3G/2G
  18. java实现会员充值功能
  19. java获取工作日 日历接口_节假日api接口之获取指定日期的节假日信息
  20. 基于Python个人博客系统设计与实现 开题报告

热门文章

  1. HTTP / HTTPS抓包工具-Fiddler
  2. double+float
  3. eclipse中菜单Build Path的解释和设置
  4. 线性映射和线性变换的区别
  5. MVC教程第六篇:拦截器
  6. 由dataview生成对应的datatable的方法
  7. 每天进步一点点《ML - 人工神经网络》
  8. 漫步线性代数一——引言
  9. 转载——逻辑回归的袅娜曲线,你是否会过目难忘?
  10. C++/C--STL API查询及免费教程网站