1.Scala基础知识
1.1 基本数据类型和变量
基本数据类型包括Byte、Short、Int、Long、Char、Float、Double、String、Boolean。
Scala变量 从变量声明角度看,只有两种类型,val和var,对于val声明的变量,在声明时就必须初始化,并且不可变。对于var声明的变量,是可变的。声明变量的语法如下:
val 变量名[:数据类型] = 初始值
var 变量名[:数据类型] = 初始值
Scala提供了一种类型推断机制(Type Inference),它会根据初始值自动推断变量的类型。因此,若给出了初始值,数据类型可以省略。
1.2 输入/输出
1.2.1 控制台输入输出语句
- 从控制台读取数据,可以使用以read为前缀的方法,包括:readInt、readDouble、readByte、readShort、readFloat、readLong、readChar、readBoolean、readLine,readLine可以带参数,参数为想显示输出的提示。所有这些函数都属于对象scala.io.StdIn的方法,使用前必须导入。
import io.StdIn._ var i = readInt() var f = readFloat //可以不加括号 var str = readLine("Please input your name") //会在控制台输出"Please input your name",并读取用户输入的一整行
- 为了向控制台输出信息,常用的两个函数是print()和println(),同时也可以使用C语言风格的printf函数。另外Scala提供了字符串插值机制,以方便在字符串字面量中直接嵌入变量的值:只需要在字符串字面量前加一个"s"或"f"字符,然后,在字符串中既可以用"$"插入变量的值,s插值字符串不支持格式化,f插值字符串支持在$变量后再跟格式化参数,比如
val i = 10 val f = 3.5452 val s = "hello" println(s"$s:i=$i,f=$f") //s插值字符串,输出hello:i=10,f=3.5452 println(f"$s:i=$i%-4d,f=$f%.1f") //f插值字符串,输出hello:i=10 ,f=3.5
1.2.2 读写文件
- Scala使用类java.io.PrintWriter实现文本文件的创建与写入。实例如下:
import java.io.PrintWriter object test { def main(args: Array[String]): Unit = { val i = 9 val outputFile = new PrintWriter("test.txt") outputFile.println("hello,world") //也可以使用print,printf //若需要实现数值类型的格式化写入,可以使用String类的format方法,或者用f插值字符串,例如下 outputFile.print("%3d --> %d\n".format(i,i*i)) //向文件写入“ 9 --> 81” outputFile.println(f"$i%3d --> ${i*i}%d")//与上句等效 outputFile.close() } }
- Scala使用类Scala.io.Source实现对文件的读取,最常用的方法是getLines方法,它会返回一个含所有行的迭代器。
import scala.io.Source object test { def main(args: Array[String]): Unit = { val inputFile = Source.fromFile("test.txt") for (line <- inputFile.getLines()) println(line) inputFile.close() } } /*输出结果如下 hello,world 9 --> 81 9 --> 81 */
1.3 控制结构
- 1.if条件表达式
与java类似,但是不同的是scala中的if条件表达式会返回一个值,类似于三目表达式。例子如下val a = if (6>0) 1 else -1 println(a) //输出1
- 2.while循环
Scala的while循环于java完全一样,也同样有do-while。 - 3.for循环表达式
scala中的for循环表达式与java中的foreach类似,具体语法结构如下:for (变量 <- 表达式 if 条件表达式) 语句块
【例1】 val arr = Array(1,3,4,6,7,12,31) //或者for(i <- 1 to arr.length-1) for(i <- 1 until arr.length) print(arr(i)+" ") //输出3 4 6 7 12 31 【例2】 for (i <- 1 to 3 if i%2 == 0) print(i+" ") //输出2 【例3】 for (i <- Array(3,5,6) print(i+" ") //输出3 5 6
- 4.异常处理结构
和java一样,Scala也使用try-catch结构来捕获异常,例如:import java.io.FileReader import java.io.FileNotFoundException import java.io.IOException try{ val f = new FileReader("input.txt") //文件操作 //...... }catch{ case ex: FileNotFoundException => ...//文件不存在时的操作 case ex: IOException => ... //发送I/O错误时的操作 }finally{ file.close()//确保关闭文件 }
- 5.循环控制(break和continue)
Scala中没有break和continue关键词,但是提供了一个Breaks类来实现类似功能,该类位于包scala.util.control中。具体用法如下:import util.control.Breaks._//导入Breaks类的所有方法 val array = Array(1,3,10,5,4) //break用法 breakable{ for(i <- array){ if(i>5) break //跳出breakable,终止for循环,相当于java中的break println(i) } }//输出1,3 //continue用法 for(i <- array){ breakable{ if(i>5) break //跳出breakable,终止当此循环,相当于java的continue println(i) } }//输出1,3,5,4
1.4 数据结构
-
1.数组
数组(Array)是一种可变的、可索引的、元素具有相同类型的数据集合。数组的具体用法如下所示://1.数组的声明初始化 val intValueArr = new Array[Int](3) //显示给出类型参数Int定义一个长度为3的整型数组 val myStrArr = Array("BigData","Hadoop","Spark") //使用Scala中伴生对象的apply方法来生成一个对象,自动推导数据类型 //2.访问数组的方法和java不同,使用圆括号加从零开始的下标访问数组数据 val array = new Array[Int](3) array(0) = 2 println(array(0))//输出2 //3.多维数组定义方法 val myMatrix = Array.ofDim[Int](3,4) val myCube = Array.ofDim[String](3,2,4) //其数据类型其实是Array[Array[Array[String]]] //多维数组访问方法同一维数组,比如 println(myMatrix(0)(2)) //输出0
-
2.元组
Scala的元组是对多个不同类型对象的一种简单封装。Scala提供了TupleN类(N的范围为1~22),用于创建一个包含N个元素的元组。构造一个元组的语法如下val tuple = ("BigData",2015,45.0)//初始化方法 val (t1,t2,t3) = tuple //提取数据方法 //可以使用tuple._+从1开始的索引进行访问元组数据 println(t1 +" == "+ tuple._1)//输出BigData == BigData println(t2 +" == "+ tuple._2)//输出2015 == 2015 println(t3 +" == "+ tuple._3)//输出45.0 == 45.0
-
3.容器
下图为scala.collection包中容器的宏观层级关系(省略了很多细粒度的特质)。所有容器的根为Traverable特质,表示可便利的,它为所有的容器类定义了抽象的foreach方法。
-
4.序列
序列(Sequence)是指元素可以按照特定的顺序访问的容器。特质Seq具有两个子特质LinearSeq和IndexedSeq。LinearSeq序列具有高效的head和tail操作,而IndexedSeq序列具有高效的随机存储操作。实现了LinearSeq的常用序列有列表(List) 和 队列(Queue)。实现了IndexedSeq的常用序列有可变数组(ArrayBuffer) 和 向量(Vector)。- 列表
列表(List)是一种共享相同类型的不可变的对象序列。
列表的声明方法如下
//字符串列表 val site = List("Runoob","Google","Baidu") val site = "Runoob" :: ("Google" :: ("Baidu" :: Nil))//与上行代码效果相同 //整型列表 val nums = List(1,2,3,4) val nums = 1 :: (2 :: (3 :: (4 :: Nil)))//与上行代码效果相同 //空列表 val empty = List() val empty = Nil//与上行代码效果相同 //二维列表 val dim = List( List(1,0,0), List(0,1,0), List(0,0,1) ) //下面代码效果同上面 val dim = (1 :: (0 :: (0 :: Nil))) :: (0 :: (1 :: (0 :: Nil))) :: (0 :: (0 :: (1 :: Nil))) :: Nil
列表的基本操作包括:
head:返回列表第一个元素
tail:返回一个列表,包含除了第一元素外的其他元素
isEmpty:在列表为空时返回trueval site = "Runoob"::"Google"::"Baidu"::Nil val nums = Nil println("第一个网站是:"+site.head) println("site.tail为:"+site.tail) println("查看列表site是否为空:"+site.isEmpty) println("查看nums是否为空:"+nums.isEmpty) /* 输出结果为 第一个网站是:Runoob site.tail为:List(Google, Baidu) 查看列表site是否为空:false 查看nums是否为空:true */
更多列表的方法可以参考菜鸟教程。
- Range
Range类是一种特殊的、带索引的不可变数字等差序列,其包含的值为从给定按一定步长增长(减小)到指定终点的所有数值。
for(i<-Range(1,5,2)) println(i) //输出1,3 for(i<-1 to 5) println(i) //输出1,2,3,4,5 for(i<-1 until 5) println(i) //输出1,2,3,4
- 列表
-
5.集合
Scala的集合是不重复元素的容器。var myset = Set("Hadoop","Spark") println(myset) //输出Set(Hadoop, Spark) myset += "flink" println(myset) //输出Set(Hadoop, Spark, flink)
-
6.映射
映射(Map)是一系列键值对的容器。Scala提供了可变映射和不可变映射。默认情况下,Scala使用的是不可变映射。比如val university = Map("XMU"->"Xiamen University","THU"->"Tsinghua University","PKU"->"Peking University") println(university("XMU")) //输出Ximen University
如果需要使用可变映射,需要导入scala.collection.mutable.Map
import scala.collection.mutable.Map val university = Map("XMU"->"Xiamen University","THU"->"Tsinghua University","PKU"->"Peking University","JMU"->"NULL") university("JMU") = "Jimei University" //修改映射 university("TJU") = "Tianjin University" //添加映射 university += ("FZU" -> "Fuzhou University") //添加映射 println(university("JMU")) //输出Jimei University
-
7.迭代器
迭代器(Iterator)是一种提供了按顺序访问容器元素的数据结构。迭代器只能按从前往后的顺序一次访问其元素。迭代器提供了两个基本操作next 和 hasNext。next返回迭代器的下一个元素,并将该元素从迭代器中抛弃。hasNext用于检测是否还有下一个元素。用法如下val iter = Iterator("Hadoop","Spark","Scala") while (iter.hasNext){ println(iter.next()) }
2.面向对象编程基础
2.1 类
- 1.类的定义
scala类用关键字class声明。定义形式如下:
定义完类以后,就可以通过new关键字进行实例化,并通过实例对成员进行访问,如下所示:class ClassName{ //这里定义类的字段和方法 } //例如下面是一个完整的类定义 class Counter{ //字段 var value = 0 //方法 def increment(stp:Int):Unit = {value += step} def current():Int = {value}//对于scala而言,方法的最后一个表达式值就是方法的返回值 }
val myCounter = new Counter myCounter.value = 5 //访问字段 myCounter.increment(3) //调用方法 println(myCounter.current) //调用无参数方法时,可以省略方法名后的括号
- 2.类成员的可见性
在Scala中,所有成员的默认可见性为公有,且不需要用public关键字限定,任何作用域内都能直接访问公有成员。Scala同时还提供了类似Java的可见性选项,如private和protected。其中private成员只对本类型和嵌套类型可见;protected成员对本类型和其继承类型都可见。
对于私有字段的访问,Scala采用类似Java的getter和setter方法,定义了两个函数value和value_=。具体用法如下//定义 class Counter{ private var privateValue = 0 def value = privateValue def value_=(newValue:Int){ if(newValue>0) privateValue = newValue } def increment(step:Int):Unit = {value += step} def current():Int = {value} } //调用 val myCounter = new Counter myCounter.value_=(3) //为privateValue设置新的值 //scala看到value和value_= 成对出现的时候 允许用户去掉_和括号,如下所示 myCounter.value = 3 //等同于myCounter.value_=(3) println(myCounter.value) //访问privateValue的当前值
- 3.方法的定义方式
- 在scala中,方法参数前不能加val或var关键字限定,所有方法参数都是不可变类型,相等于隐式使用了val关键字限定。
- 对于无参数的方法,定义时可以省略括号,若省略了括号,在调用时也不能带括号。若无参数的方法在定义时带有括号,在调用时可以带括号,也可以不带括号。
- 在调用方法时,方法名后面的()可以用{}来代替。
- 如果方法只有一个参数,可以省略点号,而采用中缀操作符调用方法,形式为"调用者 方法名 参数"。
val c = new Counter c increment 5 //中缀调用法 println(c.current) //等同于c.current()
- 当方法的返回结果类型可以从最后的表达式推断而出的时候,方法定义可以省略结果类型。
- 如果方法体只有一条语句,可以省略方法体两边的大括号。
- 如果方法的返回类型为Unit,可以同时省略返回结果类型和等号,但是不能省略大括号。
//比如重新定义Counter class Counter{ var value = 0 def increment(step:Int) = value += step //省略大括号 def increment(step:Int) {value += step} //同上条一句相同,省略返回结果类型和等号 def current() = value //省略结果类型和大括号 }
- scala中设置方法参数默认值,和调用方法如下
class position(var x:Double=0,var y:Double=0){ def move(deltaX:Double = 0,deltaY:Double = 0): Unit ={ x+=deltaX y+=deltaY println("new pos("+x+","+y+")") } } def main(args: Array[String]): Unit = { val p = new position(); p.move(deltaY = 5)//输出new pos(0.0,5.0) p.move(deltaX = 5)//输出new pos(5.0,5.0) p.move(5,5)//输出new pos(10.0,10.0) }
- 4.构造器
scala的构造器分为主构造器和辅助构造器。形式如下class ClassName(形参列表){ //主构造器 //类体 def this(形参列表){//辅助构造器} def this(形参列表){//辅助构造器可以有多个...} }
2.2 对象
2.2.1 单例对象
Scala采用单例对象(Singleton Object)来实现与Java静态成员同样的功能。单例对象的定义与类的定义类似,只是用object关键字替换了class关键字。例如
object Person{
private var lastId = 0 //一个人的身份编号
def newPersonId() = {
lastId += 1
lastId
}
}
上面单例对象调用效果如下:
单例对象在第一次被访问的时候初始化。单例对象包括两种,即伴生对象(Companion Object) 和 孤立对象(Standalone Object)。当一个单例对象和它的同名类一起出现时,这时的单例对象被称为这个同名类的 “伴生对象” ,相应的类被称为这个单例对象的 “伴生类”,二者可以互相访问私有的field。没有同名类的单例对象,被称为孤立对象。
Scala程序的入口main方法就是定义在一个孤立对象里。单例对象和类之间的另一个差别是,单例对象的定义不能带有参数列表。(因为不能使用new关键字实例化一个单例对象,因此没机会传递参数给单例对象)
伴生类和伴生对象关系如下代码所示:
//伴生类
class Person(val name:String){
private val id = Person.newPersonId() //调用伴生对象中的方法
def info(): Unit ={
printf("The id of %s is %d.\n",name,id)
}
}
//伴生对象
object Person {
private var lastId = 0 //一个人的身份编号
def newPersonId() = {
lastId+=1
lastId
}
def main(args: Array[String]): Unit = {
val person1 = new Person("Lilei")
val person2 = new Person("Hanmei")
person1.info() //输出The id of Lilei is 1.
person2.info() //输出The id of Hanmei is 2.
}
}
2.2.2 apply方法
val myStrArr = Array("BigData","Hadoop","Spark")
对于以上代码,并没有使用new关键字来创建Array对象。采用这种语法格式时,Scala会自动调用Array类的伴生对象Array中的一个称为apply的方法,来创建一个Array对象myStrArr。
在Scala中,apply方法遵循如下的约定被调用:用括号传递给类实例或对象名一个或多个参数时,Scala会在相应的类或对象中查找方法名为apply且参数列表与传入参数一致的方法,并用传入的参数来调用该apply方法。效果如下所示:
class test{ //伴生类
def apply(param:String): Unit ={
println("class apply method called:"+param)
}
}
object test { //伴生对象
def apply(param:String):Unit ={
println("object apply method called:"+param)
}
def main(args: Array[String]): Unit = {
val myObject1 = new test
//自动调用类中定义的apply方法,等同于下句
myObject1("test1") //输出class apply method called:test1
//手动调用apply方法
myObject1.apply("test2") //输出class apply method called:test2
//单例对象的apply方法
val myObject2 = test("test3") //输出object apply method called:test3
}
}
对apply方法而言,更通常的用法时将其定义在类的伴生对象中,即将所有类的构造方法以apply方法的形式定义在伴生对象中。
2.2.3 unapply方法
unapply()方法用于对对象进行解构操作,与apply方法类似,该方法也会被自动调用。unapply方法包含一个类型为伴生类的参数,返回的结果是Option类型,对应的类型参数是N元组,N是伴生类中主构造器参数的个数。如下所示
class Car(var brand:String,val price:Int){
def info(): Unit ={
println("Car brand is "+brand+" and price is "+price)
}
}
object Car {
def apply(brand: String, price: Int)={
println("Debug:calling apply ……")
new Car(brand,price)
}
def unapply(arg: Car): Option[(String, Int)] = {
println("Debug:calling unapply ……")
Some((arg.brand,arg.price))
}
def main(args: Array[String]): Unit = {
var Car(carbrand,carprice) = Car("BMW",800000)//等号右侧调用apply方法,左侧调用unapply方法
println("brand:"+carbrand+" and carprice:"+carprice)
/*输出结果为:
Debug:calling apply ……
Debug:calling unapply ……
brand:BMW and carprice:800000
*/
}
}
2.3 继承
2.3.1 抽象类
与java不同的是,Scala里的抽象方法不需要加abstract修饰符。scala抽象类例子如下
abstract class Car(val name:String){
val carBrand:String //字段没有初始化值,就是一个抽象字段
def info() //抽象方法
def greeting() {
println("welcome to my car")
}
}
2.3.2 类的继承
- 只支持单一继承,一个子类只能有一个父类
- 重载父类抽象成员,override关键字是可选的(建议省略)。重载父类非抽象成员时,override关键字是必选的。
- 只能重载val字段,因为var本身就可变。
- 对于父类主构造器中用var或val修饰的参数,相当于类的一个字段。如果子类主构造器要使用相同的参数,必须加override关键字。
- 子类的主构造器必须调用父类的主构造器或者辅助构造器,所采用的方法是在extend关键字后的父类名称后跟上相应的参数列表。
实例如下
//父类
abstract class Car(val name:String){
val carBrand:String //一个抽象字段
var age:Int = 0
def info() //抽象方法
def greeting(){
println("welcome to my car")
}
def this(name:String,age:Int){
this(name)
this.age = age
}
}
//派生类,其著构造函数调用了父类的主构造函数
//由于name是父类主构造器的参数,因此也必须使用override
class BMWCar(override val name:String) extends Car(name){
override val carBrand = "BMW" //重载父类抽象字段,override可选
def info() { //重载父类抽象方法,override可选
printf("This is a %s car,It has been used for %d year.\n",carBrand,age)
}
override def greeting() {//重载父类非抽象方法,override必选
println("Welcome to my BMW car!")
}
}
//派生类,其主构造函数调用了父类的辅助构造函数
class BYDCar(name:String,age:Int) extends Car(name,age){
val carBrand = "BYD" //重载父类抽象字段,override可选
override def info() {//重载父类抽象方法,override可选
printf("This is a %s car,It has been used for %d year.\n",carBrand,age)
}
}
object MyCar{
def main(args:Array[String]){
val car1 = new BMWCar("Bob's Car")
val car2 = new BYDCar("Tom's Car",3)
show(car1)
show(car2)
/*
输出如下
Welcome to my BMW car!
This is a BMW car,It has been used for 0 year.
welcome to my car
This is a BYD car,It has been used for 3 year.
*/
}
def show(thecar:Car) = {
thecar.greeting;
thecar.info()
}
}
2.3.3 Scala的类层级结构
在Scala类层级结构的最底层,有两个特殊类型:Null和Nothing。其中,Null是所有引用类型的子类,其唯一的实例为null,表示一个“空”对象,可以赋值给任何引用类型的变量,但不能赋值给值类型的变量。Nothing是所有其他类型的子类,包括Null。Nothing没有实例,主要用于异常处理函数的返回类型。
2.3.4 Option类
除非明确需要与Java库进行交互,否则,Scala建议尽量避免使用这种可能带来bug的null,而改用Option类来统一表示对象有值和无值的情形。类Option是一个抽象类,有一个具体的子类Some和一个对象None,其中,Some表示有值的情形,后者表示没有值。
2.4 参数化类型
所谓参数化类型,是指在类的定义中包含一个或几个未确定的类型参数,其具体的类型将在实例化类时确定。Scala使用方括号([…])来定义参数化类型。如下所示
import scala.collection.mutable.Stack
class Box[T]{//未确定类型参数
val elems:Stack[T] = Stack()
def remove:Option[T]={ //返回的对象采用了Option类型包装
if (elems.isEmpty) None else Some(elems.pop)
}
def append(a1:T) {elems.push(a1)}
}
object test { //伴生对象
def main(args: Array[String]): Unit = {
case class Book(name:String) //定义了一个Book类
val a = new Box[Book] //实例化一个元素为Book类型的Box实例并赋值给a
println(a) //输出Box@5ba23b66
a.append(Book("Hadoop"))
a.append(Book("Spark"))
println(a.remove)//输出Some(Book(Spark))
}
}
2.5 特质
Java8前的接口不能为接口方法提供默认实现,Scala从设计之初就对Java接口的概念进行了改进,使用 “特质(Trait)” 来实现代码的多重复用,它不仅实现了接口的功能,还具备了很多其他的特性。Scala的特质是代码重用的基本单元,可以同时拥有抽象方法和具体方法。Scala中,一个类只能继承自一个超类,缺可以混入(Mixin) 多个特质,从而宠用特质中的方法和字段,