溫馨提示×

溫馨提示×

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

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

Scala中的函數(shù)怎么理解

發(fā)布時間:2021-12-09 09:03:56 來源:億速云 閱讀:100 作者:iii 欄目:編程語言

這篇文章主要講解了“Scala中的函數(shù)怎么理解”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Scala中的函數(shù)怎么理解”吧!

普通 Scala 對象

與本系列之前的文章類似,我將使用 Person 類作為起點,探索 Scala 的繼承系統(tǒng)。清單 1 展示了 Person 的類定義:

清單 1. 嘿,我是人類

// This is Scala  class Person(val firstName:String, val lastName:String, val age:Int)  {    def toString = "[Person: firstName="+firstName+" lastName="+lastName+                           " age="+age+"]" }

Person 是一個非常簡單的 POSO(普通 Scala 對象,Plain Old Scala Object),具有三個只讀字段。您可能會想起,要使這些字段可以讀寫,只需將主構(gòu)造函數(shù)聲明中的 val 更改為 var 即可

無論如何,使用 Person 類型也非常簡單,如清單 2 所示:

清單 2. PersonApp

// This is Scala  object PersonApp  {    def main(args : Array[String]) : Unit =    {      val bindi = new Person("Tabinda", "Khan", 38)      System.out.println(bindi)    }  }

這算不上什么令人驚訝的代碼,但給我們提供了一個起點。

Scala 中的抽象方法

隨著該系統(tǒng)的發(fā)展,越來越明顯地意識到 Person 類缺乏一個成為 Person 的重要部分,這個部分是做些事情 的行為。許多人都會根據(jù)我們在生活中的作為來定義自己,而不是根據(jù)現(xiàn)有和占用的空間。因此,我會添加一個新方法,如清單 3 所示,這賦予了 Person 一些意義:

清單 3. 很好,做些事情!

// This is Scala  class Person(val firstName:String, val lastName:String, val age:Int)  {    override def toString = "[Person: firstName="+firstName+" lastName="+lastName+                            " age="+age+"]"    def doSomething = // uh.... what?  }

這帶來了一個問題:Person 的用途究竟是什么?有些 Person 繪畫,有些唱歌,有些編寫代碼,有些玩視頻游戲,有些什么也不做(問問十幾歲青少年的父母)。因此,我會為 Person 創(chuàng)建 子類,而不是嘗試去將這些活動直接整合到 Person 本身之中,如清單 4 所示:

清單 4. 這個人做的事情很少

// This is Scala  class Person(val firstName:String, val lastName:String, val age:Int)  {    override def toString = "[Person: firstName="+firstName+" lastName="+lastName+                            " age="+age+"]"    def doSomething = // uh.... what?  }   class Student(firstName:String, lastName:String, age:Int)    extends Person(firstName, lastName, age)  {    def doSomething =    {      System.out.println("I'm studying hard, Ma, I swear! (Pass the beer, guys!)")    }  }

當嘗試編譯代碼時,我發(fā)現(xiàn)無法編譯。這是因為 Person.doSomething 方法的定義無法工作;這個方法需要一個完整的主體(或許可拋出異常來表示它應(yīng)在繼承類中被覆蓋),或者不需要主體,類似于 Java 代碼中抽象方法的工作方式。我在清單 5 中嘗試使用抽象的方法:

清單 5. 抽象類 Person

// This is Scala  abstract class Person(val firstName:String, val lastName:String, val age:Int)  {    override def toString = "[Person: firstName="+firstName+" lastName="+lastName+                            " age="+age+"]"    def doSomething; // note the semicolon, which is still optional                     // but stylistically I like having it here  }   class Student(firstName:String, lastName:String, age:Int)    extends Person(firstName, lastName, age)  {    def doSomething =    {      System.out.println("I'm studying hard, Ma, I swear! (Pass the beer, guys!)")    }  }

請注意,我如何使用 abstract 關(guān)鍵字裝飾 Person 類。abstract 為編譯器指出,是的,這個類應(yīng)該是抽象的。在這方面,Scala 與 Java 語言沒有區(qū)別。

對象,遇到函數(shù)

由于 Scala 融合了對象和函數(shù)語言風(fēng)格,我實際上建模了 Person(如上所述),但并未創(chuàng)建子類型。這有些古怪,但強調(diào)了 Scala 對于這兩種設(shè)計風(fēng)格的整合,以及隨之而來的有趣理念。

回憶 前幾期文章,Scala 將函數(shù)作為值處理,就像處理語言中的其他值一樣,例如 Int、Float 或 Double。在建模 Person 時,我可以利用這一點來獲得 doSomething,不僅將其作為一種繼承類中覆蓋的方法,還將其作為可調(diào)用、替換、擴展的 函數(shù)值。清單 6 展示了這種方法:

清單 6. 努力工作的人

// This is Scala      class Person(val firstName:String, val lastName:String, val age:Int)  {    var doSomething : (Person) => Unit =       (p:Person) => System.out.println("I'm " + p + " and I don't do anything yet!");          def work() =      doSomething(this)          override def toString = "[Person: firstName="+firstName+" lastName="+lastName+                            " age="+age+"]" }   object App  {    def main(args : Array[String]) =    {      val bindi = new Person("Tabinda", "Khan", 38)      System.out.println(bindi)            bindi.work()            bindi.doSomething =        (p:Person) => System.out.println("I edit textbooks")              bindi.work()            bindi.doSomething =        (p:Person) => System.out.println("I write HTML books")              bindi.work()    }  }

將函數(shù)作為***建模工具是 Ruby、Groovy 和 ECMAScript(也就是 JavaScript)等動態(tài)語言以及許多函數(shù)語言的常用技巧。盡管其他語言也可以用函數(shù)作為建模工具,(C++ 通過函數(shù)指針和/或成員函數(shù)指針實現(xiàn),Java 代碼中通過接口引用的匿名內(nèi)部類實現(xiàn)),但所需的工作比 Scala(以及 Ruby、Groovy、ECMAScript 和其他語言)多得多。這是函數(shù)語言使用的 “高階函數(shù)” 概念的擴展。

多虧 Scala 將函數(shù)視為值,這樣您就可以在運行時需要切換功能的時候利用函數(shù)值??蓪⑦@種方法視為角色模式 —— Gang of Four 戰(zhàn)略模式的一種變體,在這種模式中,對象角色(例如 Person 的當前就職狀態(tài))作為運行時值得到了更好的表現(xiàn),比靜態(tài)類型的層次結(jié)構(gòu)更好。

層次結(jié)構(gòu)上層的構(gòu)造函數(shù)

回憶一下編寫 Java 代碼的日子,有時繼承類需要從構(gòu)造函數(shù)傳遞參數(shù)至基類構(gòu)造函數(shù),從而使基類字段能夠初始化。在 Scala 中,由于主構(gòu)造函數(shù)出現(xiàn)在類聲明中,不再是類的 “傳統(tǒng)” 成員,因而將參數(shù)傳遞到基類將成為一個全新維度的問題。

在 Scala 中,主構(gòu)造函數(shù)的參數(shù)在 class 行傳遞,但您也可以為這些參數(shù)使用 val 修飾符,以便在類本身上輕松引入讀值器(對于 var,則為寫值器)。

因此,清單 5 中的 Scala 類 Person 轉(zhuǎn)變?yōu)榍鍐?7 中的 Java 類,使用 javap 查看:

清單 7. 請翻譯一下

// This is javap  C:\Projects\scala-inheritance\code>javap -classpath classes Person  Compiled from "person.scala" public abstract class Person extends java.lang.Object implements scala.ScalaObje  ct{      public Person(java.lang.String, java.lang.String, int);      public java.lang.String toString();      public abstract void doSomething();      public int age();      public java.lang.String lastName();      public java.lang.String firstName();      public int $tag();  }

JVM 的基本規(guī)則依然有效:Person 的繼承類在構(gòu)造時向基類傳遞某些內(nèi)容,而不管語言強調(diào)的是什么。(實際上,這并非完全 正確,但在語言嘗試規(guī)避此規(guī)則時,JVM 會表現(xiàn)失常,因此大多數(shù)語言仍然堅持通過某種方法為其提供支持。)當然,Scala 需要堅守此規(guī)則,因為它不僅需要保持 JVM 正常運作,而且還要保持 Java 基類正常運作。這也就是說,無論如何,Scala 必須實現(xiàn)一種語法,允許繼承類調(diào)用基類,同時保留允許我們在基類上引入讀值器和寫值器的語法。

為了將此放到更具體的上下文中,假設(shè)我通過以下方式編寫了 清單 5 中的 Student 類:

清單 8. 壞學(xué)生!

// This is Scala  // This WILL NOT compile  class Student(val firstName:String, val lastName:String, val age:Int)    extends Person(firstName, lastName, age)  {    def doSomething =    {      System.out.println("I'm studying hard, Ma, I swear! (Pass the beer, guys!)")    }  }

本例中的編譯器將運行很長一段時間,因為我嘗試為 Student 類引入一組新方法(firstName、lastName 和 age)。這些方法將與 Person 類上名稱類似的方法彼此沖突,Scala 編譯器不一定了解我是否正在嘗試覆蓋基類方法(這很糟糕,因為我可以在這些基類方法后隱藏實現(xiàn)和字段),或者引入相同名稱的新方法(這也很糟糕,因為我可以在這些基類方法后隱藏實現(xiàn)和字段)。簡而言之,您將看到如何成功覆蓋來自基類的方法,但那并不是我們目前要追求的目標。

您還應(yīng)注意到,在 Scala 中,Person 構(gòu)造函數(shù)的參數(shù)不必一對一地與傳遞給 Student 的參數(shù)聯(lián)系起來;這里的規(guī)則實際上與 Java 構(gòu)造函數(shù)的規(guī)則完全相同。我們這樣做只是為了便于閱讀。同樣,Student 可要求額外的構(gòu)造函數(shù)參數(shù),與在 Java 語言中一樣,如清單 9 所示:

清單 9. 苛求的學(xué)生!

// This is Scala  class Student(firstName:String, lastName:String, age:Int, val subject:String)    extends Person(firstName, lastName, age)  {    def doSomething =    {      System.out.println("I'm studying hard, Ma, I swear! (Pass the beer, guys!)")    }  }

您又一次看到了 Scala 代碼與 Java 代碼有多么的相似,至少涉及繼承和類關(guān)系時是這樣。

語法差異

至此,您可能會對語法的細節(jié)感到迷惑。畢竟 Scala 并未像 Java 語言那樣將字段與方法區(qū)分開來。這實際上是一項深思熟慮的設(shè)計決策,允許 Scala 程序員輕而易舉地向使用基類的用戶 “隱藏” 字段和方法之間的差異。考慮清單 10:

清單 10. 我是什么?

// This is Scala  abstract class Person(val firstName:String, val lastName:String, val age:Int)  {    def doSomething        def weight : Int          override def toString = "[Person: firstName="+firstName+" lastName="+lastName+                            " age="+age+"]" }   class Student(firstName:String, lastName:String, age:Int, val subject:String)    extends Person(firstName, lastName, age)  {    def weight : Int =      age // students are notoriously skinny     def doSomething =    {      System.out.println("I'm studying hard, Ma, I swear! (Pass the beer, guys!)")    }  }   class Employee(firstName:String, lastName:String, age:Int)    extends Person(firstName, lastName, age)  {    val weight : Int = age * 4 // Employees are not skinny at all     def doSomething =    {      System.out.println("I'm working hard, hon, I swear! (Pass the beer, guys!)")    }  }

注意查看如何定義 weight 使其不帶有任何參數(shù)并返回 Int。這是 “無參數(shù)方法”。因為它看上去與 Java 語言中的 “專有” 方法極其相似,Scala 實際上允許將 weight 定義為一種方法(如 Student 中所示),也允許將其定義為字段/存取器(如 Employee 中所示)。這種句法決策使您在抽象類繼承的實現(xiàn)方面有一定的靈活性。請注意,在 Java 中,即便是在同一個類中,只有通過 get/set 方法來訪問各字段時,才能獲得類似的靈活性。不知道判斷正確與否,但我認為只有少數(shù) Java 程序員會用這種方式編寫代碼,因此不經(jīng)常使用靈活性。此外,Scala 的方法可像處理公共成員一樣輕松地處理隱藏/私有成員。

從 @Override 到 override

繼承類經(jīng)常需要更改在其某個基類內(nèi)定義的方法的行為;在 Java 代碼中,我們通過為繼承類添加相同名稱、相同簽名的新方法來處理這個問題。這種方法的缺點在于簽名錄入的錯誤或含糊不清可能會導(dǎo)致沒有征兆的故障,這也就意味著代碼可以編譯,但在運行時無法正確完成操作。

為解決這個問題,Java 5 編譯器引入了 @Override 注釋。@Override 驗證引入繼承類的方法實際上已經(jīng)覆蓋了基類方法。在 Scala 中,override 已經(jīng)成為語言的一部分,幾乎可以忘記它會生成編譯器錯誤。因而,繼承 toString() 方法應(yīng)如清單 11 所示:

清單 11. 這是繼承的結(jié)果

// This is Scala  class Student(firstName:String, lastName:String, age:Int, val subject:String)    extends Person(firstName, lastName, age)  {    def weight : Int =      age // students are notoriously skinny     def doSomething =    {      System.out.println("I'm studying hard, Ma, I swear! (Pass the beer, guys!)")    }        override def toString = "[Student: firstName="+firstName+                            " lastName="+lastName+" age="+age+                            " subject="+subject+"]" }

非常簡單明了。

敲定

當然,允許繼承覆蓋的反面就是采取措施防止它:基類需要禁止子類更改其基類行為,或禁止任何類型的繼承類。在 Java 語言中,我們通過為方法應(yīng)用修飾符 final 來實現(xiàn)這一點,確保它不會被覆蓋。此外,也可以為類整體應(yīng)用 final,防止繼承。實現(xiàn)層次結(jié)構(gòu)在 Scala 中的效果是相同的:我們可以向方法應(yīng)用 final 來防止子類覆蓋它,也可應(yīng)用于類聲明本身來防止繼承。

牢記,所有這些關(guān)于 abstract、final 和 override 的討論都同樣適用于 “名字很有趣的方法”(Java 或 C# 或 C++ 程序員可能會這樣稱呼運算符),與應(yīng)用于常規(guī)名稱方法的效果相同。因此,我們常常會定義一個基類或特征,為數(shù)學(xué)函數(shù)設(shè)定某些預(yù)期(可以稱之為 “Mathable”),這些函數(shù)定義抽象成員函數(shù) “+”、“-”、“*” 和 “/”,另外還有其他一些應(yīng)該支持的數(shù)學(xué)運算,例如 pow 或 abs。隨后,其他程序員可創(chuàng)建其他類型 — 可能是一個 Matrix 類,實現(xiàn)或擴展 “Mathable”,定義一些成員,看上去就像 Scala 以開箱即用的方式提供的內(nèi)置算術(shù)類型。

差別在于……

如果 Scala 能夠如此輕松地映射到 Java 繼承模型(就像本文至此您看到的那樣),就應(yīng)該能夠從 Java 語言繼承 Scala 類,或反之。實際上,這必須 可行,因為 Scala 與其他編譯為 Java 字節(jié)碼的語言相似,必須生成繼承自 java.lang.Object 的對象。請注意,Scala 類可能也要繼承自其他內(nèi)容,例如特征,因此實際繼承的解析和代碼生成的工作方式可能有所不同,但最終我們必須能夠以某種形式繼承 Java 基類。(切記,特征類似于有行為的接口,Scala 編譯器將特征分成接口并將實現(xiàn)推入特征編譯的目標類中,通過這種方式來使之運作。)

但結(jié)果表明,Scala 的類型層次結(jié)構(gòu)與 Java 語言中的對應(yīng)結(jié)構(gòu)略有不同;從技術(shù)上來講,所有 Scala 類繼承的基類(包括 Int、Float、Double 和其他數(shù)字類型)都是 scala.Any 類型,這定義了一組核心方法,可在 Scala 內(nèi)的任意類型上使用:==、!=、equals、hashCode、toString、isInstanceOf 和 asInstanceOf,大多數(shù)方法通過名稱即可輕松理解。在這里,Scala 劃分為兩大分支,“原語類型” 繼承自 scala.AnyVal;“類類型” 繼承自 scala.AnyRe。(scala.ScalaObject 又繼承自 scala.AnyRef。)

通常,這并不是您要直接去操心的方面,但在考慮跨兩種語言的繼承時,可能會帶來某些非常有趣的副作用。例如,考慮清單 12 中的 ScalaJavaPerson:

清單 12. 混合!

  // This is Scala  ass ScalaJavaPerson(firstName:String, lastName:String, age:Int)  extends JavaPerson(firstName, lastName, age)   val weight : Int = age * 2 // Who knows what Scala/Java people weigh?   override def toString = "[SJPerson: firstName="+firstName+                          " lastName="+lastName+" age="+age+"]"

……它繼承自 JavaPerson:

清單 13. 看起來是否眼熟?

    // This is Java  public class JavaPerson  {      public JavaPerson(String firstName, String lastName, int age)      {          this.firstName = firstName;          this.lastName = lastName;          this.age = age;      }            public String getFirstName()      {          return this.firstName;      }      public void setFirstName(String value)      {          this.firstName = value;      }            public String getLastName()      {          return this.lastName;      }      public void setLastName(String value)      {          this.lastName = value;      }            public int getAge()      {          return this.age;      }      public void setAge(int value)      {          this.age = value;      }            public String toString()      {          return "[Person: firstName" + firstName + " lastName:" + lastName +              " age:" + age + " ]";      }            private String firstName;      private String lastName;      private int age;  }

在編譯 ScalaJavaPerson 時,它將照常擴展 JavaPerson,但按照 Scala 的要求,它還會實現(xiàn) ScalaObject 接口。并照例支持繼承自 JavaPerson 的方法,因為 ScalaJavaPerson 是一種 Scala 類型,我們可以期望它支持 Any 引用的指派,根據(jù) Scala 的規(guī)則:

清單 14. 使用 ScalaJavaPerson

// This is Scala      val richard = new ScalaJavaPerson("Richard", "Campbell", 45)  System.out.println(richard)  val host : Any = richard  System.out.println(host)

但在 Scala 中創(chuàng)建 JavaPerson 并將其指派給 Any 引用時會發(fā)生什么?

清單 15. 使用 JavaPerson

// This is Scala          val carl = new JavaPerson("Carl", "Franklin", 35)      System.out.println(carl)      val host2 : Any = carl      System.out.println(host2)

結(jié)果顯示,這段代碼如期編譯并運行,因為 Scala 能確保 JavaPerson “做正確的事情”,這要歸功于 Any 類型與 java.lang.Object 類型的相似性。實際上,幾乎可以說,所有擴展 java.lang.Object 的內(nèi)容都支持存儲到 Any 引用之中。(存在一些極端情況,我聽說過,但我自己還從未遇到過這樣的極端情況。)

最終結(jié)果?出于實踐的目的,我們可以跨 Java 語言和 Scala 混搭繼承,而無需過分擔(dān)心。

感謝各位的閱讀,以上就是“Scala中的函數(shù)怎么理解”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Scala中的函數(shù)怎么理解這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

向AI問一下細節(jié)

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

AI