Scala数据结构

Posted 杨秉学的小屋

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Scala数据结构相关的知识,希望对你有一定的参考价值。

数组

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

val intValueArr = new Array[Int](3) //声明一个长度为3的整型数组,每个数组元素初始化为0intValueArr(0) = 12 //给第1个数组元素赋值为12intValueArr(1) = 45 //给第2个数组元素赋值为45intValueArr(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会自动根据提供的初始化数据来推断出数组的类型。

列表

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

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


元组

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

只需要用圆括号把多个元组的元素包围起来就可以了。

访问的时候是使用 ._1 这种方式


集(set)是不重复元素的集合。

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

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

Scala数据结构

要声明一个可变集,则需要引入scala.collection.mutable.Set

Scala数据结构

声明myMutableSet为val变量(不是var变量),由于是可变集,因此,可以正确执行myMutableSet += “Cloud Computing”,不会报错。

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


映射(Map)

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

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

一、创建一个不可变映射:

Scala数据结构


二、检查映射中是否包含某个值,可以使用contains方法

Scala数据结构


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

Scala数据结构


三、定义可变映射

scala> import scala.collection.mutable.Map import scala.collection.mutable.Mapscala> val university2 = Map("XMU" -> "Xiamen University""THU" -> "Tsinghua University","PKU"->"Peking University") university2: scala.collection.mutable.Map[String,String] = Map(XMU -> Xiamen University, THU -> Tsinghua University, PKU -> Peking University)scala> university2("XMU") = "Ximan University"  //更新已有元素的值scala> university2("FZU") = "Fuzhou University"  //添加新元素

Scala数据结构


四、循环遍历映射

基本格式是

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

Scala数据结构

只想把所有键打印出来:scala> for (k<-university.keys) println(k)

Scala数据结构

只想把所有值打印出来scala> for (v<-university.values) println(v)

Scala数据结构


迭代器(Iterator)

迭代器(Iterator)不是一个集合,但是,提供了访问集合的一种方法。当构建一个集合需要很大的开销时(比如把一个文件的所有行都读取内存),迭代器就可以发挥很好的作用。

迭代器包含两个基本操作:nexthasNext。next可以返回迭代器的下一个元素,hasNext用于检测是否还有下一个元素。

通常通过for循环对迭代器的遍历

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


类、对象

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

一、定义与使用

//定义class Counter{ //这里定义类的字段和方法}生成对象new Counter //或者new Counter()


二、增加字段和方法

class Counter { private var value = 0 #设置为private,这样它就成为私有字段,外界无法访问,只有在类内部可以访问该字段。 def increment(): Unit = { value += 1} #increment()是方法,没有参数,冒号后面的Unit是表示返回值的类型,在Scala中不返回任何值,那么就用Unit表示,相当于Java中的void类型。    def current(): Int = {value}             #方法Counter的返回值,不需要靠return语句,方法里面的最后一个表达式的值就是方法的返回值,这个current()方法里面只有一条语句“value”,那么,value的值就是该方法的返回值。}


三、创建对象

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


四、编译与执行

$> vim TestCounter.scalaclass Counter { private var value = 0 def increment(): Unit = { value += 1} def current(): Int = {value}}val myCounter = new CountermyCounter.increment()println(myCounter.current)
$> scala TestCounter.scala

Scala数据结构

Scala数据结构


或者启动scala解释器

Scala数据结构


使用scalac,会报错

Scala数据结构

因为,当我们使用scalac命令对TestCounter.scala进行编译时,必须要求把声明(比如val myCounter = new Counter以及myCounter.increment()等)都封装在对象中,这也是JVM字节码的要求。但是,在TestCounter.scala中,这些声明都没有被封装在对象中,所以,无法编译。
不过,如果我们确实需要把TestCounter.scala编译为JVM字节码,那么,可以使用下面命令:$> scalac -Xscript Upper1 TestCounter.scala //编译 -Xscript后面跟着的名称Upper1是你自己定义的main类名称,你愿意起个名字叫Upper2,也是可以的。$> scala -classpath . Upper1 //执行

Scala数据结构


严格写法

$> vim TestCounterJVM.scalaclass 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) }}
$> scalac TestCounterJVM.scala$> scala -classpath . MyCounter  #MyCounter是包含main方法的对象名称,这里不能使用文件名称TestCounterJVM

Scala数据结构

Scala数据结构


五、getter和setter方法

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

$> vi TestCounterJVM.scalaclass 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构造器包含1个主构造器和若干个(0个或多个)辅助构造器。

我们首先认识一下辅助构造器。辅助构造器的名称为this,每个辅助构造器都必须调用一个此前已经定义的辅助构造器或主构造器。

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
",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
",myCounter1.current) //显示计数器当前值 myCounter2.info //显示计数器信息 myCounter2.increment(2) //设置步长  printf("Current Value is: %d
",myCounter2.current) //显示计数器当前值 myCounter3.info //显示计数器信息        myCounter3.increment(3)                        //设置步长          printf("Current Value is: %d
",myCounter3.current) //显示计数器当前值 }}

Scala数据结构

Scala数据结构


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
",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
",myCounter.current) //显示计数器当前值  }}

Scala数据结构

Scala数据结构


继承

Scala中的继承与Java有着显著的不同:

(1)重写一个非抽象方法必须使用override修饰符。

(2)只有主构造器可以调用超类的主构造器。

(3)在子类中重写超类的抽象方法时,不需要使用override关键字。

(4)可以重写超类中的字段。

Scala和Java一样,不允许类从多个超类继承,因此,下面我们只讨论继承自一个类的情形。

一、抽象类

汽车为例子,首先我们创建一个抽象类,让这个抽象类被其他类继承。abstract class Car{ //是抽象类,不能直接被实例化 val carBrand: String //字段没有初始化值,就是一个抽象字段 def info() //抽象方法,不需要使用abstract关键字 def greeting() {println("Welcome to my car!")}}
关于上面的定义,说明几点:1)定义一个抽象类,需要使用关键字abstract2)定义一个抽象类的抽象方法,也不需要关键字abstract,只要把方法体空着,不写方法体就可以。3)抽象类中定义的字段,只要没有给出初始化值,就表示是一个抽象字段,但是,抽象字段必须要声明类型,比如:val carBrand: String,就把carBrand声明为字符串类型,这个时候,不能省略类型,否则编译会报错。


二、扩展类(继承类)

抽象类不能直接被实例化,所以,下面我们定义几个扩展类,它们都是扩展了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关键字}

Scala数据结构

Scala数据结构


特质(trait)

Java中提供了接口,允许一个类实现任意数量的接口。在Scala中没有接口的概念,而是提供了“特质(trait)”,它不仅实现了接口的功能,还具备了很多其他的特性。Scala的特质,是代码重用的基本单元,可以同时拥有抽象方法和具体方法。Scala中,一个类只能继承自一个超类,却可以实现多个特质,从而重用特质中的方法和字段,实现了多重继承。


一、特质的定义

trait CarId{ var id: Int def currentId(): Int //定义了一个抽象方法} 上面定义了一个特质,里面包含一个抽象字段id和抽象方法currentId。 注意,抽象方法不需要使用abstract关键字,特质中没有方法体的方法,默认就是抽象方法。


二、类中调用特质

使用extendswith关键字把特质混入类中

 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} //返回汽车编号 } 

Scala数据结构

Scala数据结构


三、特质包含具体实现

Java接口的功能只包含了抽象字段和抽象方法。但实际上,特质也可以包含具体实现,也就是说,特质中的字段和方法不一定要是抽象的。

trait CarGreeting{ def greeting(msg: String) {println(msg)} }


四、类调用多个特质


以上是关于Scala数据结构的主要内容,如果未能解决你的问题,请参考以下文章

详解 Scala 模式匹配

初学scala4——trait混入

Scala附加列表

scala编程——函数和闭包

在Scala项目中使用Spring Cloud

Scala的面向对象与函数编程