close

本篇博客中我們將採用類似的方法,並熟悉Scala編程語言的另一個重要特性—模式匹配。同樣我們將通過編寫一些簡短的代碼片段,一系列小步驟來逐步深入。

我們首先聲明一個非常簡單的case類,後面將對其詳細剖析

caseclassFullName(first:String,last:String)

case 類的許多其他有用特性(例如結構化 equals、hashCode、copy 和 toString)中,Scala 編譯器支持以下代碼

valme=FullName("Linas","Medžiūnas")valFullName(meFirst,meLast)=me//meFirst:String=Linas//meLast:String=Medžiūnas

請注意這裡的一個很好的對稱性:構造時me 在左側,帶有兩個字符串參數的FullName(...)在賦值的右側,解構時正好相反。

當談到 Scala 模式匹配時,首先想到的是 match 語句(它類似於許多其他編程語言中的 switch / case,但是更強大)。可以在 Scala 中的很多地方可以使用模式匹配:你可以在定義 lambda 函數時使用它,也可以在 for-comprehension 生成器的左側,甚至在上面例子中的賦值語句中。為簡單起見,在本文的其餘部分,我們將主要在賦值語句中使用模式匹配。

現在我們已經定義了case類以及一些使用它的代碼,接着嘗試了解 Scala case類的特別之處以及如何使用相關代碼。有時理解某事物如何工作的一個非常好的方法是破壞它,然後嘗試使其再次工作!先將FullName類定義的 case 關鍵字排除

/*case*/classFullName(first:String,last:String)

如果嘗試上述代碼,會發現代碼(value me 的構建和它的解構)編譯報錯。為了修復它,我們需要在事情開始崩潰之前手動實現 Scala 編譯器之前提供給我們的功能,我們為FullName類添加一個伴隨對象

objectFullName{defapply(first:String,last:String):FullName=newFullName(first,last)defunapply(full:FullName):Some[(String,String)]=Some((full.first,full.last))}

Scala 中的伴生對象是一個單例,與它的伴生類同名且在同一個文件中。而且伴隨對象和它的類可以訪問彼此的私有成員。伴生對象是放置類的靜態成員的地方(與 Java 不同,Scala 沒有 static 修飾符),這提供了更清晰的靜態/實例成員分離。

注意:我們必須稍微更改FullName類定義,以使FullName.unapply編譯成功

/*case*/classFullName(valfirst:String,vallast:String)

如果不進行修改,first 和 last 只會作為構造函數的參數,無法通過unapply訪問它們。在 first 和 last 之前添加 val 會將它們同時轉換為構造函數參數和實例字段(默認為 public)。在我們刪除 case 關鍵字之前Scala 編譯器會自動為我們生成此功能以及伴隨對象。

現在手動添加所有這些代碼可以修復編譯問題,繼續讓我們深入了解剛剛實現的兩個方法的細節

defapply(first:String,last:String):FullName

apply 是 Scala 中的一個特殊方法名稱,按照約定可以在代碼中省略,所以FullName(...)等價於FullName.apply(...),我們正在使用它來構造FullName的新實例,而無需 new 關鍵字。

defunapply(full:FullName):Some[(String,String)]

unapply 正好相反——它解構了一個FullName的實例,並且是模式匹配的基礎,接下來我們將重點介紹這種方法,在這種情況下,它將FullName解構為兩個字符串值,並將它們包裝在 Some 中,這意味着它可以匹配FullName的任何實例(稍後我們將探討部分匹配partial matching)。

再次注意這兩個方法的對稱性:apply將兩個字符串作為參數,並返回一個FullName的實例。而unapply則恰好相反。

現在我們對什麼是 unapply 以及它如何用於解構/模式匹配有了一個非常基本的了解。在大多數情況下,它已經由 Scala 處理—— unapply 的實現不僅為我們編寫的所有case類提供,而且為幾乎所有 Scala 標準庫中的所有內容提供,包括集合(如果適用),事實上實現自己的unapply並不常見,除非你是某個有趣庫的開發者,然而我們可以作弊—在Java中unapply 肯定不存在,讓我們從 java.time 中獲取一些類,並在它們上添加對 Scala 模式匹配的支持

importjava.time.{LocalDate,LocalDateTime,LocalTime}

能夠將 Date 分解為年、月和日,將 Time 分解為小時、分鐘和秒,這很自然。此外DateTime — 轉換為日期和時間,根據我們已有的知識,這非常簡單。但是我們不能使用名稱 LocalDate、LocalDateTime 和 LocalTime 來創建合適的伴生對象,因為伴生對象需要與對應的類放在相同的文件,但由於這些類來自 Java 標準庫,因此不可能。為了避免名稱衝突,我們簡單地將實現對象的名稱中省略 Local

objectDateTime{defunapply(dt:LocalDateTime):Some[(LocalDate,LocalTime)]=Some((dt.toLocalDate,dt.toLocalTime))}objectDate{defunapply(d:LocalDate):Some[(Int,Int,Int)]=Some((d.getYear,d.getMonthValue,d.getDayOfMonth))}objectTime{defunapply(t:LocalTime):Some[(Int,Int,Int)]=Some((t.getHour,t.getMinute,t.getSecond))}

接着使用它們:

valDate(year,month,day)=LocalDate.nowvalTime(hour,minute,second)=LocalTime.now

LocalDate 和 LocalTime 都按照預期被解構為 3 個 Int 值。如果我們只需要一些解構的值而不需要其他值,可以使用下劃線代替那些不需要的值

valDate(_,month,day)=LocalDate.now

一個更有趣的例子是 LocalDateTime 的嵌套解構

valDateTime(Date(y,m,d),Time(h,mm,s))=LocalDateTime.now

這為我們提供了 6 個 Int 值(日期部分為 3,時間部分為 3)。

模式匹配的另一個非常有用的特性是整個值的賦值,這可以在解構之外完成。對於我們的 DateTime 示例,它可能如下所示

valdt@DateTime(date@Date(y,m,d),time@Time(h,mm,s))=LocalDateTime.now

除了 6 個 Int 值,還得到一個 LocalDate 值,一個是 LocalTime 值,最後是 LocalDateTime 的整個值(以 dt 為單位)。

在上面的所有示例中,我們都解構為固定數量的值——(年、月、日)、或(時、分、秒)或(日期、時間)。在某些情況下我們需要處理一系列值,而不是某些固定數量的值,可以嘗試通過將 LocalDateTime 解構為一系列 Int

objectDateTimeSeq{defunapplySeq(dt:LocalDateTime):Some[Seq[Int]]=Some(Seq(dt.getYear,dt.getMonthValue,dt.getDayOfMonth,dt.getHour,dt.getMinute,dt.getSecond))}

unapplySeq是unapply的變體,它解構為一系列值而不是固定大小的元組。在這個例子中,序列的長度總是 6,但可以省略它的尾部,因為不需要它

valDateTimeSeq(year,month,day,hour,_*)=LocalDateTime.now

_*是 Scala varargs 的語法

到現在為止,unapply / unapplySeq總是返回 Some。為此unapply將返回Some以防該值符合某些條件,而None則不符合。我們已經在處理 LocalTime 的值,將它們匹配到 AM 或 PM 時間將是一個自然的例子

objectAM{defunapply(t:LocalTime):Option[(Int,Int,Int)]=tmatch{caseTime(h,m,s)ifh<12=>Some((h,m,s))case_=>None}}objectPM{defunapply(t:LocalTime):Option[(Int,Int,Int)]=tmatch{caseTime(12,m,s)=>Some(12,m,s)caseTime(h,m,s)ifh>12=>Some(h-12,m,s)case_=>None}}

其中case _ =>是默認情況,如果沒有其他匹配項,則會使用此進行匹配,此外我們剛剛介紹了另外兩個用於部分匹配的功能

•守衛(guards),例如case Time(h, m, s) if h < 12•常量匹配,例如case Time(12, m, s)

現在已經看到 Scala 模式匹配的強大功能!

我們自己實現一個可以很好地格式化當前時間的時鐘,通過使用模式匹配和 AM / PM 提取器(加上一些看起來像表情符號流的老派 Java 字符串格式)

LocalTime.nowmatch{caset@AM(h,m,_)=>f"$h%2d:$m%02dAM($tprecisely)"caset@PM(h,m,_)=>f"$h%2d:$m%02dPM($tprecisely)"}

我們已經探索了 Scala 模式匹配的大部分特性。可以在這裡[1]找到這篇博文的所有源代碼,為了更好地理解可以在 IntelliJ IDEA中運行這些代碼,最後如果 Scala 代碼中有一些複雜的、嵌套的 ifs 和 elses,請嘗試使用模式匹配來更好地重構它。

引用鏈接

[1]這裡:https://gist.github.com/linasm/003eec9eacc641167227193f5879bbd9

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()