溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

從Java走進(jìn)Scala如何使用元組、數(shù)組和列表

發(fā)布時(shí)間:2021-11-03 11:40:20 來(lái)源:億速云 閱讀:175 作者:小新 欄目:編程語(yǔ)言

這篇文章主要介紹了從Java走進(jìn)Scala如何使用元組、數(shù)組和列表,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

使用 Option(s)

在什么情況下,“無(wú)” 并不代表 “什么也沒(méi)有”?當(dāng)它為 0 的時(shí)候,與 null 有什么關(guān)系。

對(duì)于我們大多數(shù)人都非常熟悉的概念,要在軟件中表示為 “無(wú)” 是一件十分困難的事。例如,看看 C++ 社區(qū)中圍繞 NULL 和 0 進(jìn)行的激烈討論,或是 SQL 社區(qū)圍繞 NULL 列值展開(kāi)的爭(zhēng)論,便可知曉一二。 NULL 或 null 對(duì)于大多數(shù)程序員來(lái)說(shuō)都表示 “無(wú)”,但是這在 Java 語(yǔ)言中引出了一些特殊問(wèn)題。

考慮一個(gè)簡(jiǎn)單操作,該操作可以從一些位于內(nèi)存或磁盤(pán)的數(shù)據(jù)庫(kù)查找程序員的薪資:API 允許調(diào)用者傳入一個(gè)包含程序員名字的 String,這會(huì)返回什么呢?從建模角度來(lái)看,它應(yīng)該返回一個(gè) Int,表示程序員的年薪;但是這里有一個(gè)問(wèn)題,如果程序員不在數(shù)據(jù)庫(kù)中(可能根本沒(méi)有雇用她,或者已經(jīng)被解雇,要不就是輸錯(cuò)了名字……),那么應(yīng)該返回什么。如果返回類型是 Int,則不能返回 null,這個(gè) “標(biāo)志” 通常表示沒(méi)有在數(shù)據(jù)庫(kù)中找到該用戶(您可能認(rèn)為應(yīng)該拋出一個(gè)異常,但是大多數(shù)時(shí)候數(shù)據(jù)庫(kù)丟失值并不能視為異常,因此不應(yīng)該在這里拋出異常)。

在 Java 代碼中,我們最終將方法標(biāo)記為返回 java.lang.Integer,這迫使調(diào)用者知道方法可以返回 null。自然,我們可以依靠程序員來(lái)全面歸檔這個(gè)場(chǎng)景,還可以依賴程序員讀取 精心準(zhǔn)備的文檔。這類似于:我們可以要求經(jīng)理傾聽(tīng)我們反對(duì)他們要求的不可能完成的項(xiàng)目期限,然后經(jīng)理再進(jìn)一步把我們的反對(duì)傳達(dá)給上司和用戶。

Scala 提供了一種普通的函數(shù)方法,打破了這一僵局。在某些方面,Option 類型或 Option[T],并不重視描述。它是一個(gè)具有兩個(gè)子類 Some[T] 和 None 的泛型類,用來(lái)表示 “無(wú)值” 的可能性,而不需要語(yǔ)言類型系統(tǒng)大費(fèi)周折地支持這個(gè)概念。實(shí)際上,使用 Option[T] 類型可以使問(wèn)題更加清晰(下一節(jié)將用到)。

在使用 Option[T] 時(shí),關(guān)鍵的一點(diǎn)是認(rèn)識(shí)到它實(shí)質(zhì)上是一個(gè)大小為 “1” 的強(qiáng)類型集合,使用一個(gè)不同的值 None 表示 “nothing” 值的可能性。因此,在這里方法沒(méi)有返回 null 表示沒(méi)有找到數(shù)據(jù),而是進(jìn)行聲明以返回 Option[T],其中 T 是返回的原始類型。那么,對(duì)于沒(méi)有查找到數(shù)據(jù)的場(chǎng)景,只需返回 None,如下所示:

清單 1. 準(zhǔn)備好踢足球了嗎?

@Test def simpleOptionTest =  {    val footballTeamsAFCEast =      Map("New England" -> "Patriots",          "New York" -> "Jets",          "Buffalo" -> "Bills",          "Miami" -> "Dolphins",          "Los Angeles" -> null)        assertEquals(footballTeamsAFCEast.get("Miami"), Some("Dolphins"))    assertEquals(footballTeamsAFCEast.get("Miami").get(), "Dolphins")    assertEquals(footballTeamsAFCEast.get("Los Angeles"), Some(null))    assertEquals(footballTeamsAFCEast.get("Sacramento"), None)  }

注意,Scala Map 中 get 的返回值實(shí)際上并不對(duì)應(yīng)于傳遞的鍵。相反,它是一個(gè) Option[T] 實(shí)例,可以是與某個(gè)值有關(guān)的 Some(),也可以是 None,因此可以很清晰地表示沒(méi)有在 map 中找到鍵。如果它可以表示 map 上存在某個(gè)鍵,但是有對(duì)應(yīng)的 null 值,這一點(diǎn)特別重要了。比如清單 1 中 Los Angeles 鍵。

通常,當(dāng)處理 Option[T] 時(shí),程序員將使用模式匹配,這是一個(gè)非常函數(shù)化的概念,它允許有效地 “啟用” 類型和/或值,更不用說(shuō)在定義中將值綁定到變量、在 Some() 和 None 之間切換,以及提取 Some 的值(而不需要調(diào)用麻煩的 get() 方法)。清單 2 展示了 Scala 的模式匹配:

清單 2. 巧妙的模式匹配

@Test def optionWithPM =  {    val footballTeamsAFCEast =      Map("New England" -> "Patriots",          "New York" -> "Jets",          "Buffalo" -> "Bills",          "Miami" -> "Dolphins")              def show(value : Option[String]) =    {      value match      {        case Some(x) => x        case None => "No team found"     }    }        assertEquals(show(footballTeamsAFCEast.get("Miami")), "Dolphins")  }

C# 2.0 可變?yōu)?null 值的類型

其他語(yǔ)言已試圖通過(guò)各種方法解決 “可 null 值化” 問(wèn)題:C++ 一直都忽略了這個(gè)問(wèn)題,直至最后確定 null 和 0 是不同的值。Java 語(yǔ)言仍然沒(méi)有徹底解決這個(gè)問(wèn)題,而是依賴于自動(dòng)裝箱(autobox)— 將原語(yǔ)類型自動(dòng)轉(zhuǎn)換為它們的包裝器對(duì)象(在 1.1 以后引入)— 幫助 Java 程序員解決問(wèn)題。一些模式愛(ài)好者建議每種類型都應(yīng)該有一個(gè)對(duì)應(yīng)的 “Null Object”,即將自己的所有方法重寫(xiě)為不執(zhí)行任何操作的類型(實(shí)際上是子類型)的實(shí)例 — 實(shí)踐證明這需要大量工作。C# 1.0 發(fā)布后,C# 設(shè)計(jì)者決定采取一種完全不同的方法解決 null 值化問(wèn)題。

C# 2.0 引入了可變?yōu)?null 值的類型 的概念,重要的是添加了語(yǔ)法支持,認(rèn)為任何特定值類型(基本指原語(yǔ)類型)都可以通過(guò)將 null 封裝到一個(gè)泛型/模板類 Nullable< T>,從而提供 null 支持。Nullable< T> 本身是在類型聲明中通過(guò) ? 修飾符號(hào)引入。因此,int? 表示一個(gè)整數(shù)也可能為 null。

表面上看,這似乎很合理,但是事情很快就變得復(fù)雜起來(lái)。int 和 int? 是否應(yīng)該被視為可兼容類型,如果是的話,什么時(shí)候?qū)?int 提升為 int?,反之呢?當(dāng)將 int 添加到 int? 會(huì)發(fā)生什么,結(jié)果會(huì)是 null 嗎?這類問(wèn)題等等。隨后類型系統(tǒng)進(jìn)行了一些重要的調(diào)整,可變?yōu)?null 值的類型隨后包含到了 2.0 中 — 而 C# 程序員幾乎完全忽略了它們。

回顧一下 Option 類型的函數(shù)方法,它使 Option[T] 和 Int 之間的界限變得很清晰,看上去要比其他方法更加簡(jiǎn)單。在那些圍繞可變?yōu)?null 值類型的反直覺(jué)(counterintuitive)提升規(guī)則之間進(jìn)行比較時(shí),尤其如此。(函數(shù)領(lǐng)域?qū)υ搯?wèn)題近二十年的思考是值得的)。要使用 Option[T] 必須付出一些努力,但是總的來(lái)說(shuō),它產(chǎn)生了更清晰的代碼和期望。

元組和集合

在 C++ 中,我們將之稱為結(jié)構(gòu)體。在 Java 編程中,我們稱之為數(shù)據(jù)傳輸對(duì)象或參數(shù)對(duì)象。在 Scala 中,我們稱為元組。實(shí)質(zhì)上,它們是一些將其他數(shù)據(jù)類型收集到單個(gè)實(shí)例的類,并且不使用封裝或抽象 — 實(shí)際上,不 使用任何抽象常常更有用。

在 Scala 創(chuàng)建一個(gè)元組類型非常的簡(jiǎn)單,這只是主體的一部分:如果首先將元素公開(kāi)給外部,那么在類型內(nèi)部創(chuàng)建描述這些元素的名稱就毫無(wú)價(jià)值。考慮清單 3:

清單 3. tuples.scala

// JUnit test suite  //  class TupleTest  {    import org.junit._, Assert._    import java.util.Date       @Test def simpleTuples() =    {      val tedsStartingDateWithScala = Date.parse("3/7/2006")       val tuple = ("Ted", "Scala", tedsStartingDateWithScala)            assertEquals(tuple._1, "Ted")      assertEquals(tuple._2, "Scala")      assertEquals(tuple._3, tedsStartingDateWithScala)    }  }

創(chuàng)建元組非常簡(jiǎn)單,將值放入一組圓括號(hào)內(nèi),就好象調(diào)用一個(gè)方法調(diào)用一樣。提取這些值只需要調(diào)用 “_n” 方法,其中 n 表示相關(guān)的元組元素的位置參數(shù):_1 表示第一位,_2 表示第二位,依此類推。傳統(tǒng)的 Java java.util.Map 實(shí)質(zhì)上是一個(gè)分兩部分的元組集合。

元組可以輕松地實(shí)現(xiàn)使用單個(gè)實(shí)體移動(dòng)多個(gè)值,這意味著元組可以提供在 Java 編程中非常重量級(jí)的操作:多個(gè)返回值。例如,某個(gè)方法可以計(jì)算 String 中字符的數(shù)量,并返回該 String 中出現(xiàn)次數(shù)最多的字符,但是如果程序員希望同時(shí) 返回最常出現(xiàn)的字符和 它出現(xiàn)的次數(shù),那么程序設(shè)計(jì)就有點(diǎn)復(fù)雜了:或是創(chuàng)建一個(gè)包含字符及其出現(xiàn)次數(shù)的顯式類,或?qū)⒅底鳛樽侄伪4娴綄?duì)象中并在需要時(shí)返回字段值。無(wú)論使用哪種方法,與使用 Scala 相比,都需要編寫(xiě)大量代碼;通過(guò)簡(jiǎn)單地返回包含字符及其出現(xiàn)次數(shù)的元組,Scala 不僅可以輕松地使用 “_1”、“_2” 等訪問(wèn)元組的各個(gè)值,還可以輕松地返回多個(gè)返回值。

如下節(jié)所示,Scala 頻繁地將 Option 和元組保存到集合(例如 Array[T] 或列表)中,從而通過(guò)一個(gè)比較簡(jiǎn)單的結(jié)構(gòu)提供了極大的靈活性和威力。

數(shù)組帶您走出陰霾

讓我們重新審視一個(gè)老朋友 — 數(shù)組 — 在 Scala 中是 Array[T]。和 Java 代碼中的數(shù)組一樣,Scala 的 Array[T] 是一組有序的元素序列,使用表示數(shù)組位置的數(shù)值進(jìn)行索引,并且該值不可以超過(guò)數(shù)組的總大小,如清單 4 所示:

清單 4. array.scala

object ArrayExample1  {    def main(args : Array[String]) : Unit =    {      for (i <- 0 to args.length-1)      {        System.out.println(args(i))      }    }  }

盡管等同于 Java 代碼中的數(shù)組(畢竟后者是最終的編譯結(jié)果),Scala 中的數(shù)組使用了截然不同的定義。對(duì)于新手,Scala 中的數(shù)組實(shí)際上就是泛型類,沒(méi)有增加 “內(nèi)置” 狀態(tài)(至少,不會(huì)比 Scala 庫(kù)附帶的其他類多)。例如,在 Scala 中,數(shù)組一般定義為 Array[T] 的實(shí)例,這個(gè)類定義了一些額外的有趣方法,包括常見(jiàn)的 “l(fā)ength” 方法,它將返回?cái)?shù)組的長(zhǎng)度。因此,在 Scala 中,可以按照傳統(tǒng)意義使用 Array,例如使用 Int 在 0 到 args.length - 1 間進(jìn)行迭代,并獲取數(shù)組的第 i 個(gè)元素(使用圓括號(hào)而不是方括號(hào)來(lái)指定返回哪個(gè)元素,這是另一種名稱比較有趣的方法)。 

擴(kuò)展數(shù)組

事實(shí)證明 Array 擁有大量方法,這些方法繼承自一個(gè)非常龐大的 parent 層次結(jié)構(gòu):Array 擴(kuò)展 Array0,后者擴(kuò)展 ArrayLike[A],ArrayLike[A] 擴(kuò)展 Mutable[A],Mutable[A] 又?jǐn)U展 RandomAccessSeq[A],RandomAccessSeq[A] 擴(kuò)展了 Seq[A],等等。實(shí)際上,這種層次結(jié)構(gòu)意味著 Array 可以執(zhí)行很多操作,因此與 Java 編程相比,在 Scala 中可以更輕松地使用數(shù)組。

例如,如清單 4 所示,使用 foreach 方法遍歷數(shù)組更加簡(jiǎn)單并且更貼近函數(shù)的方式,這些都繼承自 Iterable 特性:

清單 5. ArrayExample2

object   {    def main(args : Array[String]) : Unit =    {      args.foreach( (arg) => System.out.println(arg) )    }  }

看上去您沒(méi)有節(jié)省多少工作,但是,將一個(gè)函數(shù)(匿名或其他)傳入到另一個(gè)類中以便獲得在特定語(yǔ)義下(在本例中指遍歷數(shù)組)執(zhí)行的能力,是函數(shù)編程的常見(jiàn)主題。以這種方式使用更高階函數(shù)并不局限于迭代;事實(shí)上,還得經(jīng)常對(duì)數(shù)組內(nèi)容執(zhí)行一些過(guò)濾 操作去掉無(wú)用的內(nèi)容,然后再處理結(jié)果。例如,在 Scala 中,可以輕松地使用 filter 方法進(jìn)行過(guò)濾,然后獲取結(jié)果列表并使用 map 和另一個(gè)函數(shù)(類型為 (T) => U,其中 T 和 U 都是泛型類型),或 foreach 來(lái)處理每個(gè)元素。我在清單 6 中采取了后一種方法(注意 filter 使用了一個(gè) (T) : Boolean 方法,意味著使用數(shù)組持有的任意類型的參數(shù),并返回一個(gè) Boolean)。

清單 6. 查找所有 Scala 程序員

class ArrayTest  {    import org.junit._, Assert._        @Test def testFilter =    {      val programmers = Array(          new Person("Ted", "Neward", 37, 50000,            Array("C++", "Java", "Scala", "Groovy", "C#", "F#", "Ruby")),          new Person("Amanda", "Laucher", 27, 45000,            Array("C#", "F#", "Java", "Scala")),          new Person("Luke", "Hoban", 32, 45000,            Array("C#", "Visual Basic", "F#")),    new Person("Scott", "Davis", 40, 50000,      Array("Java", "Groovy"))        )       // 查找所有Scala程序員 ...      val scalaProgs =        programmers.filter((p) => p.skills.contains("Scala") )            // 應(yīng)該只有2      assertEquals(2, scalaProgs.length)            // ... now perform an operation on each programmer in the resulting      // array of Scala programmers (give them a raise, of course!)      //      scalaProgs.foreach((p) => p.salary += 5000)            // Should each be increased by 5000 ...      assertEquals(programmers(0).salary, 50000 + 5000)      assertEquals(programmers(1).salary, 45000 + 5000)            // ... except for our programmers who don't know Scala      assertEquals(programmers(2).salary, 45000)   assertEquals(programmers(3).salary, 50000)    }  }

創(chuàng)建一個(gè)新的 Array 時(shí)將用到 map 函數(shù),保持原始的數(shù)組內(nèi)容不變,實(shí)際上大多數(shù)函數(shù)性程序員都喜歡這種方式:

清單 7. Filter 和 map

@Test def testFilterAndMap =  {    val programmers = Array(        new Person("Ted", "Neward", 37, 50000,          Array("C++", "Java", "Scala", "C#", "F#", "Ruby")),        new Person("Amanda", "Laucher", 27, 45000,          Array("C#", "F#", "Java", "Scala")),        new Person("Luke", "Hoban", 32, 45000,          Array("C#", "Visual Basic", "F#"))  new Person("Scott", "Davis", 40, 50000,    Array("Java", "Groovy"))      )     // Find all the Scala programmers ...    val scalaProgs =      programmers.filter((p) => p.skills.contains("Scala") )        // Should only be 2    assertEquals(2, scalaProgs.length)        // ... now perform an operation on each programmer in the resulting    // array of Scala programmers (give them a raise, of course!)    //    def raiseTheScalaProgrammer(p : Person) =    {      new Person(p.firstName, p.lastName, p.age,        p.salary + 5000, p.skills)    }    val raisedScalaProgs =       scalaProgs.map(raiseTheScalaProgrammer)        assertEquals(2, raisedScalaProgs.length)    assertEquals(50000 + 5000, raisedScalaProgs(0).salary)    assertEquals(45000 + 5000, raisedScalaProgs(1).salary)  }

注意,在清單 7 中,Person 的 salary 成員可以標(biāo)記為 “val”,表示不可修改,而不是像上文一樣為了修改不同程序員的薪資而標(biāo)記為 “var”。

Scala 的 Array 提供了很多方法,在這里無(wú)法一一列出并演示??偟膩?lái)說(shuō),在使用數(shù)組時(shí),應(yīng)該充分地利用 Array 提供的方法,而不是使用傳統(tǒng)的 for ... 模式遍歷數(shù)組并查找或執(zhí)行需要的操作。最簡(jiǎn)單的實(shí)現(xiàn)方法通常是編寫(xiě)一個(gè)函數(shù)(如果有必要的話可以使用嵌套,如清單 7 中的 testFilterAndMap 示例所示),這個(gè)函數(shù)可以執(zhí)行所需的操作,然后根據(jù)期望的結(jié)果將該函數(shù)傳遞給 Array 中的 map、filter、foreach 或其他方法之一。

函數(shù)性列表

函數(shù)編程多年來(lái)的一個(gè)核心特性就是列表,它和數(shù)組在對(duì)象領(lǐng)域中享有相同級(jí)別的 “內(nèi)置” 性。列表對(duì)于構(gòu)建函數(shù)性軟件非常關(guān)鍵,因此,您(作為一名剛起步的 Scala 程序員)必須能夠理解列表及其工作原理。即使列表從未形成新的設(shè)計(jì),但是 Scala 代碼在其庫(kù)中廣泛使用了列表。因此學(xué)習(xí)列表是非常必要的。

在 Scala 中,列表類似于數(shù)組,因?yàn)樗暮诵亩x是 Scala 庫(kù)中的標(biāo)準(zhǔn)類 List[T]。并且,和 Array[T] 相同,List[T] 繼承了很多基類和特性,首先使用 Seq[T] 作為直接上層基類。

基本上,列表是一些可以通過(guò)列表頭或列表尾提取的元素的集合。列表來(lái)自于 Lisp,后者是一種主要圍繞 “LISt 處理” 的語(yǔ)言,它通過(guò) car 操作獲得列表的頭部,通過(guò) cdr 操作獲得列表尾部(名稱淵源與歷史有關(guān);第一個(gè)可以解釋它的人有獎(jiǎng)勵(lì))。

從很多方面來(lái)講,使用列表要比使用數(shù)組簡(jiǎn)單,原因有二,首先函數(shù)語(yǔ)言過(guò)去一直為列表處理提供了良好的支持(而 Scala 繼承了這些支持),其次可以很好地構(gòu)成和分解列表。例如,函數(shù)通常從列表中挑選內(nèi)容。為此,它將選取列表的第一個(gè)元素 — 列表頭部 — 來(lái)對(duì)該元素執(zhí)行處理,然后再遞歸式地將列表的其余部分傳遞給自身。這樣可以極大減少處理代碼內(nèi)部具有相同共享狀態(tài)的可能性,并且,假如每個(gè)步驟只需處理一個(gè)元素,極有可能使代碼分布到多個(gè)線程(如果處理是比較好的)。

構(gòu)成和分解列表非常簡(jiǎn)單,如清單 8 所示:

清單 8. 使用列表

class ListTest  {    import org.junit._, Assert._        @Test def simpleList =    {      val myFirstList = List("Ted", "Amanda", "Luke")            assertEquals(myFirstList.isEmpty, false)      assertEquals(myFirstList.head, "Ted")      assertEquals(myFirstList.tail, List("Amanda", "Luke")      assertEquals(myFirstList.last, "Luke")    }  }

注意,構(gòu)建列表與構(gòu)建數(shù)組十分相似;都類似于構(gòu)建一個(gè)普通對(duì)象,不同之處是這里不需要 “new”(這是 “case 類” 的功能,我們將在未來(lái)的文章中介紹到)。請(qǐng)進(jìn)一步注意 tail 方法調(diào)用的結(jié)果 — 結(jié)果并不是列表的最后一個(gè)元素(通過(guò) last 提供),而是除第一個(gè)元素以外的其余列表元素。

當(dāng)然,列表的強(qiáng)大力量部分來(lái)自于遞歸處理列表元素的能力,這表示可以從列表提取頭部,直到列表為空,然后累積結(jié)果:

清單 9. 遞歸處理

@Test def recurseList =  {    val myVIPList = List("Ted", "Amanda", "Luke", "Don", "Martin")        def count(VIPs : List[String]) : Int =    {      if (VIPs.isEmpty)        0     else       count(VIPs.tail) + 1   }        assertEquals(count(myVIPList), myVIPList.length)  }

注意,如果不考慮返回類型 count,Scala 編譯器或解釋器將會(huì)出現(xiàn)點(diǎn)麻煩 — 因?yàn)檫@是一個(gè)尾遞歸(tail-recursive)調(diào)用,旨在減少在大量遞歸操作中創(chuàng)建的棧幀的數(shù)量,因此需要指定它的返回類型。即使是這樣,也可以輕松地使用 List 的 “l(fā)ength” 成員獲取列表項(xiàng)的數(shù)量,但關(guān)鍵是如何解釋列表處理強(qiáng)大的功能。清單 9 中的整個(gè)方法完全是線程安全的,因?yàn)榱斜硖幚碇惺褂玫恼麄€(gè)中間狀態(tài)保存在參數(shù)的堆棧上。因此,根據(jù)定義,它不能被多個(gè)線程訪問(wèn)。函數(shù)性方法的一個(gè)優(yōu)點(diǎn)就是它實(shí)際上與程序功能截然不同,并且仍然創(chuàng)建共享的狀態(tài)。

列表 API

列表具有另外一些有趣的特性,例如構(gòu)建列表的替代方法,使用 :: 方法(是的,這是一種方法。只不過(guò)名稱比較有趣)。因此,不必使用 “List” 構(gòu)造函數(shù)語(yǔ)法構(gòu)建列表,而是將它們 “拼接” 在一起(在調(diào)用 :: 方法時(shí)),如清單 10 所示:

清單 10. 是 :: == C++ 嗎?

@Test def recurseConsedList =  {    val myVIPList = "Ted" :: "Amanda" :: "Luke" :: "Don" :: "Martin" :: Nil        def count(VIPs : List[String]) : Int =    {      if (VIPs.isEmpty)        0     else       count(VIPs.tail) + 1   }        assertEquals(count(myVIPList), myVIPList.length)  }

在使用 :: 方法時(shí)要小心 — 它引入了一些很有趣的規(guī)則。它的語(yǔ)法在函數(shù)語(yǔ)言中非常常見(jiàn),因此 Scala 的創(chuàng)建者選擇支持這種語(yǔ)法,但是要正確、普遍地使用這種語(yǔ)法,必須使用一種比較古怪的規(guī)則:任何以冒號(hào)結(jié)束的 “名稱古怪的方法” 都是右關(guān)聯(lián)(right-associative)的,這表示整個(gè)表達(dá)式從它的最右邊的 Nil 開(kāi)始,它正好是一個(gè) List。因此,可以將 :: 認(rèn)定為一個(gè)全局的 :: 方法,與 String 的一個(gè)成員方法(本例中使用)相對(duì);這又表示您可以對(duì)所有內(nèi)容構(gòu)建列表。在使用 :: 時(shí),最右邊的元素必須是一個(gè)列表,否則將得到一個(gè)錯(cuò)誤消息。

什么是右關(guān)聯(lián)?

要更好地理解 :: 方法,要記住 “冒號(hào)” 這類操作符僅僅是一些名稱比較有趣的方法。對(duì)于普通的左管理語(yǔ)法,左側(cè)的標(biāo)記一般是我將要對(duì)其調(diào)用方法名(右側(cè)的標(biāo)記)的對(duì)象。因此,通常來(lái)說(shuō),表達(dá)式 1 + 2 在編譯器看來(lái)等同于 1.+(2)。

但是對(duì)于列表而言,這些都不適合 — 系統(tǒng)中的每個(gè)類都需要對(duì)系統(tǒng)中的所有類型使用 :: 方法,而這嚴(yán)重違背了關(guān)注點(diǎn)分離原則。

Scala 的修復(fù)方法是:以冒號(hào)結(jié)束的任何具有奇怪名稱的方法(例如 :: 或 :::,甚至是我自己創(chuàng)建的方法,比如 foo:)都是右關(guān)聯(lián)的。因此,比方說(shuō),a :: b :: c :: Nil 轉(zhuǎn)換為 Nil.::(c.::(b.::(a))),后者正是我需要的:List 在首位,這樣每次調(diào)用 :: 都可以獲取對(duì)象參數(shù)并返回一個(gè) List,并繼續(xù)執(zhí)行下去。

最好為其他命名約定指定右關(guān)聯(lián)屬性,但是在撰寫(xiě)本文之際,Scala 已將這條規(guī)則硬編碼到該語(yǔ)言中。就目前來(lái)說(shuō),冒號(hào)是惟一觸發(fā)右關(guān)聯(lián)行為的字符。

在 Scala 中,列表的一種最強(qiáng)大的用法是與模式匹配結(jié)合。由于列表不僅可以匹配類型和值,它還可以同時(shí)綁定變量。例如,我可以簡(jiǎn)化清單 10 的列表代碼,方法是使用模式匹配區(qū)別一個(gè)至少具有一個(gè)元素的列表和一個(gè)空列表:

清單 11. 結(jié)合使用模式匹配和列表

@Test def recurseWithPM =  {    val myVIPList = "Ted" :: "Amanda" :: "Luke" :: "Don" :: "Martin" :: Nil        def count(VIPs : List[String]) : Int =    {      VIPs match      {        case h :: t => count(t) + 1       case Nil => 0     }    }        assertEquals(count(myVIPList), myVIPList.length)  }

在第一個(gè) case 表達(dá)式中,將提取列表頭部并綁定到變量 h,而其余部分(尾部)則綁定到 t;在本例中,沒(méi)有對(duì) h 執(zhí)行任何操作(實(shí)際上,更好的方法是指明這個(gè)頭部永遠(yuǎn)不會(huì)被使用,方法是使用一個(gè)通配符 _ 代替 h,這表明它是永遠(yuǎn)不會(huì)使用到的變量的占位符)。但是 t 被遞歸地傳遞給 count,和前面的示例一樣。還要注意,Scala 中的每一個(gè)表達(dá)式將隱式返回一個(gè)值;在本例中,模式匹配表達(dá)式的結(jié)果是遞歸調(diào)用 count + 1,當(dāng)達(dá)到列表結(jié)尾時(shí),結(jié)果為 0。

考慮到相同的代碼量,使用模式匹配的價(jià)值體現(xiàn)在哪里?實(shí)際上,對(duì)于比較簡(jiǎn)單的代碼,模式匹配的價(jià)值不很明顯。但是對(duì)于稍微復(fù)雜的代碼,例如擴(kuò)展示例以匹配特定值,那么模式匹配非常有幫助。

清單 12. 模式匹配

@Test def recurseWithPMAndSayHi =  {    val myVIPList = "Ted" :: "Amanda" :: "Luke" :: "Don" :: "Martin" :: Nil        var foundAmanda = false   def count(VIPs : List[String]) : Int =    {      VIPs match      {        case "Amanda" :: t =>          System.out.println("Hey, Amanda!"); foundAmanda = true; count(t) + 1       case h :: t =>          count(t) + 1       case Nil =>          0     }    }        assertEquals(count(myVIPList), myVIPList.length)    assertTrue(foundAmanda)  }

示例很快會(huì)變得非常復(fù)雜,特別是正則表達(dá)式或 XML 節(jié)點(diǎn),開(kāi)始大量使用模式匹配方法。模式匹配的使用同樣不局限于列表;我們沒(méi)有理由不把它擴(kuò)展到前面的數(shù)組示例中。事實(shí)上,以下是前面的 recurseWithPMAndSayHi 測(cè)試的數(shù)組示例:

清單 13. 將模式匹配擴(kuò)展到數(shù)組

@Test def recurseWithPMAndSayHi =  {    val myVIPList = Array("Ted", "Amanda", "Luke", "Don", "Martin")     var foundAmanda = false       myVIPList.foreach((s) =>      s match      {        case "Amanda" =>          System.out.println("Hey, Amanda!")          foundAmanda = true       case _ =>          ; // Do nothing      }    )     assertTrue(foundAmanda)  }

如果希望進(jìn)行實(shí)踐,那么嘗試構(gòu)建清單 13 的遞歸版本,但這不用在 recurseWithPMAndSayHi 范圍內(nèi)聲明一個(gè)可修改的 var。提示:需要使用多個(gè)模式匹配代碼塊

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“從Java走進(jìn)Scala如何使用元組、數(shù)組和列表”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI