《Scala程序設計(Ver.2)》讀書筆記

第一章


  1. 命令行中使用:load命令來加載(編譯并運行)文件(腳本文件):scala> :load example.scala
  2. 編譯為字節碼文件:
    • 腳本:將腳本內容封裝入一個指定的類中,使用>scalac -Xscript MyClass example.scala于是會生成MyClass.class的文件。
    • 有package、類描述的代碼文件:使用>scalac example.scala
  3. 對于生成的字節碼文件,假定文件的包在com.example下,使用>scala -cp . com.example.MyClass來運行。
  4. 如果想要java來編譯生成的scala的字節碼文件,需要用到scala-library.jar文件,假定該文件在C:\Users\Berlin\.sbt\boot\scala-2.10.6\lib下,則寫為>java -cp .;C:\Users\Berlin\.sbt\boot\scala-2.10.6\lib\scala-library.jar MyClass
  5. val foo定義一個不可變的量,var foo定義了一個可變的量。
  6. 類的定義:
    class Upper {
        def upper(strings: String*): Seq[String] = {
            strings.map((s:String) => s.toUpperCase())
        }
    }
    val up = new Upper
    println(up.upper("Hello", "World!"))
    
    • 定義了名為Upper的類,其中有名為upper的方法,
    • 方法接受String類型參數,參數名為strings,個數為任意個(因為String后有一個星號*)。
    • 返回類型為Seq泛型類型(序列),類型為String,相當于Java語法的Seq<String>
    • class Upper后沒有參數列表(比如class Upper(name:String,age:Int){ def upper... }),因此構造方法沒有參數,所以不用寫括號,直接寫val up = new Upper
  7. 方法定義
    def methodName( parameter1 : Type1,parameter2 : Type2 ) : returnedType = { //...method Body }
    • 返回類型通常可以省略(遞歸時要寫)
    • 在方法體只有一個語句時可以省略花括號
    • 方法體最后一句表達式的值就是返回值。
    • 返回空可以寫成...) : Unit = {//......}
  8. 單例對象
    object Upper {
        def upper(strings: String*) = strings.map(_.toUpperCase())
    }
    println(Upper.upper("Hello", "World!"))
    
    • object關鍵字定義了一個名為Upper的單例對象,
    • Scala 運行時只會創建Upper 的一個實例。也就是說,你無法通過new 創建Upper 對象。就好像Java 使用靜態類型一樣
    • 形如( foo : Type ) => foo.method()( foo : Type ) => method(a,foo,b,c)可以簡寫_.method()method(a,_,b,c)
  9. Scala 不支持靜態類型
  10. 插值字符串:以s表示:println( s"Hello ${ this.name } "),則變量值會被替換進去。切記以s為標識
  11. case class:
    • 支持模式匹配,多用于模式匹配
    • 必須有參數列表,就算沒有也要有括號:case class Clazz(){....
    • 參數列表中的參數為public,并且是val,即不可變,可以外部訪問
    • case class創建實例時可以不用加new(普通class必須加new)
    • 默認實現了toString、hashCode、equals方法
    • 默認是可以序列化的,也就是實現了Serializable ;
    • 同時創建了伴生object,并實現apply方法
    • 更多參考...
  12. 伴生對象:case class會生成與其同名的單例對象(object),它實現了apply方法。這個方法是一個工廠方法,使用case class生成實例時不用new,就是因為scala可以自動尋找apply方法產生一個實例對象。因此,假設Point是一個case class,則這兩句話是等價的:
    • val p1 = Point.apply(1.0, 2.0)
    • val p2 = Point(1.0, 2.0)
  13. 可以自己定義伴生對象。任何時候只要對象名(object Clazz {...})和類名(class Clazz {...})相同并且定義在同一個文件中,這些對象就能稱作伴生對象。
    在伴生對象中可以定義自己的apply,然后使用類名(參數列表)即可使用
    object Singleton {
        def apply(age:Int,name:String):Unit =
            println(s"your name is ${name}, age is ${age}");
    }
    
    Singleton(name="Amy",age=100);
    
    //輸出:your name ....
    
    但是如果這么寫就會有問題:
    class CaseClass_{
        def apply(s:Int) ={println("good")}
    }
    CaseClass_(5)
    
    輸出錯誤信息:error: not found: value CaseClass_
  14. equals方法:scala的==會映射為equals方法,即進行值比較(包括對象)。若比較對象的內存地址,則使用eqneobj1 eq obj2
  15. 嵌套導入:
    object Messages { 
        object Exit           // 沒有類體
        object Finished
        case class Response(message: String) 
    }
    class ShapesDrawingActor { 
        import Messages._   //只在這個類范圍內生效:導入Messages對象內容,可以直接使用,如Exit,而不用寫全稱Messages.Exit
        def ....
    
  16. 在Scala 中,main 方法必須為對象方法(object)。(在Java 中,main 方法必須是類靜態方法:
     object Test{     //而不是 class Test,否則會提示 CaseClass_.main is not static
          def main(args: Array[String]) = {
                //.......
           }
      }
    

第二章


  1. 變量聲明: val/var name : Type = value

    • 不可變:val array: Array[String] = new Array(5)
    • 可變:var stockPrice: Double = 100.0
      變量聲明的同時必須立即初始化。(例外:如構造函數的參數列表:class Person(val name: String, var age: Int)
  2. Range: 支持Range 的類型包括Int、Long、Float、Double、Char、BigInt、BigDecimal

    • 1 to 10 : [1,10]
    • 1 until 10:[1,10)
    • 1 to 10 by 3
    • 10 to 1 by -3
    • 1L to 10L by 3
    • 'a' to 'g' by 3
    • BigDecimal(1.1) to BigDecimal(10.3) by 3.1
  3. 偏函數:在偏函數中只能使用case 語句(處理那些能與至少一個case 語句匹配的輸入,輸入卻與所有語句都不匹配,系統就會拋出一個MatchError),而整個函數必須用花括號包圍。

    object CaseClass_{
    def main(args: Array[String]) = {
            var func:PartialFunction[Any,String]={   //輸入任意類型,返回字符串
                case s:String=>    //匹配String,值賦予s
                    "hello "+s
                case w:Int =>    //匹配Int,值賦予w
                    "This is Int"
                case whatAreYouTalking=>    //任意類型,賦予變量whatAreYouTalking
                    "Nothing" 
            }
            println(func("bbc"));
            println(func(123));
            println(func(3.14));
        }
    }
    

    輸出:

    hello bbc
    This is Int
    Nothing
    

    在偏函數上調用isDefinedAt方法可以檢測某個實例是否能被該偏函數匹配,返回是布爾值:func.isDefinedAt(3.14f)

  4. copy 方法:copy 方法也是case 類自動創建的。copy 方法允許你在創建case 類的新實例時只給出與原對象不同部分的參數。例如某case class方法的參數列表有x、y兩個值且都有默認值,則調用copy方法時只寫 p.copy(y=3.14),從而創建一個新的實例,它的y是3.14但是x是默認值。

  5. 方法具有多個參數列表

    • def draw (offset: Point = Point(0, 0)) (f: String => Unit) = {....} 有兩個參數列表
    • 使用方法:draw(Point(1, 2))(str => println("hello")
    • 允許把參數列表兩邊的圓括號替換為花括號: draw(Point(1, 2)){str => println("hello")}
    • 使用缺省的參數,第一個圓括號就不能省略:draw(){str => println("hello")}
    • 請注意區分函數體和參數列表,盡管在scala中它們都可用花括號包裹。
  6. 注意無論是不是多參數列表,只有為單一參數時才能大小括號混用,否則只能用小括號:

    scala> def s(a:Int)(b:String,c:Double)={
     | println(a)
     | println(b,c)
     | }
     scala> s{123}{"das",3.14} //因為第二個列表不是單參數,所以不能用花括號。
     <console>:1: error: ';' expected but ',' found.
       s{123}{"das",3.14}
                   ^
    

    參考: Scala之小括號和花括號(Parentheses & Crurly Braces)

  7. 多參數列表可以進行類型推斷:

    • def m1(a: Int, f : Int => String) = .....,則m1(100, i => s"$i + $i")會提示i的類型沒有給定
    • def m2(a: Int)(f: Int => String) = ...,則m2(100)(i => s"$i + $i")就米有錯,Scala可以推斷出i是Int類型。
  8. 方法的定義還可以嵌套:

    def outer() = {
        def inner() : Int ={ 
            ...
        }
        inner();
    }
    

    內部參數可以屏蔽同名外部參數

  9. 使用scala.annotation.tailrec的tailrec注解來檢查遞歸函數是否實現了尾遞歸,如果沒有則會拋出異常:

    import scala.annotation.tailrec
    @tailrec
    def method(...) : Type = { ...}
    
  10. 推斷類型信息

    • 在java等靜態語言中,要寫:HashMap<Integer, String> intToStringMap = new HashMap<Integer, String>();或者HashMap<Integer, String> intToStringMap = new HashMap<>();
    • 在Scala中只用寫:
      • val intToStringMap: HashMap[Integer, String] = new HashMap
      • val intToStringMap2 = new HashMap[Integer, String] 非顯式類型注解
    • 例如,
      def report(name:String)={
          val copy = name;   
          // copy沒有寫成 var copy:String =name。因為可以自動推斷出類型
          println(s"Your name is ${copy}")
      }
      report("Robust") //輸出:Your name is Robust
      
      但是如果寫成
      def report(name:String)={
         var copy :String;  // 或是 var copy,即不指定類型,但是都沒有初始化
         copy = name;   
         println(s"Your name is ${copy}")
      }
      report("Robust") //輸出:Your name is Robust
      
      則會報錯:
      • var copy;error: '=' expected but ';' found.
      • var copy :String;error: only traits and abstract classes can have declared but undefined members
  11. 需要顯式類型注解的情況

    • 在類中抽象聲明時,聲明了可變的var 變量或不可變的val 變量,但沒有進行初始化。
    • 所有的方法參數(如def deposit(amount: Money) = {…})。注意,如果寫成def deposit(var amount: Money) = ...def deposit(val amount: Money) = ...就會報出兩個錯誤:
      • error: identifier expected but 'var' found.
      • error: only traits and abstract classes can have declared but undefined members
        因此在方法的參數列表中不要寫val或var
    • 方法的返回值類型,在以下情況中必須顯式聲明其類型:
      • 明顯地使用了return
      • 遞歸方法
      • 兩個或多個方法重載(擁有相同的函數名),其中一個方法調用了另一個重載方法,調用者需要顯式類型注解。
      • Scala 推斷出的類型比你期望的類型更為寬泛,如Any。
  12. Scala中下劃線的用法

  13. _*的用法:設def joiner(strings: String*): String = {....}函數joiner是一個接受變長參數的函數,參數類型為String。則現在有一個列表strings: List[String],為了將其變為分隔的變長參數從而可以適應joiner的參數列表,可以使用:joiner ( strings: _*)的語法結構。這個可以這么來理解:

    • 變量標識符后面的冒號表示告訴編譯器這個變量是某種類型
    • 下劃線表示類型卻不是指定的,而是根據輸入推斷得出的。Scala 不允許你寫成strings :String *即使你需要為第一個joiner 方法指定的輸入類型就是String。(奇怪)
    • *指示是一個變長列表
      所以綜上所述,它就是告訴編譯器這個參數需要由列表類型“拆分”成變長參數列表。
  14. 返回類型推斷:最近公共類型。假定某方法不指定返回值,其中有一個if-else結構,if中返回List[Int]類型結果,而else返回List[String]結果,則Scala推斷出的返回值類型就是它們的公共父類型,即List[Any]

  15. 函數和過程:在scala中能夠定義函數。定義的函數可以有返回值,也可以沒有返回值。沒有返回值的叫做過程,有返回值的叫做函數。在語法上的區別是是否有等號

    • def greeting(name:String){ println(s"Hello ${name}"); 3.14159} 這是一個過程,因為參數列表和花括號之間沒有等號,所以它返回的是Unit盡管它會返回一個浮點數3.14159。打印它的結果是不可預知的,通常情況會打印一個()。例如,println(greeting("David"))會輸出Hello David以及()
    • def greeting(name:String)={ println(s"Hello ${name}")} 這是一個函數,盡管類型推斷認為它也返回Unit,并且println(greeting("David"))輸出結果和上面相同。
  16. break 和continue在Scala 中不存在

  17. 若方法中含有某些關鍵字,則使用單引號來表示。比如,java.util.Scanner.match,而match是Scala關鍵字,所以要寫java.util.Scanner.`match`

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容