Scala 随笔
转自:http://dblab.xmu.edu.cn/blog/spark/
一、声名值和变量
1. val变量
1.1 隐式声明变量
scala> val myStr = "Hello World!"
myStr: String = Hello World!
1.2 显式声明变量的类型
scala> val myStr2 : String = "Hello World!"
myStr2: String = Hello World!
1.3 使用java.lang.String来声明
scala> val myStr3 : java.lang.String = "Hello World!"
myStr3: String = Hello World!
2. var变量
2.1 声明变量
scala> var myPrice : Double = 9.9
myPrice: Double = 9.9
再次对myPrice进行赋值
scala> myPrice = 10.6
myPrice: Double = 10.6
二、基本数据类型和操作
1. 基本数据类型
Scala的数据类型包括:Byte、Char、Short、Int、Long、Float、Double和Boolean。和Java不同的是,在Scala中,这些类型都是“类”,并且都是包scala的成员,比如,Int的全名是scala.Int。对于字符串,Scala用java.lang.String类来表示字符串。
val i = 123 //123就是整数字面量
val i = 3.14 //3.14就是浮点数字面量
val i = true //true就是布尔型字面量
val i = 'A' //'A'就是字符字面量
val i = "Hello" //"Hello"就是字符串字面量
2. 操作符
a 方法 b
或a.方法(b)
示例:
scala> val sum1 = 5 + 3 //实际上调用了 (5).+(3)
sum1: Int = 8
scala> val sum2 = (5).+(3) //可以发现,写成方法调用的形式,和上面得到相同的结果
sum2: Int = 8
和Java不同,在Scala中并没有提供++和–操作符
scala> var i = 5;
i: Int = 5
scala> i += 1 //将i递增
scala> println(i)
6
三、Range
在执行for循环时,我们经常会用到数值序列,比如,i的值从1循环到5,这时就可以采用Range来实现。
1. to
(1)创建一个从1到5的数值序列,包含区间终点5,步长为1
scala> 1 to 5
res0: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5)
// 或
scala> 1.to(5)
res0: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5)
(2)创建一个从1到10的数值序列,包含区间终点10,步长为2
scala> 1 to 10 by 2
res2: scala.collection.immutable.Range = Range(1, 3, 5, 7, 9)
2. until
(1)创建一个从1到5的数值序列,不包含区间终点5,步长为1
scala> 1 until 5
res1: scala.collection.immutable.Range = Range(1, 2, 3, 4)
(2)创建一个从1到5的数值序列,不包含区间终点5,步长为2
scala> 1 until 5 to 2
res1: scala.collection.immutable.Range = Range(1, 3)
四、打印语句
1. print
print("My name is:")
print("Ziyu")
2. printf
val i = 5;
val j = 8;
printf("My name is %s. I hava %d apples and %d eggs.\n","Ziyu",i,j)
五、读写文件
1.写入文本文件
scala> import java.io.PrintWriter
import java.io.PrintWriter //这行是Scala解释器执行上面语句后返回的结果
scala> val out = new PrintWriter("output.txt")
out: java.io.PrintWriter = java.io.PrintWriter@25641d39 //这行是Scala解释器执行上面语句后返回的结果
scala> for (i <- 1 to 5) out.println(i)
scala> out.close()
2. 读取文本文件中的行
scala> import scala.io.Source
import scala.io.Source //这行是Scala解释器执行上面语句后返回的结果
scala> val inputFile = Source.fromFile("output.txt")
inputFile: scala.io.BufferedSource = non-empty iterator //这行是Scala解释器执行上面语句后返回的结果
scala> val lines = inputFile.getLines //返回的结果是一个迭代器
lines: Iterator[String] = non-empty iterator //这行是Scala解释器执行上面语句后返回的结果
scala> for (line <- lines) println(line)
六、条件控制
1.if
和Java一样,if语句可以采用各种嵌套的形式,比如:
val x = 3
if (x>0) {
println("This is a positive number")
} else if (x==0) {
println("This is a zero")
} else {
println("This is a negative number")
}
但是,有一点与Java不同的是,Scala中的if表达式的值可以赋值给变量,比如:
val x = 6
val a = if (x>0) 1 else -1
七、循环
1. while
var i = 9
while (i > 0) {
i -= 1
printf("i is %d\n",i)
}
2. do-while
var i = 0
do {
i += 1
println(i)
}while (i<5)
3. for
for (i <- 1 to 5) println(i)
设置步长为2,如下所示:
for (i <- 1 to 5 by 2) println(i)
比如,我们只希望输出1到5之中的所有偶数,就需要使用到称为“守卫(guard)”的表达式,可以采用以下语句:
for (i <- 1 to 5 if i%2==0) println(i)
Scala也支持“多个生成器”的情形,可以用分号把它们隔开,比如:
for (i <- 1 to 5; j <- 1 to 3) println(i*j)
也可以给每个生成器都添加一个“守卫”,如下:
for (i <- 1 to 5 if i%2==0; j <- 1 to 3 if j!=i) println(i*j)
八、数据结构
1. 数组(Array)
1.1 声明定长数组
定长数组,就是长度不变的数组,在Scala中使用Array进行声明
val intValueArr = new Array[Int](3) //声明一个长度为3的整型数组,每个数组元素初始化为0
intValueArr(0) = 12 //给第1个数组元素赋值为12
intValueArr(1) = 45 //给第2个数组元素赋值为45
intValueArr(2) = 33 //给第3个数组元素赋值为33
实际上,Scala提供了更加简洁的数组声明和初始化方法,如下:
val intValueArr = Array(12,45,33)
val myStrArr = Array("BigData","Hadoop","Spark")
2. 列表(List)
2.1 声明list
val intList = List(1,2,3)
2.2 ::
由于列表的头部是一个元素,所以,我们可以使用::操作,在列表的头部增加新的元素,得到一个新的列表.
val intList = List(1,2,3)
val intListOther = 0::intList
也可以:
val intList = 1::2::3::Nil
2.3 function
Scala还为列表提供了一些常用的方法,比如,如果要实现求和,可以直接调用sum方法,如下:
intList.sum
3.元组(tuple)
元组是不同类型的值的聚集。元组和列表不同,列表中各个元素必须是相同类型,而元组可以包含不同类型的元素。
3.1 声明一个名称为tuple的元组
scala> val tuple = ("BigData",2015,45.0)
tuple: (String, Int, Double) = (BigData,2015,45.0) //这行是Scala解释器返回的执行结果
scala> println(tuple._1)
BigData
scala> println(tuple._2)
2015
scala> println(tuple._3)
45.0
4. 集(Set)
4.1 不可变集
scala> var mySet = Set("Hadoop","Spark")
mySet: scala.collection.immutable.Set[String] = Set(Hadoop, Spark)
scala> mySet += "Scala" //向mySet中增加新的元素
scala> println(mySet.contains("Scala"))
true
4.2 可变集
scala> import scala.collection.mutable.Set
import scala.collection.mutable.Set
scala> val myMutableSet = Set("Database","BigData")
myMutableSet: scala.collection.mutable.Set[String] = Set(BigData, Database)
scala> myMutableSet += "Cloud Computing"
res0: myMutableSet.type = Set(BigData, Cloud Computing, Database)
scala> println(myMutableSet)
Set(BigData, Cloud Computing, Database)
5. 映射(Map)
5.1 不可变映射
val university = Map("XMU" -> "Xiamen University", "THU" -> "Tsinghua University","PKU"->"Peking University")
获取value
println(university("XMU"))
如果要检查映射中是否包含某个值,可以使用contains方法
val xmu = if (university.contains("XMU")) university("XMU") else 0
println(xmu)
5.2 可变映射
import scala.collection.mutable.Map
val university2 = Map("XMU" -> "Xiamen University", "THU" -> "Tsinghua University","PKU"->"Peking University")
university2("XMU") = "Ximan University" //更新已有元素的值
university2("FZU") = "Fuzhou University" //添加新元素
也可以使用+=操作来添加新的元素,如下:
university2 + = ("TJU"->"Tianjin University") //添加一个新元素
university2 + = ("SDU"->"Shandong University","WHU"->"Wuhan University") //同时添加两个新元素
5.3 循环遍历
for ((k,v) <- 映射) 语句块
下面给出一个实例:
for ((k,v) <- university) printf("Code is : %s and name is: %s\n",k,v)
或者
val map = Map("zhangsan" -> 18, "li" -> 20, "wangwu" -> 23)
for (i <- map) {
println(i._1 + " ---- " + i._2)
}
6. 迭代器(Iterator)
通常可以通过while循环或者for循环实现对迭代器的遍历。
while循环如下:
val iter = Iterator("Hadoop","Spark","Scala")
while (iter.hasNext) {
println(iter.next())
}
for循环如下:
val iter = Iterator("Hadoop","Spark","Scala")
for (elem <- iter) {
println(elem)
}
九、类
1. class类
最简单的类的定义形式是:
class Counter{
//这里定义类的字段和方法
}
然后,就可以使用new关键字来生成对象:
new Counter //或者new Counter()
2. 给类增加字段和方法
下面我们给这个类增加字段和方法:
class Counter {
private var value = 0
def increment(): Unit = { value += 1}
def current(): Int = {value}
}
3. 创建对象
下面我们新建对象,并调用其中的方法:
val myCounter = new Counter
myCounter.increment() //或者也可以不用圆括号,写成myCounter.increment
println(myCounter.current)
4. getter和setter方法
class Counter {
var value = 0 //注意这里没有private修饰符,从而让这个变量对外部可见
def increment(step: Int): Unit = { value += step}
def current(): Int = {value}
}
object MyCounter{
def main(args:Array[String]){
val myCounter = new Counter
println(myCounter.value) //不是用getXxx获取字段的值
myCounter.value = 3 //不是用setXxx设置字段的值
myCounter.increment(1) //这里设置步长为1,每次增加1
println(myCounter.current)
}
}
在Scala中,可以通过定义类似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}
}
object MyCounter{
def main(args:Array[String]){
val myCounter = new Counter
println(myCounter.value) //打印value的初始值
myCounter.value = 3 //为value设置新的值
println(myCounter.value) //打印value的新值
myCounter.increment(1) //这里设置步长为1,每次增加1
println(myCounter.current)
}
}
5. 辅助构造器
class Counter {
private var value = 0 //value用来存储计数器的起始值
private var name = "" //表示计数器的名称
private var mode = 1 //mode用来表示计数器类型(比如,1表示步数计数器,2表示时间计数器)
def this(name: String){ //第一个辅助构造器
this() //调用主构造器
this.name = name
}
def this (name: String, mode: Int){ //第二个辅助构造器
this(name) //调用前一个辅助构造器
this.mode = mode
}
def increment(step: Int): Unit = { value += step}
def current(): Int = {value}
def info(): Unit = {printf("Name:%s and mode is %d\n",name,mode)}
}
object MyCounter{
def main(args:Array[String]){
val myCounter1 = new Counter //主构造器
val myCounter2 = new Counter("Runner") //第一个辅助构造器,计数器的名称设置为Runner,用来计算跑步步数
val myCounter3 = new Counter("Timer",2) //第二个辅助构造器,计数器的名称设置为Timer,用来计算秒数
myCounter1.info //显示计数器信息
myCounter1.increment(1) //设置步长
printf("Current Value is: %d\n",myCounter1.current) //显示计数器当前值
myCounter2.info //显示计数器信息
myCounter2.increment(2) //设置步长
printf("Current Value is: %d\n",myCounter2.current) //显示计数器当前值
myCounter3.info //显示计数器信息
myCounter3.increment(3) //设置步长
printf("Current Value is: %d\n",myCounter3.current) //显示计数器当前值
}
}
编译执行上述代码后,得到如下结果:
Name: and mode is 1
Current Value is: 1
Name:Runner and mode is 1
Current Value is: 2
Name:Timer and mode is 2
Current Value is: 3
主构造器
Scala的每个类都有主构造器。但是,Scala的主构造器和Java有着明显的不同,Scala的主构造器是整个类体,需要在类名称后面罗列出构造器所需的所有参数,这些参数被编译成字段,字段的值就是创建对象时传入的参数的值。
对于上面给计数器设置name和mode的例子,刚才我们是使用辅助构造器来对name和mode的值进行设置,现在我们重新来一次,这次我们转而采用主构造器来设置name和mode的值。
class Counter(val name: String, val mode: Int) {
private var value = 0 //value用来存储计数器的起始值
def increment(step: Int): Unit = { value += step}
def current(): Int = {value}
def info(): Unit = {printf("Name:%s and mode is %d\n",name,mode)}
}
object MyCounter{
def main(args:Array[String]){
val myCounter = new Counter("Timer",2)
myCounter.info //显示计数器信息
myCounter.increment(1) //设置步长
printf("Current Value is: %d\n",myCounter.current) //显示计数器当前值
}
}
十、对象
1.单例对象
下面是单例对象的定义:
object Person {
private var lastId = 0 //一个人的身份编号
def newPersonId() = {
lastId +=1
lastId
}
}
2. 伴生对象
在Java中,我们经常需要用到同时包含实例方法和静态方法的类,在Scala中可以通过伴生对象来实现。当单例对象与某个类具有相同的名称时,它被称为这个类的“伴生对象”。类和它的伴生对象必须存在于同一个文件中,而且可以相互访问私有成员(字段和方法)。
class Person {
private val id = Person.newPersonId() //调用了伴生对象中的方法
private var name = ""
def this(name: String) {
this()
this.name = name
}
def info() { printf("The id of %s is %d.\n",name,id)}
}
object Person {
private var lastId = 0 //一个人的身份编号
private def newPersonId() = {
lastId +=1
lastId
}
def main(args: Array[String]){
val person1 = new Person("Ziyu")
val person2 = new Person("Minxing")
person1.info()
person2.info()
}
}
伴生对象中定义的newPersonId()实际上就实现了Java中静态(static)方法的功能
实际上,在编译上面的源代码文件以后,在Scala里面的class和object在Java层面都会被合二为一,class里面的成员成了实例成员,object成员成了static成员。并且伴生对象Person的定义的方法不能用private修饰符,必须去掉,因为如果不去掉,作为伴生对象的私有方法,在javap反编译后,在执行结果中是看不到这个方法的。
3. apply方法和update方法
object apply 是一种比较普遍用法。 主要用来解决复杂对象的初始化问题,同时也是单例.
class TestApplyClass {
def apply(param: String): String = {
println("apply method called, parameter is: " + param)
"Hello World!"
}
}
val myObject = new TestApplyClass
println(myObject("param1"))
运行后会得到以下结果:
apply method is called, parameter is:param1
Hello World!
下面我们测试一个伴生类和伴生对象中的apply方法实例。请在Linux系统的“/usr/local/scala/mycode/test.scala”文件中输入以下代码:
class TestApplyClassAndObject {
}
class ApplyTest{
def apply() = println("apply method in class is called!")
def greetingOfClass: Unit ={
println("Greeting method in class is called.")
}
}
object ApplyTest{
def apply() = {
println("apply method in object is called")
new ApplyTest()
}
}
object TestApplyClassAndObject{
def main (args: Array[String]) {
val a = ApplyTest() //这里会调用伴生对象中的apply方法
a.greetingOfClass
a() // 这里会调用伴生类中的apply方法
}
}
上述代码执行后得到以下结果:
apply method in object is called
Greeting method in class is called.
apply method in class is called!
我们通常将伴生对象作为工厂使用,这样就不需要使用关键字new来创建一个实例化对象了,具体实例如下:
class Car(name: String){
def info() {println("Car name is "+ name)}
}
object Car {
def apply(name: String) = new Car(name) //apply方法会调用伴生类Car的构造方法创建一个Car类的实例化对象
}
object MyTest{
def main (args: Array[String]) {
val mycar = Car("BMW") //这里会调用伴生对象中的apply方法,apply方法会创建一个Car类的实例化对象
mycar.info()
}
}
上述代码执行后得到以下结果:
Car name is BMW
十一、继承
Scala中的继承与Java有着显著的不同:
(1)重写一个非抽象方法必须使用override修饰符。
(2)只有主构造器可以调用超类的主构造器。
(3)在子类中重写超类的抽象方法时,不需要使用override关键字。
(4)可以重写超类中的字段。
1.抽象类
以汽车为例子,首先我们创建一个抽象类,让这个抽象类被其他类继承。
abstract class Car{ //是抽象类,不能直接被实例化
val carBrand: String //字段没有初始化值,就是一个抽象字段
def info() //抽象方法,不需要使用abstract关键字
def greeting() {println("Welcome to my car!")}
}
关于上面的定义,说明几点:
(1)定义一个抽象类,需要使用关键字abstract。
(2)定义一个抽象类的抽象方法,也不需要关键字abstract,只要把方法体空着,不写方法体就可以。
(3)抽象类中定义的字段,只要没有给出初始化值,就表示是一个抽象字段,但是,抽象字段必须要声明类型,比如:val carBrand: String,就把carBrand声明为字符串类型,这个时候,不能省略类型,否则编译会报错。
2.扩展类
抽象类不能直接被实例化,所以,下面我们定义几个扩展类,它们都是扩展了Car类,或者说继承自Car类。
class BMWCar extends Car {
override val carBrand = "BMW" //重写超类字段,需要使用override关键字,否则编译会报错
def info() {printf("This is a %s car. It is on sale", carBrand)} //重写超类的抽象方法时,不需要使用override关键字,不过,如果加上override编译也不错报错
override def greeting() {println("Welcome to my BMW car!")} //重写超类的非抽象方法,必须使用override关键字
}
class BYDCar extends Car {
override val carBrand = "BYD" //重写超类字段,需要使用override关键字,否则编译会报错
def info() {printf("This is a %s car. It is cheap.", carBrand)} //重写超类的抽象方法时,不需要使用override关键字,不过,如果加上override编译也不错报错
override def greeting() {println("Welcome to my BYD car!")} //重写超类的非抽象方法,必须使用override关键字
}
在test.scala文件中输入以下内容:
abstract class Car{
val carBrand: String
def info()
def greeting() {println("Welcome to my car!")}
}
class BMWCar extends Car {
override val carBrand = "BMW"
def info() {printf("This is a %s car. It is expensive.\n", carBrand)}
override def greeting() {println("Welcome to my BMW car!")}
}
class BYDCar extends Car {
override val carBrand = "BYD"
def info() {printf("This is a %s car. It is cheap.\n", carBrand)}
override def greeting() {println("Welcome to my BYD car!")}
}
object MyCar {
def main(args: Array[String]){
val myCar1 = new BMWCar()
val myCar2 = new BYDCar()
myCar1.greeting()
myCar1.info()
myCar2.greeting()
myCar2.info()
}
}
十二、特质(trait)
1. 特质的定义
特质的定义和类的定义非常相似,有区别的是,特质定义使用关键字trait。
trait CarId{
var id: Int
def currentId(): Int //定义了一个抽象方法
}
上面定义了一个特质,里面包含一个抽象字段id和抽象方法currentId。注意,抽象方法不需要使用abstract关键字,特质中没有方法体的方法,默认就是抽象方法。
2.把特质混入类中
特质定义好以后,就可以使用extends或with关键字把特质混入类中。
class BYDCarId extends CarId{ //使用extends关键字
override var id = 10000 //BYD汽车编号从10000开始
def currentId(): Int = {id += 1; id} //返回汽车编号
}
class BMWCarId extends CarId{ //使用extends关键字
override var id = 20000 //BMW汽车编号从20000开始
def currentId(): Int = {id += 1; id} //返回汽车编号
}
在test.scala文件中输入以下内容:
trait CarId{
var id: Int
def currentId(): Int //定义了一个抽象方法
}
class BYDCarId extends CarId{ //使用extends关键字
override var id = 10000 //BYD汽车编号从10000开始
def currentId(): Int = {id += 1; id} //返回汽车编号
}
class BMWCarId extends CarId{ //使用extends关键字
override var id = 20000 //BMW汽车编号从10000开始
def currentId(): Int = {id += 1; id} //返回汽车编号
}
object MyCar {
def main(args: Array[String]){
val myCarId1 = new BYDCarId()
val myCarId2 = new BMWCarId()
printf("My first CarId is %d.\n",myCarId1.currentId)
printf("My second CarId is %d.\n",myCarId2.currentId)
}
}
3.特质可以包含具体实现
上面的实例中,特质只包含了抽象字段和抽象方法,相当于实现了类似Java接口的功能。实际上,特质也可以包含具体实现,也就是说,特质中的字段和方法不一定要是抽象的。
trait CarGreeting{
def greeting(msg: String) {println(msg)}
}
4.把多个特质混入类中
在test.scala文件中输入以下内容:
trait CarId{
var id: Int
def currentId(): Int //定义了一个抽象方法
}
trait CarGreeting{
def greeting(msg: String) {println(msg)}
}
class BYDCarId extends CarId with CarGreeting{ //使用extends关键字混入第1个特质,后面可以反复使用with关键字混入更多特质
override var id = 10000 //BYD汽车编号从10000开始
def currentId(): Int = {id += 1; id} //返回汽车编号
}
class BMWCarId extends CarId with CarGreeting{ //使用extends关键字混入第1个特质,后面可以反复使用with关键字混入更多特质
override var id = 20000 //BMW汽车编号从10000开始
def currentId(): Int = {id += 1; id} //返回汽车编号
}
object MyCar {
def main(args: Array[String]){
val myCarId1 = new BYDCarId()
val myCarId2 = new BMWCarId()
myCarId1.greeting("Welcome my first car.")
printf("My first CarId is %d.\n",myCarId1.currentId)
myCarId2.greeting("Welcome my second car.")
printf("My second CarId is %d.\n",myCarId2.currentId)
}
}
上面命令执行后,会在屏幕输出以下结果:
Welcome my first car.
My first CarId is 10001.
Welcome my second car.
My second CarId is 20001.
///