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.

 ///

posted @ 2022-11-10 19:26  彬在俊  阅读(207)  评论(0编辑  收藏  举报