第一部分 Scala基础
第 1 节 Scala语言概况
1.1 Scala语言起源
马丁·奥德斯基(Martin Odersky)是编译器及编程的狂热爱好者。主流JVM的Javac编译器就是马丁·奥德斯基编写出来的,JDK5.0、JDK8.0的编译器就是他写的。长时间的编程之后,他希望发明一种语言,能够让写程序这样的基础工作变得高效,简单。当接触到Java语言后,对Java这门语言产生了极大的兴趣,所以决定将函数式编程语言的特点融合到Java中,由此发明了Scala。
1.2 Scala语言特点
Scala是一门以 JVM 为运行环境并将面向对象和函数式编程的最佳特性结合在一起的静态类型编程语言。
- Scala源代码会被编译成Java字节码,然后运行于JVM之上,并可以调用现有的Java类库,实现两种语言的无缝互操作的。
- 面向对象
- Scala是一种面向对象的语言。
- Scala中的每个值都是一个对象,包括基本数据类型(即布尔值、数字等)在内,连函数也是对象。
- 函数式编程
- Scala也是一种函数式语言,其函数也能当成值来使用。
- Scala中支持高阶函数,允许嵌套多层函数,并支持柯里化。
- Scala提供了模式匹配,可以匹配各种情况,比如变量的类型、集合的元素、有值或无值。
- 静态类型
- Scala具备类型系统,通过编译时检查,保证代码的安全性和一致性。
并发性
优雅:这是框架设计师第一个要考虑的问题,框架的用户是应用开发程序员,API是否优雅直接影响用户体验。
- 简洁:Scala语言表达能力强,一行代码抵得上Java多行,开发速度快。
融合大数据生态圈:Hadoop现在是大数据事实标准,(Kafka Spark源码都是用Scala编写的,Spark、Flink都支持使用Scala进行开发)Spark并不是要取代Hadoop,而是要完善Hadoop生态。
第 2 节 环境准备
Scala官网:https://www.scala-lang.org/
1 、下载Scala
2 、Windows下安装Scala
3 、配置IDEA开发环境
4 、REPL
2.1 Windows下环境配置
访问Scala官网下载Scala 2.11.8安装包,下载地址:https://www.scala-lang.org/download/2.11.8.html
- 下载scala-2.11.8.msi后,点击下一步就可以了(自动配置上环境变量)。
- 也可以下载 scala-2.11.8.zip,解压后配置上环境变量就可以了。
备注:安装Scala之前,Windows系统需要安装JDK。
2.2 IDEA环境配置
IDEA是 Java 的集成开发环境,要支持Scala开发,需要安装Scala插件;
object HelloWorld {
def main(args: Array[String]): Unit = {
println("Hello World")
}
}
2.3 Scala的REPL
- 在命令行输入Scala可启动Scala REPL。
- REPL 是一个交互式解析器环境,R(read)、E(evaluate) 、P(print)、L(loop)
输入值,交互式解析器会读取输入内容并对它求值,再打印结果,并重复此过程。
第 3 节 基础语法
基础语法规则:
- 区分大小写 - Scala语言对大小写敏感;
- 类名 - 对于所有的类名的第一个字母要大写。如果需要使用几个单词来构成一个类名,每个单词的第一个字母要大写;比如:ListDemo
- 方法名 - 所有方法名的第一个字母用小写。如果需要使用几个单词来构成方法名,除第一个单词外每个词的第一个字母应大写;比如:getResult
- 程序文件名 - Scala程序文件的后缀名是 .scala,程序文件的名称可以不与对象名称完全匹配。这点与Java有所区别。备注:建议遵循 Java 的惯例,程序文件名称与对象名称匹配;
- main()方法 - Scala程序从main()方法开始处理,这是每一个Scala程序的入口点。main()定义在object中;
- 标识符。 所有Scala组件都需要名称,用于对象、类、变量和方法的名称称为标识符。关键字不能用作标识符,标识符区分大小写;标识符以字母或下划线开头,后面可以有更多的字母、数字或下划线;$字符是Scala中的保留关键字,不能在标识符中使用;
- 注释。 Scala使用了与Java相同的单行和多行注释;
- 换行符。 Scala语句可以用分号作为一行的结束,语句末尾的分号通常可以省略,但是如果一行里有多个语句那么分号是必须的。
小结:
Scala和Java一样,有 8 种数值类型 Byte、Short、Int、Long、Float、Double、Char、Boolean 类型;和 Java 不同的是 ,这些类型都是类,有自己的属性和方法。Scala并不刻意的区分基本类型和引用类型。String 直接引用 Java.lang.String 中的类型,String在需要时能隐式转换为StringOps,因此不需要任何额外的转换,String就可以使用StringOps中的方法。每一种数据类型都有对应的Rich类型,如RichInt、RichChar等,为基本类型提供了更多的有用操作。
整数字面量 。整数字面量有两种形式,十进制与十六进制(0X/0x开头) ```scala — 十六进制整数字面量 scala> val a = 0xa a: Int = 10
scala> val a = 0X00FF a: Int = 255
scala> val magic = 0xcafe magic: Int = 51966
— 十进制整数字面量 scala> val dec1 = 255 dec1: Int = 255
scala> val dec1 = 31 dec1: Int = 31
— Long类型整数字面量 scala> val magic = 0xcafeL magic: Long = 51966
scala> val long1 = 255L long1: Long = 255
— Short 或 Byte 类型,需要明确声明,否则编译器会推断为Int类型 scala> val little: Short = 32767 little: Short = 32767
scala> val littler: Byte = 127 littler: Byte = 127
- **浮点数字面量**
```scala
-- 十进制数、可选的小数点、可选的e开头的指数
scala> val big = 3.1415926
big: Double = 3.1415926
scala> val bigger = 3.1415926e1
bigger: Double = 31.415926
-- 浮点数字面量以F/f结尾为Float类型;否则为Double类型;
scala> val litte = 0.31415926f
litte: Float = 0.31415927
scala> val litte = 0.31415926e1F
litte: Float = 3.1415925
- 字符字面量 ```scala scala> val a = ‘A’ a: Char = A
— 用字符的Unicode码来表示。Unicode码前128个字符就是ASCII码 scala> val b = ‘\u0042’ b: Char = B
— 转义字符 scala> val mark = ‘\’
- **字符串字面量**
```scala
scala> val str = "Hello Scala"
str: String = Hello Scala
第 5 节 类层次结构
- Scala中,所有的类,包括值类型和引用类型,都最终继承自一个统一的根类型Any。
- Scala中定义了以下三个底层类:
- Any是所有类型共同的根类型,Any是AnyRef和AnyVal的超类
- AnyRef是所有引用类型的超类
- AnyVal是所有值类型的超类
- 上图中有三个类型需要注意:
- Null是所有引用类型的子类型,Null类只有一个实例对象null 。
- null可以赋值给任意引用类型,但是不能赋值给值类型。
- Nothing位于Scala类继承关系的底部,它是其他所有其他类型的子类型
- Nothing对泛型结构有用 。比如,空列表Nil的类型就是List[Nothing]
- Nothing的可以给出非正常终止的信号。比如,使用Nothing处理异常
- Unit类型用来标识过程,过程就是没有返回值的方法,Unit类似于Java里的void。 Unit只有一个实例()
```scala
— null 不能赋值给值类型
scala> val i: Int = null
:11: error: an expression of type Null is ineligible for implicit conversion val i: Int = null
scala> val str: String = null str: String = null
— 使用 Nothing 处理异常 val test = false val thing: Int = if (test) 42 else throw new Exception(“ERROR!”)
— Unit类型只有一个实例(),该实例没有实际意义 scala> val a = () a: Unit = ()
<a name="6bb76f61"></a>
## 第 6 节 值与变量&自动类型推断
- Scala当中的声明变量可以使用以下两种方式:
- val,值 -- value,用val定义的变量,值是不可变的
- var,变量 -- variable,用var定义的变量,值是可变的
- **在Scala中,鼓励使用val。** 大多数程序并不需要那么多的var变量。声明变量时,可以不指定变量的数据类型,编译器会根据赋值内容自动推断当前变量的数据类型。**备注:简单数据类型可以省略,对于复杂的数据类型建议明确声明;**
- 声明变量时,可以将多个变量放在一起声明。
```scala
-- val定义的变量不可更改,变量的类型编译器可以进行自动类型推断
val name = "zhangsan"
-- 必要时可以指定数据类型
var name: String = null
-- 可以将多个值或变量放在一起声明
val x, y = 100;
var name, message: String = null
第 7 节 操作符
- Scala的算术操作符、位操作符与 Java中的效果一样的。
需要特别注意一点:Scala中的操作符都是方法
a + b 等价 a.+(b) 1 to 10 等价 1.to(10)
书写时推荐使用:a + b 、1 to 10这种代码风格。
Scala 没有提供 ++、— 操作符,但是可以使用+=、-=
第 8 节 块表达式和赋值语句
{} 块包含一系列表达式,其结果也是一个表达式,块中最后一个表达式的值就是块的值。
- 赋值语句返回Unit类型,代表没有值; ```scala val x1 = 1 val y1 = 1 val x2 = 0 val y2 = 0
val distance = { val dx = x1 - x2 val dy = y1 - y2 math.sqrt(dxdx + dydy) } — 赋值语句的值是Unit类型,不要把它们串接在一起。x的值是什么? var y = 0 val x = y = 1
<a name="8f761d92"></a>
## 第 9 节 输入和输出
- 通过readLine 从控制台读取一行输入。
- 如果要读取数字、Boolean或者字符,可以用readInt、readDouble、readByte、readShort、readLong、readFloat、readBoolean或者readChar。
- print、println、printf 可以将结果输出到屏幕;
<a name="NepEt"></a>
## 第 10 节 字符串插值器
- Scala 提供了三种字符串插值器:
- s 插值器,对内嵌的每个表达式求值,对求值结果调用toString,替换掉字面量中的那些表达式
- f 插值器,它除s插值器的功能外,还能进行格式化输出,在变量后用%指定输出格式,使用java.util.Formatter中给出的语法
- raw 插值器,按照字符串原样进行输出
```scala
-- s插值器
val subject = "Spark"
val str1 = s"Hello, $subject"
println(str1)
val arr = (1 to 10).toArray
val str2 = s"arr.length = ${arr.length}"
println(str2)
println(s"The answer is ${6*6}")
-- f插值器
val year=2020
val month=6
val day=9
println(s"$year-$month-$day")
-- yyyy-MM-dd,不足2位用0填充
println(f"$year-$month%02d-$day%02d")
-- raw插值器
println("a\nb\tc")
println(raw"a\nb\tc")
println("""a\nb\tc""")
第 11 节 对象相等性
- Java 中可以 == 来比较基本类型和引用类型:
- 对基本类型而言,比较的是值的相等性
- 对引用类型而言,比较的是引用相等性,即两个变量是否指向JVM堆上的同个对象
- Scala中,要比较两个基础类型的对象是否相等,可以使用 == 或 !=;
1 == 1
1 != 2
2 == 2
- == 或 != 可以比较同一类型的两个对象;
List( 1 , 2 , 3 ) == List( 1 , 2 , 3 )
List( 1 , 2 , 3 ) != Array( 4 , 5 , 6 )
== 或 != 还可以比较不同类型的两个对象:
Scala中 if 表达式有返回值。
- 如果if 和 else 的返回值类型不一样,那么就返回两个返回值类型公共的父类。 ```scala — if 语句有返回值 val x = 10 val s = if (x > 0) 1 else -1
— 多分支if 语句 val s = if (x==0) 0 else if (x > 1) 1 else 0
— 如果返回的类型不一致就返回公共的父类 val s = if (x > 0) “positive” else -1
— 缺省 else 语句;s1/s2的返回类型不同 val s1 = if (x > 0) 1 等价 val s1 = if (x > 0) 1 else () val s2 = if (x > 0) “positive” 等价 val s2 = if (x > 0) “positive” else ()
<a name="f6758b32"></a>
## 第 2 节 for 表达式
- Scala中,for循环语法结构:for (i <- 表达式 / 集合),让变量 i遍历<-右边的表达式/集合的所有值。
- Scala为for循环提供了很多的特性,这些特性被称之为 for守卫式 或 for推导式。
```scala
-- 基本结构。使用to实现左右两边闭合的访问区间
for (i <- 1 to 10) {
println(s"i = $i")
}
-- 基本结构。使用until实现左闭右开的访问区间
for (i <- 1 until 10) {
println(s"i = $i")
}
-- 双重循环。条件之间使用分号分隔
for (i <- 1 until 5; j <- 2 until 5){
println(i * j )
}
-- 使用变量
for (i <- 1 to 3 ;j = 4-i){
println(i * j )
}
-- 守卫语句。增加 if 条件语句
for (i <- 1 to 10; j <- 1 to 10 if i==j){
println(s"i * j = $i * $j = ${i * j}")
}
-- 使用 yield 接收返回的结果,这种形式被称为for推导式
val result = for (i <- 1 to 10) yield i
-- 使用大括号将生成器、守卫、定义包含在其中;并以换行的方式来隔开它们
for { i <- 1 to 3
from = 4 - i
j <- from to 3
}
println(s"i = $i; j = $j")
第 3 节 while 表达式
- Scala提供了与 Java 类似的while和do…while循环。while语句的本身没有任何返回值类型,即while语句的返回结果是Unit类型的 () 。
- Scala内置控制结构特地去掉了 break 和 continue。
- 特殊情况下如果需要终止循环,可以有以下三种方式:
- 使用Boolean类型的控制变量
- 使用return关键字
- 使用breakable和break,需要导入scala.util.control.Breaks包 ```scala // while循环 var flag = true var result = 0 var n = 0 while (flag) { result += n n += 1 println(“res = “ + result) println(“n = “ + n) if (n == 10) { flag = false } }
// for循环 var flag = true var res = 0 for (i <- 0 until 10 if flag) { res += i println(“res = “ + res ) if (i == 5) flag = false }
/**
* 1 + 2 + 3 + 4
*
- @return */ def addInner() { for (i <- 0 until 10) { if (i == 5) { return } res += i println(“res = “ + res) } }
def main(args: Array[String]): Unit = { addInner() }
def main(args: Array[String]): Unit = { // 需要导包 import scala.util.control.Breaks._ var res = 0 breakable { for (i <- 0 until 10) { if (i == 5) { break } res += i } } println(“res = “ + res) }
<a name="gJ5Ds"></a>
## 第 4 节 函数
![image.png](https://cdn.nlark.com/yuque/0/2021/png/3013578/1611047355203-02428b75-6cae-4479-aea3-582e6326a825.png#align=left&display=inline&height=446&margin=%5Bobject%20Object%5D&name=image.png&originHeight=892&originWidth=1742&size=639298&status=done&style=none&width=871)
- 函数体中最后一句为返回值的话,可以将return 去掉;如果一个函数体只有一句代码,大括号可以去掉;
- 如果一个函数没有返回值,其返回类型为Unit , 并且 “=” 号可以去掉,这样的函数被称为过程;
- 可以不声明函数的返回类型,返回类型可通过自动类型推断来完成,但递归函数的返回类型必须声明;
- **备注:建议明确声明函数的返回值,即使为Unit**
```scala
-- 定义函数
def add(x: Int, y: Int): Int = {
x + y
}
-- 递归函数必须声明返回类型
def fibonacci(n: Int): Long = {
if(n > 0){
if(n==1 || n==2)
1
else
fibonacci(n - 1) + fibonacci(n - 2)
}
}
-- 参数的默认值
def add(x: Int, y: Int=10): Int = {
x + y
}
add(1)
add(1, 20)
-- 带名参数
def add(x: Int=1, y: Int=10, z: Int): Int = {
x + y + z
}
-- 现在要求 x=1, y=10, =100
add(1, 10, 100)
add(z=100)
-- 变长参数。x的数据类型可以简单的认为是Array[Int]
def add(x: Int*): Int = {
x.sum
}
// 告诉编译器这个参数被当做参数序列处理。使用 parameter: _* 的形式
val arr = (1 to 10).toArray
add(a:_*)
// 变长参数只能出现在参数列表的尾部,只能有一个
// Error: *-parameter must come last
def add1(x: Int*, y: String) = {
... ...
}
第 5 节 懒值
- 当 val 被声明为lazy时 (var不能声明为lazy),它的初始化将被推迟,直到首次对此取值,适用于初始化开销较大的场景。 ```scala // 语句立刻执行,发现文件不存在,报错 val file1 = scala.io.Source.fromFile(“src/test111.scala”) // 文件不存在时,不会报错。因为语句此时并没有被执行 lazy val file2 = scala.io.Source.fromFile(“src/test111.scala”) println(“OK!”)
file2.getLines().size // 先打印OK!,才报错
<a name="a58919dd"></a>
## 第 6 节 文件操作
- 导入scala.io.Source后,可引用Source中的方法读取文本文件的内容
```scala
import scala.io.{BufferedSource, Source}
object FileDemo {
def main(args: Array[String]): Unit = {
//注意文件的编码格式,如果编码格式不对,那么读取报错
val file: BufferedSource = Source.fromFile("... ...", "GBK");
val lines: Iterator[String] = file.getLines()
for (line <- lines) {
println(line)
}
//注意关闭文件
file.close()
}
}
- 如果要将文件内容转数组,直接调用toArray。
- 读取网络资源 ```scala import scala.io.{BufferedSource, Source}
object FileDemo2 { def main(args: Array[String]): Unit = { val source: BufferedSource = Source.fromURL(“http://www.baidu.com“) val string: String = source.mkString println(string) source.close() } }
- Scala没有内建的对写入文件的支持。要写入文本文件,可使用 java.io.PrintWriter
```scala
import java.io.PrintWriter
object FileDemo3 {
def main(args: Array[String]): Unit = {
val writer = new PrintWriter("../a.txt")
for(i <- 1 to 100){
writer.println(i)
writer.flush()
}
writer.close()
}
}
第三部分 数组和元组
第 1 节 数组定义
- 数组几乎是所有语言中最基础的数据结构。数组可索引、类型一致、长度不变。 ```scala — 长度为10的整型数组,初始值为0 val nums = new ArrayInt
— 使用()访问数据元素;下标从0开始 nums(9) = 10
— 长度为10的字符串数组,初始值为null val strs = new ArrayString
— 省略new关键字,定义数组,scala进行自动类型推断 val arrays = Array(1, 2, 3)
— 快速定义数组,用于测试 val numsTest = (1 to 100).toArray
<a name="b0e09f5e"></a>
## 第 2 节 变长数组
- 长度按需要变换的数组ArrayBuffer。Scala 中很多数组类型都有可变、不可变两个版本,推荐使用不可变的数组类型,使用可变数组类型时需要显示声明;
- **使用ArrayBuffer时,需要导包 import scala.collection.mutable.ArrayBuffer;**
```scala
import scala.collection.mutable.ArrayBuffer
object VarArrayDemo {
def main(args: Array[String]){
// 定义一个空的可变长Int型数组。注意:后面要有小括号
val nums = ArrayBuffer[Int]()
// 在尾端添加元素
nums += 1
// 在尾端添加多个元素
nums += (2,3,4,5)
// 使用++=在尾端添加任何集合
nums ++= Array(6,7,8)
// 这些操作符,有相应的 -= ,--=可以做数组的删减,用法同+=,++=
// 使用append追加一个或者多个元素
nums.append(1)
nums.append(2,3)
// 在下标2之前插入元素
nums.insert(2,20)
nums.insert(2,30,30)
// 移除最后2元素
nums.trimEnd(2)
// 移除最开始的一个或者多个元素
nums.trimStart(1)
// 从下标2处移除一个或者多个元素
nums.remove(2)
nums.remove(2,2)
}
}
第 3 节 数组操作
数组转换
// Array <==> BufferArray定长数组与变长数组转换 //toArray,变长数组转换为定长数组 val array: Array[Int]=nums.toArray //toBuffer,定长数组转换为变长数组 val arrayBuffer: mutable.Buffer[Int]=array.toBuffer
数组遍历
// 使用until,基于下标访问使用增强for循环进行数组遍历 for (i <- 0 until nums.length) println(nums(i)) // 使用to,基于下标访问使用增强for循环进行数组遍历 for (i <- 0 to nums.length-1) println(nums(i)) // 使用增强for循环遍历数组元素 for (elem <- nums) println(elem)
第 4 节 常见算法
在Scala中对数组进行转换非常简单方便,这些转换动作不会修改原始数组,而是产生一个全新的数组。
- 任务:将数组中偶数元素加倍,奇数元素丢弃 ```scala val arr = (1 to 10).toArray
— 使用for推导式。注意:原来的数组并没有改变 val result1 = for (elem <- arr) yield if (elem % 2 == 0) elem 2 else 0 val result2 = for (elem <- arr if elem % 2 == 0) yield elem 2
— scala中的高阶函数 arr.filter(%2==0).map(*2)
```scala
// 取第一个元素
a1.head
// 取最后一个元素
a1.last
// 除了第一个元素,剩下的其他元素
a1.tail
// 除了最后一个元素,剩下其他元素
a1.init
// 数组常用算法
Array(1,2,3,4,5,6,7,8,9,10).sum //求和
Array(2,3,4).product //元素相乘
Array(1,2,3,4,5,6,7,8,9,10).max //求最大值
Array(1,2,3,4,5,6,7,8,9,10).min //求最小值
Array(1,3,2,7,6,4,8,9,10).sorted //升序排列
// max、min、sorted方法,要求数组元素支持比较操作
Array(1,2,3,4,5,4,3,2,1).map(_*2)
Array(1,2,3,4,5,4,3,2,1).reduce(_+_)
Array(1,2,3,4,5,4,3,2,1).distinct //数据去重
Array(1,2,3,4,5,4,3,2,1).length
Array(1,2,3,4,5,4,3,2,1).size
Array(1,2,3,4,5,4,3,2,1).indices //数据索引
// count计数,需要注意的是count中必须写条件
Array(1,2,3,4,5,4,3,2,1).count(_>3)
Array(1,2,3,4,5,4,3,2,1).count(_%2==0)
// filter 过滤数据,原始数组中的数据保持不变,返回一个新的数组
Array(1,2,3,4,5,4,3,2,1).filter(_>3)
Array(1,2,3,4,5,4,3,2,1).filterNot(_%2==0)
// 在REPL环境中输入数组名称即可打印数组元素,非常方便
// 在IDEA中,print(a) / print(a.toString)都不能打印数组元素
// 使用mkString / toBuffer 是打印数组元素简单高效的方法
Array(10,9,8,7,6,5,4,3,2,1).toString
Array(10,9,8,7,6,5,4,3,2,1).mkString(" & ")
Array(10,9,8,7,6,5,4,3,2,1).mkString("<", " & ", ">")
Array(10,9,8,7,6,5,4,3,2,1).toBuffer
// take取前4个元素;takeRight取后4个元素
//原始数组中的数据保持不变,返回一个新的数组
Array(1,2,3,4,5,6,7,8,9,10).take(4)
Array(1,2,3,4,5,6,7,8,9,10).takeRight(4)
// takeWhile 从左向右提取列表的元素,直到条件不成立(条件不成立时终止)
Array(1,2,3,4,5,6,1,2,3,4).takeWhile(_<5)
// drop 删除前4个元素;dropRight删除后4个元素;
// dropWhile删除元素,直到条件不成立
Array(1,2,3,4,5,6,7,8,9,10).drop(4)
Array(1,2,3,4,5,6,7,8,9,10).dropRight(4)
Array(1,2,3,4,5,6,1,2,3,4).dropWhile(_<5)
// 将数组分为前n个,与剩下的部分
Array(1,2,3,4,5,6,7,8,9,10).splitAt(4)
// 数组切片。取下标第2到第4的元素(不包括第5个元素)
// 返回结果:Array(2, 3, 4)
Array(0,1,2,3,4,5,6,7,8,9,10).slice(2,5)
// 拉链操作;a1,a2的长度不一致时,截取相同的长度
val a1 = Array("A","B","C")
val a2 = Array(1,2,3,4)
val z1 = a1.zip(a2)
// 拉链操作;a1,a2的长度不一致时,a1用 * 填充,a2用 -1 填充
val z2 = a1.zipAll(a2, "*", -1)
val z3 = a1.zipAll(a2, -1, "*")
// 用数组索引号填充
val z4 = a1.zipWithIndex
// unzip 的逆操作,拆分成2个数组
val (l1,l2) = z4.unzip
// unzip3拆分成3个数组
val (l1,l2,l3) = Array((1, "one", '1'),(2, "two", '2'),(3, "three", '3')).unzip3
// 用于数组的操作符(:+、+:、++)
// :+ 方法用于在尾部追加元素;+: 方法用于在头部追加元素;
// 备注:冒号永远靠近集合类型,加号位置决定元素加在前还是后;
// ++ 该方法用于连接两个集合(数组、列表等),arr1 ++ arr2;
val a = (1 to 4).toArray
val b = (5 to 8).toArray
// 分别在集合头部、尾部增加元素;连接两个集合
val c = 10 +: a
val d = c :+ 9
val e = a ++ b
// 说明:上述的很多方法不仅仅对Array适用,一般情况下对其他集合类型同样适用。
val list = (1 to 10).toList
list.sum
list.max
list.take(4)
list.drop(4)
//数组排序
val nums = Array(1, 3, 2, 6, 4, 7, 8, 5)
println(nums.sorted.toBuffer) //升序
println(nums.sorted.reverse.toBuffer) //降序
println(nums.sortWith(_ > _).toBuffer) //降序
println(nums.sortWith(_ < _).toBuffer) //升序
第 5 节 多维数组
通过Array的ofDim方法来定义一个多维的数组,多少行,多少列,都是自己说了算。
//创建一个3行4列的二维数组 val dim = Array.ofDim[Double](3,4) dim(1)(1) = 11.11 for (i <- 0 to 2; j <- 0 to 3) { print(dim(i)(j) + " ") if (j == 3) println() }
第 6 节 元组及操作
Tuple,元组。Map是键值对的集合。对偶是元组的最简单形态;
- 元组是不同类型的值的集合,元组中的元素可以是不同的数据类型,元组在Scala中的应用非常广泛。 ```scala //报错,元组的元素个数上限是22个 val a = Tuple23(1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3)
// 定义元组 val a = (1, 1.2, “ad”, ‘d’) val b = Tuple4(1, 1.2, “ad”, ‘d’) println(a==b)
// Tuple的访问形式比较特殊。元组的下标从1开始 a._1 a _2 a._3 a _4
// 从元组接收数据 val (a1, a2, a3, a4), a5 = a val (b1, , b2, ), b5 = a
// 遍历元组,第一种方式 for(x <- a.productIterator){ println(x) }
// 遍历元组,第二种方式 a.productIterator.foreach(x => println(x))
<a name="cedfe34d"></a>
# 第四部分 类与对象
<a name="f36dc552"></a>
## 第 1 节 类和无参构造器
- 在Scala中,类并不用声明为public;
- Scala源文件中可以包含多个类,所有这些类都具有公有可见性;
- val修饰的变量(常量),值不能改变,只提供getter方法,没有setter方法;
- var修饰的变量,值可以改变,对外提供getter、setter方法;
- 如果没有定义构造器,类会有一个默认的无参构造器;
```scala
class Person {
// Scala中声明一个字段,必须显示的初始化,然后根据初始化的数据类型自动推断其类型,字段类型可以省略
var name = "jacky"
// _ 表示一个占位符,编译器会根据变量的数据类型赋予相应的初始值
// 使用占位符,变量类型必须指定
// _ 对应的默认值:整型默认值0;浮点型默认值0.0;String与引用类型,默认值null; Boolean默认值false
var nickName: String = _
var age=20
// 如果赋值为null,则一定要加数据类型,因为不加类型, 该字段的数据类型就是Null类型
// var address = null
// 改为: var address: String = null
// val修饰的变量不能使用占位符******************************************************
val num = 30
// 类私有字段,有私有的getter方法和setter方法,
// 在类的内部可以访问,其伴生对象也可以访问
private var hobby: String = "旅游"
// 对象私有字段,访问权限更加严格,只能在当前类中访问
private[this] val cardInfo = "123456"
//自定义方法
def hello(message: String): Unit = {
//只能在当前类中访问cardInfo
println(s"$message,$cardInfo")
}
//定义一个方法实现两数相加求和
def addNum(num1: Int, num2: Int): Int = {
num1 + num2
}
}
类的实例化以及使用:
object ClassDemo { def main(args: Array[String]): Unit = { //创建对象两种方式,这里都是使用的无参构造器来进行创建对象的 val person = new Person() //创建类的对象时,小括号()可以省略 val person1 = new Person //给类的属性赋值 person.age = 50 //注意:如果使用对象的属性加上 _= 给var修饰的属性进行重新赋值,其实就是调用age_=这个setter方法 person.age_=(20) //直接调用类的属性,其实就是调用getter方法 println(person.age) //调用类中的方法 person.hello("hello") val result = person.addNum(10, 20) println(result) } }
第 2 节 自定义getter和setter方法
对于 Scala 类中的每一个属性,编译后会有一个私有的字段和相应的getter、setter方法生成。
//getter方法 println(person age) //setter方法 person age_= (18) //getter方法 println(person.age)
可以不使用自动生成的方式,自己定义getter和setter方法 ```scala class Dog { private var leg = 0 //自定义getter方法 def leg = _leg //自定义setter方法 def leg=(newLeg: Int) { _leg = newLeg } }
// 使用自定义getter和setter方法 val dog = new Dog dog.leg_=(4) println(dog.leg)
- 自定义变量的getter和setter方法需要遵循以下原则:
- 字段属性名以“_”作为前缀,如: _leg
- getter方法定义为:def leg = _leg
- setter方法定义为:def leg_=(newLeg: Int)
<a name="fee56033"></a>
## 第 3 节 Bean属性
- JavaBean规范把Java属性定义为一堆getter和setter方法。
- 类似于Java,当将Scala字段标注为 @BeanProperty时,getFoo和setFoo方法会自动生成。
- 使用@BeanProperty并不会影响Scala自己自动生成的getter和setter方法。
- 在使用时需要导入包scala.beans.BeanProperty
```scala
import scala.beans.BeanProperty
class Teacher {
@BeanProperty
var name:String = _
}
object BeanDemo{
def main(args: Array[String]): Unit = {
val tea: Teacher = new Teacher
tea.name = "zhagnsan"
tea.setName("lisi") //BeanProperty生成的setName方法
println(tea.getName) //BeanProperty生成的getName方法
}
}
- 上述Teacher类中共生成了四个方法:
name: String
name_= (newValue: String): Unit
getName(): String
setName (newValue: String): Unit
第 4 节 构造器
- 如果没有定义构造器,Scala类中会有一个默认的无参构造器;
- Scala当中类的构造器分为两种:主构造器和辅助构造器;
- 主构造器的定义与类的定义交织在一起,将主构造器的参数直接放在类名之后。
- 当主构造器的参数不用var或val修饰时,参数会生成类的私有val成员。
Scala中,所有的辅助构造器都必须调用另外一个构造器,另外一个构造器可以是辅助构造器,也可以是主构造器。 ```scala //主构造器直接定义在类中,其代码不包含在任何方法中 //Scala中的主构造器与类名交织在一起,类名后面的参数即为主构造器的参数 class Dog(name: String, age: Int) { //类中不在任何方法中的代码,都属于主构造器的代码。 //创建类的对象时会去执行主构造器的代码。下面的println代码就是主构造器的一部分 println(name) println(age)
var gender: String = “”
def this(name: String, age: Int, gender: String) { //每个辅助构造器,都必须以其他辅助构造器,或者主构造器的调用作为第一句代码 this(name, age) this.gender = gender }
var color = “”
def this(name: String, age: Int, gender: String, color: String) { //调用上面的辅助构造器 this(name, age, gender) this.color = color } }
object Dog { def main(args: Array[String]): Unit = { val dog1=new Dog(“狗蛋”,4) val dog2=new Dog(“旺才”,3,”雄性”) val dog3=new Dog(“小六”,5,”雄性”,”黑色”) } }
<a name="b3d697c6"></a>
## 第 5 节 对象
<a name="2afe67a8"></a>
### 5.1 单例对象
- Scala并没有提供Java那样的静态方法或静态字段;
- 可以采用object关键字实现单例对象,具备和Java静态方法同样的功能;
- 使用object语法结构【object是Scala中的一个关键字】达到静态方法和静态字段的目的;对象本质上可以拥有类的所有特性,除了不能提供构造器参数;
- 对于任何在Java中用单例对象的地方,在Scala中都可以用object实现:
- 作为存放工具函数或常量的地方
- 高效地共享单个不可变实例
```scala
class Session {
def hello(first: Int): Int = {
println(first)
first
}
}
object SessionFactory {
val session = new Session
def getSession(): Session = {
session
}
def main(args: Array[String]): Unit = {
for (x <- 1 to 10) {
//通过直接调用,产生的对象都是单例的
val session = SessionFactory.getSession()
println(session)
}
}
}
- Scala中的单例对象具有如下特点:
- 创建单例对象不需要使用new关键字
- object中只有无参构造器
主构造代码块只能执行一次,因为它是单例的
object ObjectDemo { println("这是单例对象的代码!") def main(args: Array[String]): Unit = { val object1=ObjectDemo val object2=ObjectDemo } }
5.2 伴生类与伴生对象
- 当单例对象与某个类具有相同的名称时,它被称为这个类的“伴生对象”;
类和它的伴生对象必须存在于同一个文件中,而且可以相互访问私有成员(字段和方法);
class ClassObject { val id = 1 private var name = "lagou" def printName(): Unit ={ //在ClassObject类中可以访问伴生对象ClassObject的私有字段 println(ClassObject.CONSTANT + name ) } } object ClassObject{ //伴生对象中的私有字段 private val CONSTANT = "汪汪汪" def main(args: Array[String]) { val p = new ClassObject //访问伴生类的私有字段name p.name = "123" p.printName() } }
5.3 应用程序对象
每个Scala应用程序都必须从一个对象的main方法开始,这个方法的类型为 Array[String] => Unit;
- 备注:main方法写在class中是没有意义的,在IDEA中这样的 class 连run的图标都不能显示
除了main方法以外,也可以扩展App特质(trait)
object Hello extends App { if (args.length > 0) println(s"Hello World; args.length = ${args.length}") else println("Hello World") }
5.4 apply方法
object 中有一个非常重要的特殊方法 — apply方法;
- apply方法通常定义在伴生对象中 ,目的是通过伴生类的构造函数功能,来实现伴生对象的构造函数功能;
- 通常我们会在类的伴生对象中定义apply方法, 当遇到类名(参数1,…参数n)时apply方法会被调用 ;
- 在创建伴生对象或伴生类的对象时,通常不会使用new class/class() 的方式,而是直接使用class()隐式的调用伴生对象的 apply 方法 ,这样会让对象创建的更加简洁;
//class Student为伴生类 class Student(name: String, age: Int) { private var gender: String = _ def sayHi(): Unit ={ println(s"大家好,我是$name,$gender 生") } } //object Student是class class的伴生对象 object Student { //apply方法定义在伴生对象中 def apply(name: String, age: Int): Student = new Student(name, age) def main(args: Array[String]): Unit = { //直接利用类名进行对象的创建,这种方式实际上是调用伴生对象的apply方法实现的 val student=Student("jacky",30) student.gender="男" student.sayHi() } }
问题 :在Scala中实现工厂方法,让子类声明哪种对象应该被创建,保持对象创建在同一位置。例如,假设要创建Animal工厂,让其返回Cat和Dog类的实例,基于这个需求,通过实现Animal伴生对象的apply方法,工厂的使用者可以像这样创建新的Cat和Dog实例。 ```scala abstract class Animal { def speak }
class Dog extends Animal { override def speak: Unit = { println(“woof”) } }
class Cat extends Animal { override def speak: Unit = { println(“meow”) } }
object Animal { def apply(str: String): Animal = { if (str == “dog”) new Dog else new Cat } def main(args: Array[String]): Unit = { val cat = Animal(“cat”) cat.speak val dog = Animal(“dog”) dog.speak } } ```