两天过完scala语法(第一天)

注:以下内容来自子雨大数据 https://dblab.xmu.edu.cn/blog/949/

第三章 scala 基础

1. 声明值和变量

val:是不可变的,在声明时就必须被初始化,而且初始化以后就不能再赋值;

var:是可变的,声明的时候需要进行初始化,初始化以后还可以再次对其赋值。(注意:var变量在声明的时候也需要初始化)

 

val myStr = "Hello World!"

注意的是,尽管我们在第1行代码的声明中,没有给出myStr是String类型,但是,Scala具有“类型推断”能力,可以自动推断出变量的类型。

当然,我们也可以显式声明变量的类型:

val myStr2 : String = "Hello World!"

需要说明的是,上面的String类型全称是java.lang.String,也就是说,Scala的字符串是由Java的String类来实现的,因此,我们也可以使用java.lang.String来声明,具体如下:

val myStr3 : java.lang.String = "Hello World!"

但是,为什么可以不用java.lang.String,而只需要使用String就可以声明变量呢?这是因为,在每个应用程序中,Scala都会自动添加一些引用,这样,就相当于在每个程序源文件的顶端都增加了一行下面的代码:

  1. import java.lang._ //java.lang包里面所有的东西

 

2. 基本数据类型和操作

Scala的数据类型包括:Byte、Char、Short、Int、Long、Float、Double和Boolean。和Java不同的是,在Scala中,这些类型都是“类”,并且都是包scala的成员,比如,Int的全名是scala.Int。对于字符串,Scala用java.lang.String类来表示字符串。


这里要明确什么是“字面量”?字面量包括整数字面量、浮点数字面量、布尔型字面量、字符字面量、字符串字面量、符号字面量、函数字面量和元组字面量。举例如下:

  1. val i = 123 //123就是整数字面量
  2. val i = 3.14 //3.14就是浮点数字面量
  3. val i = true //true就是布尔型字面量
  4. val i = 'A' //'A'就是字符字面量
  5. val i = "Hello" //"Hello"就是字符串字面量

Scala允许对“字面量”直接执行方法,比如:

  1. 5.toString() //产生字符串"5"
  2. "abc".intersect("bcd") //输出"bc"

上面的intersect()方法用来输出两个字符串中都存在的字符。

操作符

在Scala中,可以使用加(+)、减(-) 、乘(*) 、除(/) 、余数(%)等操作符,而且,这些操作符就是方法。例如,5 + 3和(5).+(3)是等价的,也就是说:

a 方法 b
a.方法(b)

上面这二者是等价的。前者是后者的简写形式,这里的+是方法名,是Int类中的一个方法。具体代码如下:

  1. scala> val sum1 = 5 + 3 //实际上调用了 (5).+(3)
  2. sum1: Int = 8
  3. scala> val sum2 = (5).+(3) //可以发现,写成方法调用的形式,和上面得到相同的结果
  4. sum2: Int = 8

需要注意的是,和Java不同,在Scala中并没有提供++和--操作符,当需要递增和递减时,可以采用如下方式表达:

  1. scala> var i = 5;
  2. i: Int = 5
  3. scala> i += 1 //将i递增
  4. scala> println(i)
  5. 6

此外,也可以使用关系和逻辑操作,比如,大于(>)、小于(<)、大于等于(>=)和小于等于(<=),会产生Boolean类型的结果。

 

3. Range

在执行for循环时,我们经常会用到数值序列,比如,i的值从1循环到5,这时就可以采用Range来实现。Range可以支持创建不同数据类型的数值序列,包括Int、Long、Float、Double、Char、BigInt和BigDecimal等。

在创建Range时,需要给出区间的起点和终点以及步长(默认步长为1)。下面通过几个实例来介绍:
(1)创建一个从1到5的数值序列,包含区间终点5,步长为1

  1. scala> 1 to 5
  2. res0: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5)

之前我们已经介绍过,在Scala中允许对“字面量”直接执行方法,所以,上面的代码,也可以用下面的代码来实现:

  1. scala> 1.to(5)
  2. res0: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5)

(2)创建一个从1到5的数值序列,不包含区间终点5,步长为1

  1. scala> 1 until 5
  2. res1: scala.collection.immutable.Range = Range(1, 2, 3, 4)

(3)创建一个从1到10的数值序列,包含区间终点10,步长为2

  1. scala> 1 to 10 by 2
  2. res2: scala.collection.immutable.Range = Range(1, 3, 5, 7, 9)

(4)创建一个Float类型的数值序列,从0.5f到5.9f,步长为0.8f

    1. scala> 0.5f to 5.9f by 0.8f
    2. res3: scala.collection.immutable.NumericRange[Float] = NumericRange(0.5, 1.3, 2.1, 2.8999999, 3.6999998, 4.5, 5.3

 

4. 打印语句

在Scala编程中,经常需要用到打印语句。

print("My name is:")
print("Ziyu")

上述代码执行后,会得到连在一起的一行结果,如下:

My name is Ziyu

如果要每次打印后追加一个换行符,实现换行的效果,就要采用println语句,如下:

println("My name is:")
println("Ziyu")

上述代码执行后,会得到两行结果,如下:

My name is
Ziyu

如果要打印整型变量的值,可以使用下面语句:

val i = 7
println(i)

此外,Scala还带有C语言风格的格式化字符串的printf函数:

val i = 5;
val j = 8;
printf("My name is %s. I hava %d apples and %d eggs.\n","Ziyu",i,j)

上面语句执行后会得到如下结果:

My name is Ziyu. I hava 5 apples and 8 eggs.

更多关于printf函数的使用方法,读者可以参考C语言书籍。

总结,scala可以使用 print, println, printf。

 

5. 读写文件

 

Scala需要使用java.io.PrintWriter实现把数据写入到文本文件。
假设当前正使用用户名hadoop登录Linux系统,打开Scala解释器进入命令提示符状态后,输入以下代码:

  1. scala> import java.io.PrintWriter
  2. import java.io.PrintWriter //这行是Scala解释器执行上面语句后返回的结果
  3. scala> val out = new PrintWriter("output.txt")
  4. out: java.io.PrintWriter = java.io.PrintWriter@25641d39 //这行是Scala解释器执行上面语句后返回的结果
  5. scala> for (i <- 1 to 5) out.println(i)
  6. scala> out.close()
scala

上面代码中,new PrintWriter("output.txt")中只给出了文件名,并没有给出文件路径,采用相对路径,这时,文件就会被保存到启动Scala REPL时的当前目录下。比如,如果我们是进入“/home/hadoop”用户目录,在这个目录下启动进入了Scala REPL交互式执行环境,则上面代码执行结束后,可以在hadoop用户的工作目录“/home/hadoop/”下找到新生成的这个output.txt文件。为了查看这个文件,我们可以在当前“终端”窗口的基础上,再新建一个终端窗口,你可以在当前终端窗口的左上角看到一个菜单,点击“终端”,选择“新建终端”,就可以打开第二个命令行终端窗口(用来查看文件)。
现在,让我们切换到第二个终端窗口,然后,输入下面命令进入到hadoop用户的工作目录,并显示该目录下的所有文件和文件夹信息:

  1. cd ~
  2. ls
Shell 命令

"~"就表示当前用户的工作目录,对于hadoop用户而言,就是“/home/hadoop/”目录。
上面命令执行后,就可以发现,“/home/hadoop/”目录下有个新生成的这个output.txt文件,下面使用cat命令查看该文件内容:

  1. cat output.txt
Shell 命令

需要注意的是,必须要执行out.close()语句,才会看到output.txt文件被生成,如果没有执行out.close()语句,我们就无法看到生成的output.txt文件。
如果我们想把文件保存到一个指定的目录下,就需要给出文件路径,代码如下:

  1. scala> import java.io.PrintWriter
  2. import java.io.PrintWriter //这行是Scala解释器执行上面语句后返回的结果
  3. scala> val out = new PrintWriter("/usr/local/scala/mycode/output.txt")
  4. out: java.io.PrintWriter = java.io.PrintWriter@25641d39 //这行是Scala解释器执行上面语句后返回的结果
  5. scala> for (i <- 1 to 5) out.println(i)
  6. scala> out.close()

上述过程执行结束后,就可以到“/usr/local/scala/mycode/”这个目录下找到output.txt文件。

读取文本文件中的行

可以使用Scala.io.Source的getLines方法实现对文件中所有行的读取。
仍然假设当前是用hadoop用户登录了Linux系统,并且使用scala命令启动了Scala解释器,现在,我们要把上面刚生成的、在hadoop用户工作目录下的output.txt文件读取出来,下面给出了完整的读取文件实例代码:

    1. scala> import scala.io.Source
    2. import scala.io.Source //这行是Scala解释器执行上面语句后返回的结果
    3. scala> val inputFile = Source.fromFile("output.txt")
    4. inputFile: scala.io.BufferedSource = non-empty iterator //这行是Scala解释器执行上面语句后返回的结果
    5. scala> val lines = inputFile.getLines //返回的结果是一个迭代器
    6. lines: Iterator[String] = non-empty iterator //这行是Scala解释器执行上面语句后返回的结果
    7. scala> for (line <- lines) println(line)
    8. 1
    9. 2
    10. 3
    11. 4
    12. 5

 

第四章 控制结构

1. if条件表达式

if语句是许多编程语言中都会用到的控制结构。在Scala中,执行if语句时,会首先检查if条件是否为真,如果为真,就执行对应的语句块,如果为假,就执行下一个条件分支。
请在Linux系统中进入到/usr/local/scala目录,并在之前已经创建好的mycode目录下新建test.scala,用于测试我们撰写的代码。

  1. cd /usr/local/scala
  2. vim ./mycode/test.scala
Shell 命令

请在test.scala文件中输入以下代码。

val x = 6
if (x>0) {println("This is a positive number")
} else {
    println("This is not a positive number")
}

保存test.scala文件,然后,使用下面命令执行代码(再次注意,当前工作目录是/usr/local/scala):

  1. scala ./mycode/test.scala
Shell 命令

和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

上述代码执行结束后,a的值为1。

 

2. while循环

Scala中也有和Java类似的while循环语句。

var i = 9
while (i > 0) {
    i -= 1
    printf("i is %d\n",i)
}

当然也会有do-while语句,如下:

var i = 0
do {
    i += 1
    println(i)
}while (i<5)

 

3. for循环

Scala中的for循环语句格式如下:

for (变量<-表达式) 语句块

其中,“变量<-表达式”被称为“生成器(generator)”。

下面给出一个实例:

for (i <- 1 to 5) println(i)

在上面语句中,i不需要提前进行变量声明,可以在for语句括号中的表达式中直接使用。语句中,“<-”表示,之前的i要遍历后面1到5的所有值。
语句执行结束后,会打印出下面结果:

1
2
3
4
5

当然,在前面的Range那一节,我们介绍了Range的使用方法,因此,这里可以改变步长,比如设置步长为2,如下所示:

for (i <- 1 to 5 by 2) println(i)

这样会得到下面结果:

1
3
5

但是,有时候,我们可能不希望打印出所有的结果,我们可能希望过滤出一些满足制定条件的结果,这个时候,就需要使用到称为“守卫(guard)”的表达式。比如,我们只希望输出1到5之中的所有偶数,可以采用以下语句:

for (i <- 1 to 5 if i%2==0) println(i)

这样,就只会得到下面的偶数结果:

2
4

Scala也支持“多个生成器”的情形,可以用分号把它们隔开,比如:

for (i <- 1 to 5; j <- 1 to 3) println(i*j)       注意:这里优先遍历的是变量 j 

运行上述代码后得到如下执行结果:

1
2
3
2
4
6
3
6
9
4
8
12
5
10
15

也可以给每个生成器都添加一个“守卫”,如下:

for (i <- 1 to 5 if i%2==0; j <- 1 to 3 if j!=i) println(i*j)

运行上述代码后得到如下执行结果:

2
6
4
8
12

for推导式

有时候,我们需要对上述过滤后的结果进行进一步的处理,这时,就可以采用yield关键字,对过滤后的结果构建一个集合。比如,我们可以采用以下语句:

  1. scala> for (i <- 1 to 5 if i%2==0) yield i
  2. res3: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4)
scala

上面的语句采用了黑色背景,表示这是在Scala解释器中的运行情况。
上面这种带有yield关键字的for循环,被称为“for推导式”。这个概念源自函数式编程,也就是说,通过for循环遍历一个或多个集合,对集合中的元素进行“推导”,从而计算得到新的集合,用于后续的其他处理。

 

第五章 数据结构

第1节 数组

数组是编程中经常用到的数据结构,一般包括定长数组和变长数组。本教程旨在快速掌握最基础和常用的知识,因此,只介绍定长数组。

定长数组,就是长度不变的数组,在Scala中使用Array进行声明,如下:

val intValueArr = new Array[Int](3)  //声明一个长度为3的整型数组,每个数组元素初始化为0
注意:这里 val 表示常量,这个常量指的是数组类型和大小不变,而不是数组元素不变 intValueArr(0) = 12 //给第1个数组元素赋值为12 intValueArr(1) = 45 //给第2个数组元素赋值为45 intValueArr(2) = 33 //给第3个数组元素赋值为33

需要注意的是,在Scala中,对数组元素的应用,是使用圆括号,而不是方括号,也就是使用intValueArr(0),而不是intValueArr[0],这个和Java是不同的。
下面我们再声明一个字符串数组,如下:

 val myStrArr = new Array[String](3) //声明一个长度为3的字符串数组,每个数组元素初始化为null
 myStrArr(0) = "BigData"
 myStrArr(1) = "Hadoop"
 myStrArr(2) = "Spark"
 for (i <- 0 to 2) println(myStrArr(i)) 

实际上,Scala提供了更加简洁的数组声明和初始化方法,如下:

val intValueArr = Array(12,45,33)
val myStrArr = Array("BigData","Hadoop","Spark")

从上面代码可以看出,都不需要给出数组类型,Scala会自动根据提供的初始化数据来推断出数组的类型。


第2节 列表

在后面的Spark学习中,我们会用到列表。

下面我们首先声明一个列表:

val intList = List(1,2,3)

列表有头部和尾部的概念,可以使用intList.head来获取上面定义的列表的头部,值是1,使用intList.tail来获取上面定义的列表的尾部,值是List(2,3),可以看出,头部是一个元素,而尾部则仍然是一个列表。

由于列表的头部是一个元素,所以,我们可以使用::操作,在列表的头部增加新的元素,得到一个新的列表,如下:

val intList = List(1,2,3)
val intListOther = 0::intList

注意,上面操作执行后,intList不会发生变化,依然是List(1,2,3),intListOther是一个新的列表List(0,1,2,3)

::操作符是右结合的,因此,如果要构建一个列表List(1,2,3),实际上也可以采用下面的方式:

val intList = 1::2::3::Nil

上面代码中,Nil表示空列表。
我们也可以使用:::操作符对不同的列表进行连接得到新的列表,比如:

val intList1 = List(1,2)
val intList2 = List(3,4)
val intList3 = intList1:::intList2   注意:这里使用了三个冒号

注意,执行上面操作后,intList1和intList2依然存在,intList3是一个全新的列表。

实际上,Scala还为列表提供了一些常用的方法,比如,如果要实现求和,可以直接调用sum方法,如下:

intList.sum


第3节 元组

元组是不同类型的值的聚集。元组和列表不同,列表中各个元素必须是相同类型,而元组可以包含不同类型的元素。

下面我们声明一个名称为tuple的元组(为了看到声明后的效果,我们这次在Scala解释器中输入代码并执行):

  1. scala> val tuple = ("BigData",2015,45.0)
  2. tuple: (String, Int, Double) = (BigData,2015,45.0) //这行是Scala解释器返回的执行结果
  3. scala> println(tuple._1)
  4. BigData
  5. scala> println(tuple._2)
  6. 2015
  7. scala> println(tuple._3)
  8. 45.0
scala

从上述代码在Scala解释器中的执行效果可以看出,我们声明一个元组是很简单的,只需要用圆括号把多个元组的元素包围起来就可以了。
当需要访问元组中的某个元素的值时,可以通过类似tuple._1、tuple._2、tuple._3这种方式就可以实现。


第4节 集

集(set)是不重复元素的集合。列表中的元素是按照插入的先后顺序来组织的,但是,"集"中的元素并不会记录元素的插入顺序,而是以“哈希”方法对元素的值进行组织,所以,它允许你快速地找到某个元素。

集包括可变集和不可变集,缺省情况下创建的是不可变集,通常我们使用不可变集。

补充:

1.不可变集合:scala不可变集合,就是这个集合本身不能动态变化,(类似java的数组,式不可以动态增长的)
2.可变集合:可变集合,就是这个集合本身可以动态变化的。(比如ArrayList,是可以动态增长)


下面我们用默认方式创建一个不可变集,如下(在Scala解释器中执行):

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

上面声明时,如果使用val,mySet += "Scala"执行时会报错,所以需要声明为var。

如果要声明一个可变集,则需要引入scala.collection.mutable.Set包,具体如下(在Scala解释器中执行):

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)

上面代码中,我们声明myMutableSet为val变量(不是var变量),由于是可变集,因此,可以正确执行myMutableSet += "Cloud Computing",不会报错。

注意:虽然可变集和不可变集都有添加或删除元素的操作,但是,二者有很大的区别。对不可变集进行操作,会产生一个新的集,原来的集并不会发生变化。 而对可变集进行操作,改变的是该集本身,


第5节 映射

在Scala中,映射(Map)是一系列键值对的集合,也就是,建立了键和值之间的对应关系。在映射中,所有的值,都可以通过键来获取。

映射包括可变和不可变两种,默认情况下创建的是不可变映射,如果需要创建可变映射,需要引入scala.collection.mutable.Map包。

创建映射

下面我们创建一个不可变映射:

val university = Map("XMU" -> "Xiamen University", "THU" -> "Tsinghua University","PKU"->"Peking University")

如果要获取映射中的值,可以通过键来获取,如下:

println(university("XMU"))

上面代码通过"XMU"这个键,可以获得值Xiamen University。
如果要检查映射中是否包含某个值,可以使用contains方法,如下:

val xmu = if (university.contains("XMU")) university("XMU") else 0
println(xmu)

上面我们定义的是不可变映射,是无法更新映射中的元素的,也无法增加新的元素。如果要更新映射的元素,就需要定义一个可变的映射,如下:

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") //同时添加两个新元素

循环遍历映射

循环遍历映射,是经常需要用到的操作,基本格式是:

for ((k,v) <- 映射) 语句块

下面给出一个实例:

for ((k,v) <- university) printf("Code is : %s and name is: %s\n",k,v)

上面代码执行结果如下:

Code is : XMU and name is: Xiamen University
Code is : THU and name is: Tsinghua University
Code is : PKU and name is: Peking University

或者,也可以只遍历映射中的k或者v。

比如说,我们只想把所有键打印出来:

for (k<-university.keys) println(k)
XMU //打印出的结果
THU //打印出的结果
PKU //打印出的结果

再比如说,我们只想把所有值打印出来:

for (v<-university.values) println(v)
Xiamen University //打印出的结果
Tsinghua University //打印出的结果
Peking University  //打印出的结果


第6节 迭代器

在Scala中,迭代器(Iterator)不是一个集合,但是,提供了访问集合的一种方法。当构建一个集合需要很大的开销时(比如把一个文件的所有行都读取内存),迭代器就可以发挥很好的作用。
迭代器包含两个基本操作:next和hasNext。next可以返回迭代器的下一个元素,hasNext用于检测是否还有下一个元素。

有了这两个基本操作,我们就可以顺利地遍历迭代器中的所有元素了。
通常可以通过while循环或者for循环实现对迭代器的遍历。
while循环如下:

val iter = Iterator("Hadoop","Spark","Scala")
while (iter.hasNext) {
    println(iter.next())
}

注意,上述操作执行结束后,迭代器会移动到末尾,就不能再使用了,如果继续执行一次println(iter.next)就会报错。另外,上面代码中,使用iter.next和iter.next()都是可以的,但是,hasNext后面不能加括号。

for循环如下:

val iter = Iterator("Hadoop","Spark","Scala")
for (elem <- iter) {
    println(elem)
}

第六章 类

类和对象是Java、C++等面向对象编程的基础概念。类是用来创建对象的蓝图。定义好类以后,就可以使用new关键字来创建对象。

简单的类

最简单的类的定义形式是:

class Counter{
     //这里定义类的字段和方法
}

然后,就可以使用new关键字来生成对象:

new Counter //或者new Counter()

给类增加字段和方法

下面我们给这个类增加字段和方法:

class Counter {
    private var value = 0
    def increment(): Unit = { value += 1}
    def current(): Int = {value}
}

在上面定义中,我们把value字段设置为private,这样它就成为私有字段,外界无法访问,只有在类内部可以访问该字段。如果字段前面什么修饰符都没有,就默认是public,外部可以访问该字段。对于类而言,我们并不需要声明为public,Scala文件中包含的多个类之间,都是彼此可见的。

对于方法的定义,是通过def实现的。上面的代码“def increment(): Unit = { value += 1}”中,increment()是方法,没有参数,冒号后面的Unit是表示返回值的类型,在Scala中不返回任何值,那么就用Unit表示,相当于Java中的void类型。方法的返回值,不需要靠return语句,方法里面的最后一个表达式的值就是方法的返回值,比如,上面current()方法里面只有一条语句“value”,那么,value的值就是该方法的返回值。

因为increment()方法只是对value的值进行了增加1的操作,并没有返回任何值,所以,返回值类型是Unit。Unit后面的等号和大括号后面,包含了该方法要执行的具体操作语句。如果大括号里面只有一行语句,那么也可以直接去掉大括号,写成下面的形式

class Counter {
    private var value = 0
    def increment(): Unit = value += 1 //去掉了大括号
    def current(): Int = {value}  //作为对比,这里依然保留大括号
}

或者,还可以去掉返回值类型和等号,只保留大括号,如下:

class Counter {
    private var value = 0
    def increment() {value += 1} //去掉了返回值类型和等号,只保留大括号
    def current(): Int = {value} //作为对比,这里依然保留原来形式
}

创建对象

下面我们新建对象,并调用其中的方法:

val myCounter = new Counter
myCounter.increment() //或者也可以不用圆括号,写成myCounter.increment
println(myCounter.current)

从上面代码可以看出,Scala在调用无参方法时,是可以省略方法名后面的圆括号的。

编译和执行

现在,让我们把上面完整的代码拿到Linux系统中执行。请在登录Linux系统后,打开命令行终端(可以使用快捷组合键Ctr+Alt+T,快速打开命令行终端),进入到“/usr/local/scala/mycode”目录下,然后使用vim编辑器新建一个TestCounter.scala代码文件,如下:

  1. cd /usr/local/scala/mycode
  2. vim TestCounter.scala
Shell 命令

在TestCounter.scala中输入以下代码:

class Counter {
    private var value = 0
    def increment(): Unit = { value += 1}
    def current(): Int = {value}
}
val myCounter = new Counter
myCounter.increment()
println(myCounter.current)

保存后退出vim编辑器。然后,使用scala命令执行这个代码文件:

  1. scala TestCounter.scala
Shell 命令

上面命令执行后,会在屏幕输出“1”。

这是在Linux的Shell命令中执行。实际上,我们也可以进入到Scala解释器下面去执行,首先启动Scala解释器,如下:

  1. scala //在终端窗口中输入scala命令进入Scala解释器
Shell 命令

这时就进入了scala命令提示符状态,可以在里面输入如下命令:

  1. scala> :load /usr/local/scala/mycode/TestCounter.scala //这是输入的命令
  2. Loading /usr/local/scala/mycode/TestCounter.scala... //从这里开始是执行结果
  3. defined class Counter
  4. myCounter: Counter = Counter@17550481
  5. 1
scala

完成上面操作以后,我们可以退出Scala解释器,回到Linux系统的Shell命令提示符状态,退出Scala解释器的命令如下:

  1. scala> :quit
scala

下面,我们尝试一下,看看是否可以使用scalac命令对这个TestCounter.scala文件进行编译,如下:

  1. scalac TestCounter.scala
Shell 命令
注: scalac 要求代码中的声明都封装在对象中
执行上述scalac命令后,你会发现,会出现一堆错误,无法编译。为什么呢?因为,当我们使用scalac命令对TestCounter.scala进行编译时,必须要求把声明(比如val myCounter = new Counter以及myCounter.increment()等)都封装在对象中,这也是JVM字节码的要求。但是,在TestCounter.scala中,这些声明都没有被封装在对象中,所以,无法编译。

不过,如果我们确实需要把TestCounter.scala编译为JVM字节码,那么,可以使用下面命令:

  1. scalac -Xscript Upper1 TestCounter.scala //编译
  2. scala -classpath . Upper1 //执行
Shell 命令

执行后会在屏幕上返回结果:1。在上面代码中,-Xscript后面跟着的名称Upper1是你自己定义的main类名称(或者说对象名称?),你愿意起个名字叫Upper2,也是可以的。

好了,还记得我们在前面给大家介绍的HelloWorld程序吗?当时用的是一个包含了main()方法的大家比较熟悉的JVM应用程序,这种方式是本教程中推荐使用的方式,现在,我们采用这种方式重新编写上面的代码。

请在Linux系统的Shell命令提示符状态下,进入到“/usr/local/scala/mycode”目录下,然后使用vim编辑器新建一个TestCounterJVM.scala代码文件,如下:

  1. cd /usr/local/scala/mycode
  2. vim TestCounterJVM.scala
Shell 命令

在TestCounterJVM.scala中输入以下代码:

class Counter {
    private var value = 0
    def increment(): Unit = { value += 1}
    def current(): Int = {value}
}
object MyCounter{
    def main(args:Array[String]){
        val myCounter = new Counter
        myCounter.increment()
        println(myCounter.current)
    }
}

保存后退出vim编辑器。然后,使用scalac命令编译这个代码文件,并用scala命令执行,如下:

  1. scalac TestCounterJVM.scala
  2. scala -classpath . MyCounter //MyCounter是包含main方法的对象名称,这里不能使用文件名称TestCounterJVM
Shell 命令

上面命令执行后,会在屏幕输出“1”。

现在我们对之前的类定义继续改进一下,让方法中带有参数。我们可以修改一下TestCounterJVM.scala文件:

class Counter {
    private var value = 0
    def increment(step: Int): Unit = { value += step}
    def current(): Int = {value}
}
object MyCounter{
    def main(args:Array[String]){
        val myCounter = new Counter
        myCounter.increment(5) //这里设置步长为5,每次增加5
        println(myCounter.current)
    }
}

采用上面介绍的方法,编译执行这个文件,就可以得到执行结果是5。

getter和setter方法

下面我们来看一下如何给类中的字段设置值以及读取值。我们知道,在Java中,这是通过getter和setter方法实现的。在Scala中,也提供了getter和setter方法的实现,但是并没有定义成getXxx和setXxx。

我们继续修改TestCounterJVM.scala文件:

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)
    }
}

编译执行这个文件,就可以得到两行执行结果,第一行是0,第二行是4。
但是,我们都知道,在Java中,是不提倡设置这种公有(public)字段的,一般都是把value字段设置为private,然后提供getter和setter方法来获取和设置字段的值。那么,到了Scala中该怎么做呢?
我们先把value字段声明为private,看看会出现什么效果,继续修改TestCounterJVM.scala文件:

class Counter {
    private 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)
    }
}

现在我们去用scalac命令编译上面的代码,就会报错,会出现“error:variable value in class Counter cannot be accessed in Counter”这样的错误信息。因为,value字段前面用了修饰符private,已经成为私有字段,外部是无法访问的。
那么,value变成私有字段以后,Scala又没有提供getter和setter方法,怎么可以访问value字段呢?解决方案是,在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)
    }
}

编译执行这个文件,就可以得到三行执行结果,第一行是0,第二行是3,第三行是4。

辅助构造器

Scala构造器包含1个主构造器和若干个(0个或多个)辅助构造器。
我们首先认识一下辅助构造器。辅助构造器的名称为this,每个辅助构造器都必须调用一个此前已经定义的辅助构造器或主构造器。
下面定义一个带有辅助构造器的类,我们对上面的Counter类定义进行修改:

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) //显示计数器当前值       
    }
}

编译执行上述代码后,得到如下结果:

Name:Timer and mode is 2
Current Value is: 1
 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2022-11-29 20:36  yinhuachen  阅读(42)  评论(0编辑  收藏  举报