Scala快速入门系列:声明变量、控制结构与函数、常用数组操作
Scala快速入门-1-声明变量
背景
- 因为Spark是由Scala开发的,所以在开发Spark应用程序之前要对Scala语言学习。虽然Spark也支持Java、Python语言,但是作为一名Java程序猿,还是决定要学习Scala哈。
- Scala是运行在JVM上一门语言。开发效率非常高、语法丰富简洁,三两行Scala代码能搞定Java要写的一大坨代码。
- Scala的语法糖太甜~~
Scala特性
面向对象特性
- Scala是一种纯面向对象的语言,每个值都是对象。
函数式编程
- Scala也是一种函数式语言,其函数也能当成值来使用。Scala提供了轻量级的语法用以定义匿名函数,支持高阶函数,允许嵌套多层函数,并支持柯里化。Scala的case class及其内置的模式匹配相当于函数式编程语言中常用的代数类型。
- 可以利用Scala的模式匹配,编写类似正则表达式的代码处理数据。
并发性
- Scala使用Actor作为其并发模型,Actor是类似线程的实体,通过“邮箱”发收消息。Actor可以复用线程,因此可以在程序中可以使用数百万个Actor,而线程只能创建数千个。在2.10之后的版本中,使用Akka作为其默认Actor实现。
主要内容
- 声明变量、常用类型
- 控制结构(条件、循环、for)、函数
- 常用数组操作
- 类与对象
- 继承与特质
- 集合
- 模式匹配和样例类
声明变量、常用类型
- scala代码会被编译成字节码,然后交给Java虚拟机执行。
- 不强制指定变量的类型,编译器会推断出来。
scala> 8 * 5
res0: Int = 40
- val定义的值无法改变它的内容。在Scala中,鼓励使用val。
scala> val answer = 8 * 5
answer: Int = 40
scala> answer = 10
<console>:8: error: reassignment to val
answer = 10
- 如果要声明其值可变的变量,用var。
scala> var counter = 0
counter: Int = 0
scala> counter = 10
counter: Int = 10
- 变量或函数的类型总是写在变量或函数名称后面,与Java的习惯不同。
scala> val greeting : String = "Hello"
greeting: String = Hello
- 不需要使用分号最后,仅当同一行代码中存在多条语句时才需要分号隔开。
- 常用的数据类型与Java一样,Byte、Char、Short、Int、Long、Float、Double及Boolean,这些都是类。
- 在基本类型和包装类型之间的转换是Scala编译器的工作。
- +-*/%等操作符实际上是方法。
- 对于BigInt和BigDecimal对象,可以以常规的方式使用数学操作符(但在Java中同样的操作要写成x.multiply(x))。
scala> val x : BigInt = 123
x: BigInt = 123
scala> x * x
res1: scala.math.BigInt = 15129
- 不带参数的Scala方法通常不使用圆括号,一般没有参数且不改变当前对象的方法不带圆括号。
Scala快速入门-2-控制结构与函数
背景
- 表达式有值,语句执行动作。
- Scala中,几乎所有构造出来的语法结构都有值,不像Java中把表达式和语句(if语句)分为两类。
- 在这里if表示式有值。
- 代码块也有值,最后一个表达式就是值。
- 语句中,分号不是必需的。
- 函数式中不使用return。
条件 表达式
- 在Scala中if/else 表达式 有值,这个值就是在if或else之后的表达式的值。
scala> var x = 10
x: Int = 10
scala> val r = if (x > 0) 1 else -1
r: Int = 1
scala> var x = 0
x: Int = 0
scala> val r = if (x > 0) 1 else -1
r: Int = -1
- 可能if没有输出值,但在Scala中,每个表达式都有某种值。
scala> var x = 0
x: Int = 0
scala> val r = if (x > 0) 1
r: AnyVal = ()
块表达式和赋值
- 在Scala中{}块包含一系列表达式,其结果也是一个表达式。块中最后一个表达式的值就是块的值。
- 对于某个val的初始化需要分多步完成的情况很实用。
val dis = {val dx = x - x0; val dy = y - y0; sqrt(dx * dx + dy * dy)}
循环
- while与Java中的循环一样。
while(n > 0) {
r = r * n
n -= 1
- Scala没有for(初始化; 检查变量是否满足; 更新变量)的结构。
for(i <- 1 to n) {
r = r * i
- 1 to n 表达式表示:返回数据1到n(包含)的区间。
- 1 until n 表达式表示:返回数据1到n(不包含)的区间。
增强for循环和for推导式
- 可以以 变量<-表达式的形式提供多个生成器,用分号将他们隔开
scala> for(i <- 1 to 3; j <- 1 to 3) print ((10 * i + j) + " ")
11 12 13 21 22 23 31 32 33
- 每个生成器都可以带一个守卫,以if开头的Boolean表达式 (if前并没有分号)
scala> for(i <- 1 to 3; j <- 1 to 3 if i != j) print((10 * i + j) + " ")
12 13 21 23 31 32
- for推导式:for循环的循环以yield开始,则该循环会构造出一个集合,每次迭代生成集合中的一个值
scala> for(i <- 1 to 10) yield i % 3
res2: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 0, 1, 2, 0, 1, 2, 0, 1)
函数
- 函数定义:需要给出函数名、参数和函数体,格式如下
def abs(x: Double) = if (x >= 0) x else -x
- 必须给出所有参数的类型
- 递归函数必须指定返回值类型
def fac(n: Int) : Int = if(n <= 0) 1 else n * fac(n - 1)
-
不需要
return
语句 -
有
=
等号连接函数体 - 默认参数和带名参数
scala> def decorate(str: String, left: String = "[", right: String = "]") = left + str + right
decorate: (str: String, left: String, right: String)String
scala> decorate("Hello World")
res3: String = [Hello World]
scala> decorate("Hello World", "<", ">")
res4: String = <Hello World>
- 也可以在提供参数值时指定参数名,这样就可与函数定义参数列表的顺序不一致
scala> decorate(left = "<<", str = "Hello Scala", right = ">>")
res5: String = <<Hello Scala>>
- 可以混用未命名参数和带名参数,只要未命名的参数排在前面即可
scala> decorate("Hello Spark", right = "]<<")
res6: String = [Hello Spark]<<
scala> decorate("Hello Spark", "[", "]<<")
- 实现一个可以接受可变长参数列表的函数
scala> def sum(args: Int*) = {
| var result = 0
| for (arg <- args) result += arg
| result
sum: (args: Int*)Int
scala> val s = sum(1, 3, 5, 7)
s: Int = 16
-
可以使用
_*
将一个整数区间转换成参数序列
直接使用会抛出如下错误:
scala> val ss = sum(1 to 5)
<console>:8: error: type mismatch;
found : scala.collection.immutable.Range.Inclusive
required: Int
val ss = sum(1 to 5)
scala> val ss = sum(1 to 5: _*)
ss: Int = 15
-
如果函数体包含在花括号当中,但没有前面的
=
号,返回类型是Unit,这样的函数被称做过程。过程不返回值,调用它仅仅是为了它的副作用。 - 当val被声明为lazy时,它的始始化将被推迟,直到首次对它取值。
lazy val words = scala.io.Source.fromFile("/usr/share/dict/words").mkString
可以故意把文件名写错,试一下在初始化语句被执行的时候会不会报错(只有访问words时才提示文件未找到)
Scala快速入门-3-常用数组操作
知识点
- 长度固定使用Array,长度有变化使用ArrayBuffer
- 提供初始值时不要使用new
- 用()来访问元素
- for(elem <- arr)遍历元素
- for(elem <- arr if …) yield …将原数组转为新数组
定长数组
- 10个整数的数组,所有元素初始为0
scala> val nums = new Array[Int](10)
nums: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
- 10个元素的字符中数组,所有元素初始化为null
scala> val str = new Array[String](10)
str: Array[String] = Array(null, null, null, null, null, null, null, null, null, null)
- 提供初始值就不需要new,长度为2的Array[String],类型是推断出来的
scala> val str1 = Array("Hello", "Scala")
str1: Array[String] = Array(Hello, Scala)
- 使用()来访问元素
scala> val s = str1(0)
s: String = Hello
变长数组
- 与Java中ArrayList功能等效的数据结构ArrayBuffer
- 初始化一个空的可变长数组,准备存入整数
scala> import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.ArrayBuffer
scala> val b = ArrayBuffer[Int]()
b: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer()
- 用+=在尾添加元素或多个用括号包都来的元素
scala> b += 1
res0: b.type = ArrayBuffer(1)
scala> b += (1, 2, 3)
res1: b.type = ArrayBuffer(1, 1, 2, 3)
- 用++=操作符追加任何集合
scala> b ++= Array(6, 8, 9)
res2: b.type = ArrayBuffer(1, 1, 2, 3, 6, 8, 9)
- 移除最后2个元素
scala> b.trimEnd(2)
scala> b
res4: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 1, 2, 3, 6)
- 可在任意位置插入或移除元素(不高效,所有在那个位置后面的元素都必须被平移)
// 在下标2之前插入
scala> b.insert(2, 4)
scala> b
res6: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 1, 4, 2, 3, 6)
// 在下标2之前插入多个元素
scala> b.insert(2, 4, 5)
scala> b
res8: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 1, 4, 5, 4, 2, 3, 6)
- 定长数组与变长数据转换
// 转成定长数组
scala> b.toArray
res9: Array[Int] = Array(1, 1, 4, 5, 4, 2, 3, 6)
// 转成变长数组
scala> b.toBuffer
res10: scala.collection.mutable.Buffer[Int] = ArrayBuffer(1, 1, 4, 5, 4, 2, 3, 6)
遍历数组
// 使用下标访问
scala> for (i <- 0 until b.length)
| println(i + ":" + b(i))
// 不使用下标
scala> for(elem <- b)
| println(elem)
6
数组转换
- for推导式,从一个数组转换,生成一个全新的数组
scala> val a = Array(2, 3, 5, 7)
a: Array[Int] = Array(2, 3, 5, 7)
scala> val res = for(elem <- a) yield 2 * elem
res: Array[Int] = Array(4, 6, 10, 14)
scala> a