怎么理解Scala的類語法和語義

怎么理解Scala的類語法和語義,相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

成都一家集口碑和實力的網(wǎng)站建設(shè)服務(wù)商,擁有專業(yè)的企業(yè)建站團隊和靠譜的建站技術(shù),10余年企業(yè)及個人網(wǎng)站建設(shè)經(jīng)驗 ,為成都上千多家客戶提供網(wǎng)頁設(shè)計制作,網(wǎng)站開發(fā),企業(yè)網(wǎng)站制作建設(shè)等服務(wù),包括成都營銷型網(wǎng)站建設(shè),品牌網(wǎng)站制作,同時也為不同行業(yè)的客戶提供成都做網(wǎng)站、網(wǎng)站制作、成都外貿(mào)網(wǎng)站建設(shè)的服務(wù),包括成都電商型網(wǎng)站制作建設(shè),裝修行業(yè)網(wǎng)站制作建設(shè),傳統(tǒng)機械行業(yè)網(wǎng)站建設(shè),傳統(tǒng)農(nóng)業(yè)行業(yè)網(wǎng)站制作建設(shè)。在成都做網(wǎng)站,選網(wǎng)站制作建設(shè)服務(wù)商就選創(chuàng)新互聯(lián)公司。

Scala 的函數(shù)編程特性非常引人注目,但這并非 Java 開發(fā)人員應(yīng)該對這門語言感興趣的惟一原因。實際上,Scala 融合了函數(shù)概念和面向?qū)ο蟾拍?。為了?Java 和 Scala 程序員感到得心應(yīng)手,可以了解一下 Scala 的對象特性,看看它們是如何在語言方面與 Java 對應(yīng)的。記住,其中的一些特性并不是直接對應(yīng),或者說,在某些情況下,“對應(yīng)” 更像是一種類比,而不是直接的對應(yīng)。不過,遇到重要區(qū)別時,我會指出來。

Scala 和 Java 一樣使用類

我們不對 Scala 支持的類特性作冗長而抽象的討論,而是著眼于一個類的定義,這個類可用于為 Scala 平臺引入對有理數(shù)的支持:

清單 1. rational.scala

class Rational(n:Int, d:Int)  {    private def gcd(x:Int, y:Int): Int =    {      if (x==0) y      else if (x<0) gcd(-x, y)      else if (y<0) -gcd(x, -y)      else gcd(y%x, x)    }    private val g = gcd(n,d)        val numer:Int = n/g    val denom:Int = d/g        def +(that:Rational) =      new Rational(numer*that.denom + that.numer*denom, denom * that.denom)    def -(that:Rational) =      new Rational(numer * that.denom - that.numer * denom, denom * that.denom)    def *(that:Rational) =      new Rational(numer * that.numer, denom * that.denom)    def /(that:Rational) =      new Rational(numer * that.denom, denom * that.numer)     override def toString() =      "Rational: [" + numer + " / " + denom + "]" }

從詞匯上看,清單 1 的整體結(jié)構(gòu)與 Java 代碼類似,但是,這里顯然還有一些新的元素。在詳細討論這個定義之前,先看一段使用這個新 Rational 類的代碼:

清單 2. RunRational

class Rational(n:Int, d:Int)  {    // ... as before  }   object RunRational extends Application  {    val r1 = new Rational(1, 3)    val r2 = new Rational(2, 5)    val r3 = r1 - r2    val r4 = r1 + r2    Console.println("r1 = " + r1)    Console.println("r2 = " + r2)    Console.println("r3 = r1 - r2 = " + r3)    Console.println("r4 = r1 + r2 = " + r4)  }

清單 2 中的內(nèi)容平淡無奇:先創(chuàng)建兩個有理數(shù),然后再創(chuàng)建兩個 Rational,作為前面兩個有理數(shù)的和與差,最后將這幾個數(shù)回傳到控制臺上(注意, Console.println() 來自 Scala 核心庫,位于 scala.* 中,它被隱式地導(dǎo)入每個 Scala 程序中,就像 Java 編程中的 java.lang 一樣)。

用多少種方法構(gòu)造類?

現(xiàn)在,回顧一下 Rational 類定義中的第一行:

清單 3. Scala 的默認構(gòu)造函數(shù)

class Rational(n:Int, d:Int)  {    // ...

您也許會認為清單 3 中使用了某種類似于泛型的語法,這其實是 Rational 類的默認的、首選的構(gòu)造函數(shù):n 和 d 是構(gòu)造函數(shù)的參數(shù)。

Scala 優(yōu)先使用單個構(gòu)造函數(shù),這具有一定的意義 —— 大多數(shù)類只有一個構(gòu)造函數(shù),或者通過一個構(gòu)造函數(shù)將一組構(gòu)造函數(shù) “鏈接” 起來。如果需要,可以在一個 Rational 上定義更多的構(gòu)造函數(shù),例如:

清單 4. 構(gòu)造函數(shù)鏈

class Rational(n:Int, d:Int)  {    def this(d:Int) = { this(0, d) }

注意,Scala 的構(gòu)造函數(shù)鏈通過調(diào)用首選構(gòu)造函數(shù)(Int,Int 版本)實現(xiàn) Java 構(gòu)造函數(shù)鏈的功能。

實現(xiàn)細節(jié)

在處理有理數(shù)時,采取一點數(shù)值技巧將會有所幫助:也就是說,找到公分母,使某些操作變得更容易。如果要將 1/2 與 2/4 相加,那么 Rational 類應(yīng)該足夠聰明,能夠認識到 2/4 和 1/2 是相等的,并在將這兩個數(shù)相加之前進行相應(yīng)的轉(zhuǎn)換。

嵌套的私有 gcd() 函數(shù)和 Rational 類中的 g 值可以實現(xiàn)這樣的功能。在 Scala 中調(diào)用構(gòu)造函數(shù)時,將對整個類進行計算,這意味著將 g 初始化為 n 和 d 的最大公分母,然后用它依次設(shè)置 n 和 d。

回顧一下 清單 1 就會發(fā)現(xiàn),我創(chuàng)建了一個覆蓋的 toString 方法來返回 Rational 的值,在 RunRational 驅(qū)動程序代碼中使用 toString 時,這樣做非常有用。

然而,請注意 toString 的語法:定義前面的 override 關(guān)鍵字是必需的,這樣 Scala 才能確認基類中存在相應(yīng)的定義。這有助于預(yù)防因意外的輸入錯誤導(dǎo)致難于覺察的 bug(Java 5 中創(chuàng)建 @Override 注釋的動機也在于此)。還應(yīng)注意,這里沒有指定返回類型 —— 從方法體的定義很容易看出 —— 返回值沒有用 return 關(guān)鍵字顯式地標注,而在 Java 中則必須這樣做。相反,函數(shù)中的最后一個值將被隱式地當作返回值(但是,如果您更喜歡 Java 語法,也可以使用 return 關(guān)鍵字)。

一些重要值

接下來分別是 numer 和 denom 的定義。這里涉及的語法可能讓 Java 程序員認為 numer 和 denom 是公共的 Int 字段,它們分別被初始化為 n-over-g 和 d-over-g;但這種想法是不對的。

在形式上,Scala 調(diào)用無參數(shù)的 numer 和 denom 方法,這種方法用于創(chuàng)建快捷的語法以定義 accessor。Rational 類仍然有 3 個私有字段:n、d 和 g,但是,其中的 n 和 d 被默認定義為私有訪問,而 g 則被顯式地定義為私有訪問,它們對于外部都是隱藏的。

此時,Java 程序員可能會問:“n 和 d 各自的 ‘setter’ 在哪里?” Scala 中不存在這樣的 setter。Scala 的一個強大之處就在于,它鼓勵開發(fā)人員以默認方式創(chuàng)建不可改變的對象。但是,也可使用語法創(chuàng)建修改 Rational 內(nèi)部結(jié)構(gòu)的方法,但是這樣做會破壞該類固有的線程安全性。因此,至少對于這個例子而言,我將保持 Rational 不變。

當然還有一個問題,如何操縱 Rational 呢?與 java.lang.String 一樣,不能直接修改現(xiàn)有的 Rational 的值,所以惟一的辦法是根據(jù)現(xiàn)有類的值創(chuàng)建一個新的 Rational,或者從頭創(chuàng)建。這涉及到 4 個名稱比較古怪的方法:+、 -、* 和 /。

與其外表相反,這并非操作符重載。

操作符

記住,在 Scala 中一切都是對象。在上一篇 文章 中, 您看到了函數(shù)本身也是對象這一原則的應(yīng)用,這使 Scala 程序員可以將函數(shù)賦予變量,將函數(shù)作為對象參數(shù)傳遞等等。另一個同樣重要的原則是,一切都是函數(shù);也就是說,在此處,命名為 add 的函數(shù)與命名為 + 的函數(shù)沒有區(qū)別。在 Scala 中,所有操作符都是類的函數(shù)。只不過它們的名稱比較古怪罷了。

在 Rational 類中,為有理數(shù)定義了 4 種操作。它們是規(guī)范的數(shù)學操作:加、減、乘、除。每種操作以它的數(shù)學符號命名:+、-、 * 和 /。

但是請注意,這些操作符每次操作時都構(gòu)造一個新的 Rational 對象。同樣,這與 java.lang.String 非常相似,這是默認的實現(xiàn),因為這樣可以產(chǎn)生線程安全的代碼(如果線程沒有修改共享狀態(tài) —— 默認情況下,跨線程共享的對象的內(nèi)部狀態(tài)也屬于共享狀態(tài) —— 則不會影響對那個狀態(tài)的并發(fā)訪問)。

有什么變化?

一切都是函數(shù),這一規(guī)則產(chǎn)生兩個重要影響:

首先,您已經(jīng)看到,函數(shù)可以作為對象進行操縱和存儲。這使函數(shù)具有強大的可重用性,本系列 第一篇文章 對此作了探討。

第二個影響是,Scala 語言設(shè)計者提供的操作符與 Scala 程序員認為應(yīng)該 提供的操作符之間沒有特別的差異。例如,假設(shè)提供一個 “求倒數(shù)” 操作符,這個操作符會將分子和分母調(diào)換,返回一個新的 Rational (即對于 Rational(2,5) 將返回 Rational(5,2))。如果您認為 ~ 符號最適合表示這個概念,那么可以使用此符號作為名稱定義一個新方法,該方法將和 Java 代碼中任何其他操作符一樣,如清單 5 所示:

清單 5. 求倒數(shù)

val r6 = ~r1  Console.println(r6) // should print [3 / 1], since r1 = [1 / 3]

在 Scala 中定義這種一元 “操作符” 需要一點技巧,但這只是語法上的問題而已:

清單 6. 如何求倒數(shù)

class Rational(n:Int, d:Int)  {    // ... as before ...     def unary_~ : Rational =      new Rational(denom, numer)  }

當然,需要注意的地方是,必須在名稱 ~ 之前加上前綴 “unary_”,告訴 Scala 編譯器它屬于一元操作符。因此,該語法將顛覆大多數(shù)對象語言中常見的傳統(tǒng) reference-then-method 語法。

這條規(guī)則與 “一切都是對象” 規(guī)則結(jié)合起來,可以實現(xiàn)功能強大(但很簡單)的代碼:

清單 7. 求和

1 + 2 + 3 // same as 1.+(2.+(3))  r1 + r2 + r3 // same as r1.+(r2.+(r3))

當然,對于簡單的整數(shù)加法,Scala 編譯器也會 “得到正確的結(jié)果”,它們在語法上是完全一樣的。這意味著您可以開發(fā)與 Scala 語言 “內(nèi)置” 的類型完全相同的類型。

Scala 編譯器甚至會嘗試推斷具有某種預(yù)定含義的 “操作符” 的其他含義,例如 += 操作符。注意,雖然 Rational 類并沒有顯式地定義 +=,下面的代碼仍然會正常運行:

清單 8. Scala 推斷

var r5 = new Rational(3,4)  r5 += r1  Console.println(r5)

打印結(jié)果時,r5 的值為 [13 / 12],結(jié)果是正確的。

Scala 內(nèi)幕

記住,Scala 將被編譯為 Java 字節(jié)碼,這意味著它在 JVM 上運行。如果您需要證據(jù),那么只需注意編譯器生成以 0xCAFEBABE 開頭的 .class 文件,就像 javac 一樣。另外請注意,如果啟動 JDK 自帶的 Java 字節(jié)碼反編譯器(javap),并將它指向生成的 Rational 類,將會出現(xiàn)什么情況,如清單 9 所示:

清單 9. 從 rational.scala 編譯的類

C:\Projects\scala-classes\code>javap -private -classpath classes Rational  Compiled from "rational.scala" public class Rational extends java.lang.Object implements scala.ScalaObject{      private int denom;      private int numer;      private int g;      public Rational(int, int);      public Rational unary_$tilde();      public java.lang.String toString();      public Rational $div(Rational);      public Rational $times(Rational);      public Rational $minus(Rational);      public Rational $plus(Rational);      public int denom();      public int numer();      private int g();      private int gcd(int, int);      public Rational(int);      public int $tag();  }    C:\Projects\scala-classes\code>

Scala 類中定義的 “操作符” 被轉(zhuǎn)換成傳統(tǒng) Java 編程中的方法調(diào)用,不過它們?nèi)允褂每瓷先ビ行┕殴值拿Q。類中定義了兩個構(gòu)造函數(shù):一個構(gòu)造函數(shù)帶有一個 int 參數(shù),另一個帶有兩個 int 參數(shù)。您可能會注意到,大寫的 Int 類型與 java.lang.Integer 有點相似,Scala 編譯器非常聰明,會在類定義中將它們轉(zhuǎn)換成常規(guī)的 Java 原語 int。

測試 Rational 類

一種著名的觀點認為,優(yōu)秀的程序員編寫代碼,偉大的程序員編寫測試;到目前為止,我還沒有對我的 Scala 代碼嚴格地實踐這一規(guī)則,那么現(xiàn)在看看將這個 Rational 類放入一個傳統(tǒng)的 JUnit 測試套件中會怎樣,如清單 10 所示:

清單 10. RationalTest.java

import org.junit.*;  import static org.junit.Assert.*;   public class RationalTest  {      @Test public void test2ArgRationalConstructor()      {          Rational r = new Rational(2, 5);           assertTrue(r.numer() == 2);          assertTrue(r.denom() == 5);      }            @Test public void test1ArgRationalConstructor()      {          Rational r = new Rational(5);           assertTrue(r.numer() == 0);          assertTrue(r.denom() == 1);              // 1 because of gcd() invocation during construction;              // 0-over-5 is the same as 0-over-1      }                @Test public void testAddRationals()      {          Rational r1 = new Rational(2, 5);          Rational r2 = new Rational(1, 3);           Rational r3 = (Rational) reflectInvoke(r1, "$plus", r2); //r1.$plus(r2);           assertTrue(r3.numer() == 11);          assertTrue(r3.denom() == 15);      }            // ... some details omitted  }

SUnit

現(xiàn)在已經(jīng)有一個基于 Scala 的單元測試套件,其名稱為 SUnit。如果將 SUnit 用于清單 10 中的測試,則不需要基于 Reflection 的方法?;?Scala 的單元測試代碼將針對 Scala 類進行編譯,所以編譯器可以構(gòu)成符號行。一些開發(fā)人員發(fā)現(xiàn),使用 Scala 編寫用于測試 POJO 的單元測試實際上更加有趣。

SUnit 是標準 Scala 發(fā)行版的一部分,位于 scala.testing 包中(要了解更多關(guān)于 SUnit 的信息,請參閱 參考資料)。

除了確認 Rational 類運行正常之外,上面的測試套件還證明可以從 Java 代碼中調(diào)用 Scala 代碼(盡管在操作符方面有點不匹配)。當然,令人高興的是,您可以將 Java 類遷移至 Scala 類,同時不必更改支持這些類的測試,然后慢慢嘗試 Scala。

您惟一可能覺得古怪的地方是操作符調(diào)用,在本例中就是 Rational 類中的 + 方法?;仡櫼幌?javap 的輸出,Scala 顯然已經(jīng)將 + 函數(shù)轉(zhuǎn)換為 JVM 方法 $plus,但是 Java 語言規(guī)范并不允許標識符中出現(xiàn) $ 字符(這正是它被用于嵌套和匿名嵌套類名稱中的原因)。

為了調(diào)用那些方法,需要用 Groovy 或 JRuby(或者其他對 $ 字符沒有限制的語言)編寫測試,或者編寫 Reflection 代碼來調(diào)用它。我采用后一種方法,從 Scala 的角度看這不是那么有趣,但是如果您有興趣的話,可以看看本文的代碼中包含的結(jié)果(參見 下載)。

注意,只有當函數(shù)名稱不是合法的 Java 標識符時才需要用這類方法。

“更好的” Java

我學習 C++ 的時候,Bjarne Stroustrup 建議,學習 C++ 的一種方法是將它看作 “更好的 C 語言”(參見 參考資料)。在某些方面,如今的 Java 開發(fā)人員也可以將 Scala 看作是 “更好的 Java”,因為它提供了一種編寫傳統(tǒng) Java POJO 的更簡潔的方式??紤]清單 11 中顯示的傳統(tǒng) Person POJO:

清單 11. JavaPerson.java(原始 POJO)

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;  }

現(xiàn)在考慮用 Scala 編寫的對等物:

清單 12. person.scala(線程安全的 POJO)

class Person(firstName:String, lastName:String, age:Int)  {      def getFirstName = firstName      def getLastName = lastName      def getAge = age       override def toString =          "[Person firstName:" + firstName + " lastName:" + lastName +              " age:" + age + " ]" }

這不是一個完全匹配的替換,因為原始的 Person 包含一些可變的 setter。但是,由于原始的 Person 沒有與這些可變 setter 相關(guān)的同步代碼,所以 Scala 版本使用起來更安全。而且,如果目標是減少 Person 中的代碼行數(shù),那么可以刪除整個 getFoo 屬性方法,因為 Scala 將為每個構(gòu)造函數(shù)參數(shù)生成 accessor 方法 —— firstName() 返回一個 String,lastName() 返回一個 String,age() 返回一個 int。

即使必須包含這些可變的 setter 方法,Scala 版本仍然更加簡單,如清單 13 所示:

清單 13. person.scala(完整的 POJO)

class Person(var firstName:String, var lastName:String, var age:Int)  {      def getFirstName = firstName      def getLastName = lastName      def getAge = age            def setFirstName(value:String):Unit = firstName = value      def setLastName(value:String) = lastName = value      def setAge(value:Int) = age = value       override def toString =          "[Person firstName:" + firstName + " lastName:" + lastName +              " age:" + age + " ]" }

注意,構(gòu)造函數(shù)參數(shù)引入了 var 關(guān)鍵字。簡單來說, var 告訴編譯器這個值是可變的。因此,Scala 同時生成 accessor( String firstName(void))和 mutator(void firstName_$eq(String))方法。然后,就可以方便地創(chuàng)建 setFoo 屬性 mutator 方法,它在幕后使用生成的 mutator 方法。

Scala 將函數(shù)概念與簡潔性相融合,同時又未失去對象的豐富特性。從本系列中您可能已經(jīng)看到,Scala 還修正了 Java 語言中的一些語法問題(后見之明)。

看完上述內(nèi)容,你們掌握怎么理解Scala的類語法和語義的方法了嗎?如果還想學到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!

當前名稱:怎么理解Scala的類語法和語義
分享鏈接:http://muchs.cn/article8/gjgcip.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信公眾號、外貿(mào)建站、軟件開發(fā)、網(wǎng)站導(dǎo)航、品牌網(wǎng)站建設(shè)網(wǎng)站排名

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站建設(shè)網(wǎng)站維護公司