第一章
- 命令行中使用
:load
命令来加载(编译并运行)文件(脚本文件):scala> :load example.scala
- 编译为字节码文件:
- 脚本:将脚本内容封装入一个指定的类中,使用
>scalac -Xscript MyClass example.scala
于是会生成MyClass.class
的文件。 - 有package、类描述的代码文件:使用
>scalac example.scala
。
- 脚本:将脚本内容封装入一个指定的类中,使用
- 对于生成的字节码文件,假定文件的包在com.example下,使用
>scala -cp . com.example.MyClass
来运行。 - 如果想要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
- val foo定义一个不可变的量,var foo定义了一个可变的量。
- 类的定义:
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
。
- 方法定义
def methodName( parameter1 : Type1,parameter2 : Type2 ) : returnedType = { //...method Body }
- 返回类型通常可以省略(递归时要写)
- 在方法体只有一个语句时可以省略花括号
- 方法体最后一句表达式的值就是返回值。
- 返回空可以写成
...) : Unit = {//......}
- 单例对象
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)
- Scala 不支持静态类型
- 插值字符串:以s表示:
println( s"Hello ${ this.name } ")
,则变量值会被替换进去。切记以s为标识。 -
case class:
- 支持模式匹配,多用于模式匹配
- 必须有参数列表,就算没有也要有括号:
case class Clazz(){....
- 参数列表中的参数为public,并且是val,即不可变,可以外部访问
- case class创建实例时可以不用加new(普通class必须加new)
- 默认实现了toString、hashCode、equals方法
- 默认是可以序列化的,也就是实现了Serializable ;
- 同时创建了伴生object,并实现apply方法
- 更多参考...
- 伴生对象: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)
- 可以自己定义伴生对象。任何时候只要对象名(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 ....
输出错误信息:error: not found: value CaseClass_class CaseClass_{ def apply(s:Int) ={println("good")} } CaseClass_(5)
- equals方法:scala的==会映射为equals方法,即进行值比较(包括对象)。若比较对象的内存地址,则使用
eq
或ne
:obj1 eq obj2
- 嵌套导入:
object Messages { object Exit // 没有类体 object Finished case class Response(message: String) } class ShapesDrawingActor { import Messages._ //只在这个类范围内生效:导入Messages对象内容,可以直接使用,如Exit,而不用写全称Messages.Exit def ....
- 在Scala 中,main 方法必须为对象方法(object)。(在Java 中,main 方法必须是类静态方法:
object Test{ //而不是 class Test,否则会提示 CaseClass_.main is not static def main(args: Array[String]) = { //....... } }
第二章
-
变量声明: 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)
)
- 不可变:
-
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
-
偏函数:在偏函数中只能使用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)
copy 方法:copy 方法也是case 类自动创建的。copy 方法允许你在创建case 类的新实例时只给出与原对象不同部分的参数。例如某case class方法的参数列表有x、y两个值且都有默认值,则调用copy方法时只写
p.copy(y=3.14)
,从而创建一个新的实例,它的y是3.14但是x是默认值。-
方法具有多个参数列表:
-
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中它们都可用花括号包裹。
-
-
注意无论是不是多参数列表,只有为单一参数时才能大小括号混用,否则只能用小括号:
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} ^
-
多参数列表可以进行类型推断:
-
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类型。
-
-
方法的定义还可以嵌套:
def outer() = { def inner() : Int ={ ... } inner(); }
内部参数可以屏蔽同名外部参数
-
使用
scala.annotation.tailrec
的tailrec注解来检查递归函数是否实现了尾递归,如果没有则会抛出异常:import scala.annotation.tailrec @tailrec def method(...) : Type = { ...}
-
推断类型信息
- 在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
-
- 在java等静态语言中,要写:
-
需要显式类型注解的情况
- 在类中抽象声明时,声明了可变的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。
-
_*
的用法:设def joiner(strings: String*): String = {....}
函数joiner是一个接受变长参数的函数,参数类型为String。则现在有一个列表strings: List[String]
,为了将其变为分隔的变长参数从而可以适应joiner的参数列表,可以使用:joiner ( strings: _*)
的语法结构。这个可以这么来理解:- 变量标识符后面的冒号表示告诉编译器这个变量是某种类型
- 下划线表示类型却不是指定的,而是根据输入推断得出的。Scala 不允许你写成
strings :String *
,即使你需要为第一个joiner 方法指定的输入类型就是String。(奇怪) -
*
指示是一个变长列表
所以综上所述,它就是告诉编译器这个参数需要由列表类型“拆分”成变长参数列表。
返回类型推断:最近公共类型。假定某方法不指定返回值,其中有一个
if-else
结构,if中返回List[Int]类型结果,而else返回List[String]结果,则Scala推断出的返回值类型就是它们的公共父类型,即List[Any]。-
函数和过程:在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"))
输出结果和上面相同。
-
break 和continue在Scala 中不存在。
若方法中含有某些关键字,则使用单引号来表示。比如,
java.util.Scanner.match
,而match是Scala关键字,所以要写java.util.Scanner.`match`