1、,第八章: Scala语言基础,8 Scala语言概述,1 计算机的缘起 2 编程范式 3 Scala简介,8.1.1 计算机的缘起,数学家阿隆佐邱奇(Alonzo Church)设计了“演算”,这是一套用于研究函数定义、函数应用和递归的形式系统演算被视为最小的通用程序设计语言演算的通用性就体现在,任何一个可计算函数都能用这种形式来表达和求值演算是一个数理逻辑形式系统,强调的是变换规则的运用,而非实现它们的具体机器,8.1.1 计算机的缘起:图灵机模型,由一个控制器,一条有限长携带有信息和运算指令带子的带子和一个可在带子上左右移动的读写头组成所有的电子计算机,都没有超出此模型 理想的计算机:毫
2、无停顿的运行下去 关键问题:数据怎么准备好?,依次执行(顺序) 默认的执行顺序 跳跃下行(分支) 函数调用 跳跃上行(循环),8.1.1 计算机的缘起,英国数学家阿兰图灵采用了完全不同的设计思路,提出了一种全新的抽象计算模型图灵机图灵机是现代计算机的鼻祖。现有理论已经证明,演算和图灵机的计算能力是等价的,8.1.1 计算机的缘起,冯诺依曼(John Von Neumann)将图灵的理论物化成为实际的物理实体,成为了计算机体系结构的奠基者 1945年6月,冯诺依曼提出了在数字计算机内部的存储器中存放程序的概念,这是所有现代计算机的范式,被称为“冯诺依曼结构”,8.1.2 编程范式,编程范式是指计
3、算机编程的基本风格或典范模式。常见的编程范式主要包括命令式编程和函数式编程。面向对象编程就属于命令式编程,比如C+、Java等 命令式语言是植根于冯诺依曼体系的,一个命令式程序就是一个冯诺依曼机的指令序列,给机器提供一条又一条的命令序列让其原封不动地执行 函数式编程,又称泛函编程,它将计算机的计算视为数学上的函数计算 函数编程语言最重要的基础是演算,演算对函数式编程特别是Lisp语言有着巨大的影响。典型的函数式语言包括Haskell、Erlang和Lisp等,8.1.2 编程范式,一个很自然的问题是,既然已经有了命令式编程,为什么还需要函数式编程呢? 为什么在C+、Java等命令式编程流行了很
4、多年以后,近些年函数式编程会迅速升温呢?,命令式编程涉及多线程之间的状态共享,需要锁机制实现并发控制 函数式编程不会在多个线程之间共享状态,不需要用锁机制,可以更好并行处理,充分利用多核CPU并行处理能力,8.1.3 Scala简介,Scala是一门类Java的多范式语言,它整合了面向对象编程和函数式编程的最佳特性。具体来讲: Scala运行于Java虚拟机(JVM)之上,并且兼容现有的Java程序 Scala是一门纯粹的面向对象的语言 Scala也是一门函数式语言,8. 2 Scala简介,Scala是一门现代的门类Java的多范式编程语言,运行于Java平台(JVM,Java 虚拟机),并
5、兼容现有的Java程序,整合了面向对象编程和函数式编程的最佳特性。Scala的特性: Scala具备强大的并发性,支持函数式编程,可以更好地支持分布式系统 Scala语法简洁,能提供优雅的API Scala兼容Java,运行速度快,且能融合到Hadoop生态圈中,Scala是Spark的主要编程语言,但Spark还支持Java、Python、R作为编程语言 Scala的优势是提供了REPL(Read-Eval-Print Loop,交互式解释器),提高程序开发效率,8.1.4 Scala的安装和使用方法,Scala运行于Java虚拟机(JVM)之上,因此只要安装有相应的Java虚拟机,所有的操
6、作系统都可以运行Scala程序,包括Window、Linux、Unix、 Mac OS等。8.1.4.1 安装Java 8.1.4.2 安装Scala 8.1.4.3 使用Scala解释器 8.1.4.4 第1个Scala程序:HelloWorld,8.1.4.1 安装Java,直接通过命令安装 OpenJDK 7,配置 JAVA_HOME 环境变量,使配置立即生效:,8.1.4.2 安装Scala,登录Scala官网,下载scala-8.11.8.tgz,把scala命令添加到path环境变量中,启动Scala解释器:,8.1.4.3 使用Scala解释器,在Shell命令提示符界面中输入“
7、scala”命令后,会进入scala命令行提示符状态:,可以使用命令“:quit”退出Scala解释器,如下所示:,8.1.4.4 第1个Scala程序:HelloWorld,注意,上面命令中一定要加入“-classpath .“,否则会出现“No such file or class on classpath: HelloWorld”,8. 2 Scala基础,8.2.1 基本语法 8.2.2 控制结构 8.2.3 数据结构 8.8.4 面向对象编程基础 8.2.5 函数式编程基础,8.2.1 基本语法,8.2.1.1 声明值和变量 8.2.1.2 基本数据类型和操作 8.2.1.3 Ran
8、ge 8.2.1.4 控制台输入输出语句 8.2.1.5 读写文件 8.2.1.6 异常处理,8.2.1.1 声明值和变量,Scala有两种类型的变量: val:是不可变的,在声明时就必须被初始化,而且初始化以后就不能再赋值; var:是可变的,声明的时候需要进行初始化,初始化以后还可以再次对其赋值。,8.2.1.1 声明值和变量,8.2.1.1 声明值和变量,8.2.1.1 声明值和变量,小技巧:如何在Scala解释器中输入多行代码,8.2.1.2 基本数据类型和操作,Scala的数据类型包括:Byte、Char、Short、Int、Long、Float、Double和Boolean 和Ja
9、va不同的是,在Scala中,这些类型都是“类”,并且都是包scala的成员,比如,Int的全名是scala.Int。对于字符串,Scala用java.lang.String类来表示字符串,8.2.1.2 基本数据类型和操作,字面量(literal),8.2.1.2 基本数据类型和操作,操作符:在Scala中,可以使用加(+)、减(-) 、乘(*) 、除(/) 、余数(%)等操作符,而且,这些操作符就是方法。例如,5 + 3和(5).+(3)是等价的,也就是说:,等价于,前者是后者的简写形式,这里的+是方法名,是Int类中的一个方法。,和Java不同,在Scala中并没有提供+和-操作符,当需
10、要递增和递减时,可以采用如下方式表达:,8.2.1.2 基本数据类型和操作,富包装类,对于基本数据类型,除了以上提到的各种操作符外,Scala还提供了许多常用运算的方法,只是这些方法不是在基本类里面定义,还是被封装到一个对应的富包装类中 每个基本类型都有一个对应的富包装类,例如Int有一个RichInt类、String有一个RichString类,这些类位于包scala.runtime中 当对一个基本数据类型的对象调用其富包装类提供的方法,Scala会自动通过隐式转换将该对象转换为对应的富包装类型,然后再调用相应的方法。例如:3 max 5,8.2.1.3 Range,在执行for循环时,我们
11、经常会用到数值序列,比如,i的值从1循环到5,这时就可以采用Range来实现 Range可以支持创建不同数据类型的数值序列,包括Int、Long、Float、Double、Char、BigInt和BigDecimal等,(1)创建一个从1到5的数值序列,包含区间终点5,步长为1,8.2.1.3 Range,(2)创建一个从1到5的数值序列,不包含区间终点5,步长为1,(3)创建一个从1到10的数值序列,包含区间终点10,步长为2,(4)创建一个Float类型的数值序列,从0.5f到5.9f,步长为0.8f,8.2.1.4控制台输入输出语句,为了从控制台读写数据,可以使用以read为前缀的方法,
12、包括:readInt、readDouble、readByte、readShort、readFloat、readLong、readChar readBoolean及readLine,分别对应9种基本数据类型,其中前8种方法没有参数,readLine可以不提供参数,也可以带一个字符串参数的提示所有这些函数都属于对象scala.io.StdIn的方法,使用前必须导入,或者直接用全称进行调用,8.2.1.4控制台输入输出语句,8.2.1.4控制台输入输出语句,为了向控制台输出信息,常用的两个函数是print()和println(),可以直接输出字符串或者其它数据类型,8.2.1.4控制台输入输出语句,
13、Scala还带有C语言风格的格式化字符串的printf()函数,print()、println()和printf() 都在对象Predef中定义,该对象默认情况下被所有Scala程序引用,因此可以直接使用Predef对象提供的方法,而无需使用scala.Predef.的形式。,8.2.1.5读写文件,写入文件,Scala需要使用java.io.PrintWriter实现把数据写入到文件,如果我们想把文件保存到一个指定的目录下,就需要给出文件路径,8.2.1.5读写文件,读取文件,可以使用Scala.io.Source的getLines方法实现对文件中所有行的读取,8.2.1.6 异常处理,Sc
14、ala仍使用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() / 确保关闭文件 ,Scala不支持Java中的“受检查异常”
15、(checked exception),将所有异常都当作“不受检异常”(或称为运行时异常),8.2.2 控制结构,8.2.2.1 if条件表达式 8.2.2.2 while循环 8.2.2.3 for循环,8.2.2.1 if条件表达式,有一点与Java不同的是,Scala中的if表达式的值可以赋值给变量,8.2.2.2 while循环,8.2.2.3 for循环,Scala中的for循环语句格式如下:,其中,“变量-表达式”被称为“生成器(generator)”,8.2.2.3 for循环,不希望打印出所有的结果,过滤出一些满足制定条件的结果,需要使用到称为“守卫(guard)”的表达式 比
16、如,只输出1到5之中的所有偶数,可以采用以下语句:,8.2.2.3 for循环,Scala也支持“多个生成器”的情形,可以用分号把它们隔开,比如:,8.2.2.3 for循环,可以给每个生成器都添加一个“守卫”,如下:,8.2.2.3 for循环,Scala的for结构可以在每次执行的时候创造一个值,然后将包含了所有产生值的集合作为for循环表达式的结果返回,集合的类型由生成器中的集合类型确定 通过for循环遍历一个或多个集合,对集合中的元素进行“推导”,从而计算得到新的集合,用于后续的其他处理for (变量 - 表达式) yield 语句块,for推导式,8.2.3 数据结构,8.2.3.1
17、 容器(Collection) 8.2.3.2 列表(List) 8.2.3.3 集合(Set) 8.2.3.4 映射(Map) 8.2.3.5 迭代器(Iterator) 8.2.3.6 数组(Array) 8.2.3.7 元组(Tuple),8.2.3.1 容器(collection),Scala提供了一套丰富的容器(collection)库,包括列表(List)、数组(Array)、集合(Set)、映射(Map)等根据容器中元素的组织方式和操作方式,可以区分为有序和无序、可变和不可变等不同的容器类别Scala用了三个包来组织容器类,分别是scala.collection 、scala.c
18、ollection.mutable和scala.collection.immutable,8.2.3.1 容器(collection),下图显示了scala.collection包中所有的容器类。这些都是高级抽象类或特质。例如,所有容器类的基本特质(trait)是Traverable特质,它为所有的容器类定义了公用的foreach方法,用于对容器元素进行遍历操作,8.2.3.1 容器(collection),下面的图表显示了scala.collection.immutable中的所有容器类,8.2.3.1 容器(collection),下面的图表显示scala.collection.mutab
19、le中的所有容器类,8.2.3.2 列表(List),列表是一种共享相同类型的不可变的对象序列。既然是一个不可变的集合, Scala的List定义在scala.collection.immutable包中 不同于Java的java.util.List,scala的List一旦被定义,其值就不能改变,因此声明List时必须初始化,var strList=List(“BigData“,“Hadoop“,“Spark“),列表有头部和尾部的概念,可以分别使用head和tail方法来获取 head返回的是列表第一个元素的值 tail返回的是除第一个元素外的其它值构成的新列表,这体现出列表具有递归的链表
20、结构 strList.head将返回字符串”BigData”,strList.tail返回List (“Hadoop“,“Spark“),8.2.3.2 列表(List),Scala还定义了一个空列表对象Nil,借助Nil,可以将多个元素用操作符:串起来初始化一个列表,val intList = 1:2:3:Nil,与val intList = List(1,2,3)等效,构造列表常用的方法是通过在已有列表前端增加元素,使用的操作符为:,例如:,val otherList=“Apache“:strList,执行该语句后strList保持不变,而otherList将成为一个新的列表: List(
21、“Apache“,“BigData“,“Hadoop“,“Spark“),8.2.3.3 集合(Set),集合(set)是不重复元素的容器(collection)。列表中的元素是按照插入的先后顺序来组织的,但是,“集合”中的元素并不会记录元素的插入顺序,而是以“哈希”方法对元素的值进行组织,所以,它允许你快速地找到某个元素,集合包括可变集和不可变集,分别位于scala.collection.mutable包和scala.collection.immutable包,缺省情况下创建的是不可变集,val mySet = Set(“Hadoop“,“Spark“),如果要声明一个可变集,则需要提前引入
22、scala.collection.mutable.Set,import scala.collection.mutable.Set var myMutableSet = Set(“Database“,“BigData“) myMutableSet += “Cloud Computing“,8.2.3.4 映射(Map),映射(Map)是一系列键值对的容器。在一个映射中,键是唯一的,但值不一定是唯一的。可以根据键来对值进行快速的检索 和集合一样,Scala 采用了类继承机制提供了可变的和不可变的两种版本的映射,分别定义在包scala.collection.mutable 和scala.collec
23、tion.immutable 里。默认情况下,Scala中使用不可变的映射。如果想使用可变映射,必须明确地导入scala.collection.mutable.Map,val university = Map(“XMU“ - “Xiamen University“, “THU“ - “Tsinghua University“,“PKU“-“Peking University“),8.2.3.4 映射(Map),如果要获取映射中的值,可以通过键来获取,对于这种访问方式,如果给定的键不存在,则会抛出异常,为此,访问前可以先调用contains方法确定键是否存在,8.2.3.4 映射(Map),不可
24、变映射,是无法更新映射中的元素的,也无法增加新的元素。如果要更新映射的元素,就需要定义一个可变的映射,也可以使用+=操作来添加新的元素,8.2.3.4 映射(Map),循环遍历映射,或者,也可以只遍历映射中的k或者v,8.2.3.5 迭代器(Iterator),在Scala中,迭代器(Iterator)不是一个集合,但是,提供了访问集合的一种方法 迭代器包含两个基本操作:next和hasNext。next可以返回迭代器的下一个元素,hasNext用于检测是否还有下一个元素,8.2.3.5 迭代器(Iterator),scala val xs = List(1, 2, 3, 4, 5) xs:
25、ListInt = List(1, 2, 3, 4, 5) scala val git = xs grouped 3 git: IteratorListInt = non-empty iterator scala git.next() res3: ListInt = List(1, 2, 3) scala git.next() res4: ListInt = List(4, 5) scala val sit = xs sliding 3 sit: IteratorListInt = non-empty iterator scala sit.next() res5: ListInt = List
26、(1, 2, 3) scala sit.next() res6: ListInt = List(2, 3, 4) scala sit.next() res7: ListInt = List(3, 4, 5),Iterable有两个方法返回迭代器:grouped和sliding。然而,这些迭代器返回的不是单个元素,而是原容器(collection)元素的全部子序列。这些最大的子序列作为参数传给这些方法。grouped方法返回元素的增量分块,sliding方法生成一个滑动元素的窗口。两者之间的差异通过REPL的作用能够清楚看出。,8.2.3.6 数组(Array),数组是一种可变的、可索引的、元素
27、具有相同类型的数据集合,它是各种高级语言中最常用的数据结构。Scala提供了参数化类型的通用数组类ArrayT,其中T可以是任意的Scala类型,可以通过显式指定类型或者通过隐式推断来实例化一个数组。,可以不给出数组类型,Scala会自动根据提供的初始化数据来推断出数组的类型,8.2.3.6 数组(Array),Array提供了函数ofDim来定义二维和三维数组,用法如下:,val myMatrix = Array.ofDimInt(3,4) /类型实际就是ArrayArrayIntval myCube = Array.ofDimString(3,2,4) /类型实际是ArrayArrayAr
28、rayInt,可以使用多级圆括号来访问多维数组的元素,例如myMatrix(0)(1)返回第一行第二列的元素,8.2.3.6 数组(Array),采用Array类型定义的数组属于定长数组,其数组长度在初始化后就不能改变。如果要定义变长数组,需要使用ArrayBuffer参数类型,其位于包scala.collection.mutable中。举例如下:,import scala.collection.mutable.ArrayBuffer val aMutableArr = ArrayBuffer(10,20,30) aMutableArr += 40 aMutableArr.insert(2,
29、60,40) aMutableArr -= 40 var temp=aMutableArr.remove(2),8.2.3.7 元组(Tuple),元组是不同类型的值的聚集。元组和列表不同,列表中各个元素必须是相同类型,而元组可以包含不同类型的元素,8.3 面向对象编程基础,8.3.1 类 8.3.2 对象 8.3.3 继承 8.3.4 特质 8.3.5 模式匹配,8.3.1 类,8.3.1.1 简单的类 8.3.1.2 给类增加字段和方法 8.3.1.3 创建对象 8.3.1.4 编译和执行 8.3.1.5 getter和setter方法 8.3.1.6 辅助构造器 8.3.1.7主构造器,
30、8.3.1.1 简单的类,最简单的类的定义形式是:,可以使用new关键字来生成对象,8.3.1.2 给类增加字段和方法,如果大括号里面只有一行语句,那么也可以直接去掉大括号,写成下面的形式:,或者,还可以去掉返回值类型和等号,只保留大括号,如下:,Unit后面的等号和大括号后面,包含了该方法要执行的具体操作语句,8.3.1.3 创建对象,下面我们新建对象,并调用其中的方法:,从上面代码可以看出,Scala在调用无参方法时,是可以省略方法名后面的圆括号的,8.3.1.4 编译和执行,新建一个TestCounter.scala代码文件,在Linux Shell命令提示符下,使用scala命令执行这
31、个代码文件:,上面命令执行后,会在屏幕输出“1”,8.3.1.4 编译和执行,也可以进入到Scala解释器下面去执行TestCounter.scala 首先启动Scala解释器,如下:,进入scala命令提示符状态以后,可以在里面输入如下命令:,完成上面操作以后,可以退出Scala解释器,回到Linux系统的Shell命令提示符状态,退出Scala解释器的命令如下:,8.3.1.4 编译和执行,下面尝试一下,看看是否可以使用scalac命令对这个TestCounter.scala文件进行编译,如下:,执行上述scalac命令后,会出现一堆错误,无法编译。为什么呢?,原因:声明都没有被封装在对象
32、中,因此,无法编译成JVM字节码,8.3.1.4 编译和执行,在TestCounterJVM.scala中输入以下代码:,使用scalac命令编译这个代码文件,并用scala命令执行,如下:,上面命令执行后,会在屏幕输出“1”,$ scalac TestCounterJVM.scala $ scala -classpath . MyCounter /MyCounter是包含main方法的对象名称,这里不能使用文件名称TestCounterJVM,8.3.1.4 编译和执行,现在我们对之前的类定义继续改进一下,让方法中带有参数。我们可以修改一下TestCounterJVM.scala文件:,编译
33、执行这个文件,就可以得到执行结果是5。,8.3.1.5 getter和setter方法,给类中的字段设置值以及读取值,在Java中是通过getter和setter方法实现的 在Scala中,也提供了getter和setter方法的实现,但是并没有定义成getXxx和setXxx,继续修改TestCounterJVM.scala文件:,编译执行这个文件,就可以得到两行执行结果,第一行是0,第二行是4。,8.3.1.5 getter和setter方法,但是,在Java中,是不提倡设置这种公有(public)字段的,一般都是把value字段设置为private,然后提供getter和setter方法
34、来获取和设置字段的值。那么,到了Scala中该怎么做呢?,现在我们去用scalac命令编译上面的代码,就会报错,会出现“error:variable value in class Counter cannot be accessed in Counter”这样的错误信息。因为,value字段前面用了修饰符private,已经成为私有字段,外部是无法访问的。,我们先把value字段声明为private,看看会出现什么效果,继续修改TestCounterJVM.scala文件:,8.3.1.5 getter和setter方法,value变成私有字段以后,Scala又没有提供getter和sette
35、r方法,怎么可以访问value字段呢?解决方案是,在Scala中,可以通过定义类似getter和setter的方法,分别叫做value和value_=,具体如下:,编译执行这个文件,就可以得到三行执行结果,第一行是0,第二行是3,第三行是4。,8.3.1.6 辅助构造器,Scala构造器包含1个主构造器和若干个(0个或多个)辅助构造器 辅助构造器的名称为this,每个辅助构造器都必须调用一个此前已经定义的辅助构造器或主构造器 下面定义一个带有辅助构造器的类,我们对上面的Counter类定义进行修改:,(代码未完,剩余代码见下一页),8.3.1.6 辅助构造器,(代码续上一页),编译执行上述代码
36、后,得到右边结果:,8.3.1.7主构造器,Scala的每个类都有主构造器。但是,Scala的主构造器和Java有着明显的不同,Scala的主构造器是整个类体,需要在类名称后面罗列出构造器所需的所有参数,这些参数被编译成字段,字段的值就是创建对象时传入的参数的值。 对于上面给计数器设置name和mode的例子,刚才我们是使用辅助构造器来对name和mode的值进行设置,现在我们重新来一次,这次我们转而采用主构造器来设置name和mode的值。,编译执行上述代码后,得到结果:,8.3.2 对象,8.3.8.1 单例对象 8.3.8.2 伴生对象 8.3.8.3 应用程序对象 2.3.8.4 ap
37、ply方法和update方法,8.3.8.1 单例对象,Scala并没有提供Java那样的静态方法或静态字段,但是,可以采用object关键字实现单例对象,具备和Java静态方法同样的功能。,下面是单例对象的定义:,可以看出,单例对象的定义和类的定义很相似,明显的区分是,用object关键字,而不是用class关键字。,8.3.8.1 单例对象,把上述代码放入到一个test.scala文件中测试,在Shell命令提示符下输入scala命令运行上面代码:,执行后,屏幕上会显示以下结果:,8.3.2.2 伴生对象,在Java中,我们经常需要用到同时包含实例方法和静态方法的类,在Scala中可以通过
38、伴生对象来实现。 当单例对象与某个类具有相同的名称时,它被称为这个类的“伴生对象”。 类和它的伴生对象必须存在于同一个文件中,而且可以相互访问私有成员(字段和方法)。,8.3.2.2 伴生对象,删除并重新创建一个test.scala,在该文件中输入如下代码:,运行结果:,8.3.2.2 伴生对象,从上面结果可以看出,伴生对象中定义的newPersonId()实际上就实现了Java中静态(static)方法的功能,Scala源代码编译后都会变成JVM字节码,实际上,在编译上面的源代码文件以后,在Scala里面的class和object在Java层面都会被合二为一,class里面的成员成了实例成员
39、,object成员成了static成员,8.3.2.2 伴生对象,为了验证这一点,我们可以一起测试一下。,删除并重新创建一个test.scala,在该文件中输入如下代码:,这里做一点小小修改,那就是把object Person中的newPersonId()方法前面的private去掉,8.3.2.2 伴生对象,在Shell命令提示符状态下,输入以下命令编译并执行:,在目录下看到两个编译后得到的文件,即Person.class和Person$.class。经过编译后,伴生类和伴生对象在JVM中都被合并到了一起,执行结果如下:,从结果可以看出,经过编译后,伴生类Person中的成员和伴生对象Per
40、son中的成员都被合并到一起,并且,伴生对象中的方法newPersonId(),成为静态方法,忽略Person$.class,只看Person.class。请使用下面命令进行“反编译”:,8.3.2.3 应用程序对象,每个Scala应用程序都必须从一个对象的main方法开始,重新创建一个test.scala,在该文件中输入如下代码:,为了运行上述代码,我们现在可以使用两种不同的方法。,第一种方法:直接使用scala命令运行得到结果。,第二种方法:先编译再执行,8.3.8.4 apply方法和update方法,我们经常会用到对象的apply方法和update方法,虽然我们表面上并没有察觉,但是,
41、实际上,在Scala中,apply方法和update方法都会遵循相关的约定被调用,约定如下: 用括号传递给变量(对象)一个或多个参数时,Scala 会把它转换成对apply方法的调用 当对带有括号并包括一到若干参数的对象进行赋值时,编译器将调用对象的update方法,在调用时,是把括号里的参数和等号右边的对象一起作为update方法的输入参数来执行调用,8.3.8.4 apply方法和update方法,下面我们测试一下apply方法是否被调用。删除并重新创建test.scala文件,输入以下代码:,在Linux系统的Shell命令提示符下运行scala命令:,运行后会得到以下结果:,8.3.8
42、.4 apply方法和update方法,上面是类中定义了apply方法,下面看一个在单例对象中定义apply方法的例子:,把上面代码放入到test.scala文件中测试执行后,可以得到如下结果:,可以看出,在执行TestApplySingleObject(“Zhangfei“, “Liubei“)时调用了apply方法,并且把“Zhangfei and Liubei”作为返回值,赋值给group变量,因此,println(group)语句会打印出“Zhangfei and Liubei”。,8.3.8.4 apply方法和update方法,下面我们测试一个伴生类和伴生对象中的apply方法实例
43、。删除并重新创建test.scala文件,输入以下代码:,执行结果如下:,8.3.8.4 apply方法和update方法,首先使用scalac编译命令对test.scala进行编译,然后,使用scala命令运行,具体如下:,上述代码执行后得到以下结果:,从上面代码可以看出,当我们执行val a = ApplyTest()时,会导致apply方法的调用并返回该方法调用的值,也就是ApplyTest的实例化对象。当执行a()时,又会导致调用伴生类的apply方法,如果我们愿意,就可以在伴生类的apply方法中写入一些处理逻辑,这样就可以把传入的参数赋值给实例化对象的变量。,8.3.8.4 app
44、ly方法和update方法,下面看一个apply方法的例子。由于Scala中的Array对象定义了apply方法,因此,我们就可以采用如下方式初始化一个数组:,也就是说,不需要new关键字,不用构造器,直接给对象传递3个参数,Scala就会转换成对apply方法的调用,也就是调用Array类的伴生对象Array的apply方法,完成数组的初始化。,8.3.8.4 apply方法和update方法,实际上,update方法也是类似的,比如:,从上面可以看出,在进行元组赋值的时候,之所以没有采用Java中的方括号myStrArr0,而是采用圆括号的形式,myStrArr(0),是因为存在上述的up
45、date方法的机制。,8.3.3 继承,8.3.3.1 Scala与Java在继承方面的区别 8.3.3.2 抽象类 8.3.3.3 扩展类,8.3.3.1 Scala与Java在继承方面的区别,Scala中的继承与Java有着显著的不同: (1)重写一个非抽象方法必须使用override修饰符。 (2)只有主构造器可以调用超类的主构造器。 (3)在子类中重写超类的抽象方法时,不需要使用override关键字。 (4)可以重写超类中的字段。Scala和Java一样,不允许类从多个超类继承,8.3.3.2 抽象类,以汽车为例子,首先我们创建一个抽象类,让这个抽象类被其他类继承。,关于上面的定义,
46、说明几点: (1)定义一个抽象类,需要使用关键字abstract。 (2)定义一个抽象类的抽象方法,也不需要关键字abstract,只要把方法体空着,不写方法体就可以。 (3)抽象类中定义的字段,只要没有给出初始化值,就表示是一个抽象字段,但是,抽象字段必须要声明类型,比如:val carBrand: String,就把carBrand声明为字符串类型,这个时候,不能省略类型,否则编译会报错。,8.3.3.3 扩展类,抽象类不能直接被实例化,所以,下面我们定义几个扩展类,它们都是扩展了Car类,或者说继承自Car类。,8.3.3.3 扩展类,下面,我们把上述代码放入一个完整的代码文件test.
47、scala,编译运行。,执行后,屏幕上会显示以下结果:,8.3.3.3 扩展类,在Shell命令提示符下输入scala命令运行上面代码:,执行后,屏幕上会显示以下结果:,8.3.4 特质(trait),8.3.4.1 特质概述 8.3.4.2 特质的定义 8.3.4.3 把特质混入类中 8.3.4.4 特质可以包含具体实现 8.3.4.5 把多个特质混入类中,8.3.4.1 特质概述,Java中提供了接口,允许一个类实现任意数量的接口 在Scala中没有接口的概念,而是提供了“特质(trait)”,它不仅实现了接口的功能,还具备了很多其他的特性 Scala的特质,是代码重用的基本单元,可以同时
48、拥有抽象方法和具体方法 Scala中,一个类只能继承自一个超类,却可以实现多个特质,从而重用特质中的方法和字段,实现了多重继承,8.3.4.2 特质的定义,特质的定义和类的定义非常相似,有区别的是,特质定义使用关键字trait。,上面定义了一个特质,里面包含一个抽象字段id和抽象方法currentId。注意,抽象方法不需要使用abstract关键字,特质中没有方法体的方法,默认就是抽象方法。,8.3.4.3 把特质混入类中,特质定义好以后,就可以使用extends或with关键字把特质混入类中。,8.3.4.3 把特质混入类中,下面,我们把上述代码放入一个完整的代码文件test.scala,编
49、译运行。,执行结果:,8.3.4.4 特质可以包含具体实现,上面的实例中,特质只包含了抽象字段和抽象方法,相当于实现了类似Java接口的功能。实际上,特质也可以包含具体实现,也就是说,特质中的字段和方法不一定要是抽象的。,8.3.4.5 把多个特质混入类中,上面已经定义了两个特质CarId和CarGreeting。可以把两个特质都混入到类中。,执行结果如下:,8.3.5 模式匹配,8.3.5.1 简单匹配 8.3.5.2 类型模式 8.3.5.3 “守卫(guard)“语句 8.3.5.4 for表达式中的模式 8.3.5.5 case类的匹配 8.3.5.6 Option类型,8.3.5.1 简单匹配,Scala的模式匹配最常用于match语句中。下面是一个简单的整型值的匹配实例。,另外,在模式匹配的case语句中,还可以使用变量。,执行结果:,8.3.5.2 类型模式,Scala可以对表达式的类型进行匹配。,执行结果:,8.3.5.3 “守卫(guard)“语句,可以在模式匹配中添加一些必要的处理逻辑。,执行结果:,8.3.5.4 for表达式中的模式,以我们之前举过的映射为例子,我们创建的映射如下:,循环遍历映射的基本格式是:,