Scala编程
第一部分 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具备类型系统,通过编译时检查,保证代码的安全性和一致性。 - 并发性
Scala使用Actor作为其并发模型,Actor是类似线程的实体。
Actor可以复用线程,因此可以在程序中使用数百万个Actor,而线程只能创建数千个。
1.3 为什么要学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.ht
ml
下载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("HelloWorld");
}
}
2.3 Scala的REPL
在命令行输入Scala可启动Scala REPL。
REPL 是一个交互式解析器环境,R(read)、E(evaluate) 、P(print)、L(loop)
输入值,交互式解析器会读取输入内容并对它求值,再打印结果,并重复此过程。
两种方式打开:
1.cmd窗口输入Scala打开
2.安装bin目录下,双击scala.bat打开
第3节 基础语法
基础语法规则:
- 区分大小写 - Scala语言对大小写敏感;
- 类名 - 对于所有的类名的第一个字母要大写。如果需要使用几个单词来构成一个类名,每个单词的
第一个字母要大写;比如:ListDemo - 方法名 - 所有方法名的第一个字母用小写。如果需要使用几个单词来构成方法名,除第一个单词外
每个词的第一个字母应大写;比如:getResult - 程序文件名 - Scala程序文件的后缀名是 .scala,程序文件的名称可以不与对象名称完全匹配。这
点与Java有所区别。
备注:建议遵循 Java 的惯例,程序文件名称与对象名称匹配; - main()方法 - Scala程序从main()方法开始处理,这是每一个Scala程序的入口点。main()定义在
object中;
标识符。所有Scala组件都需要名称,用于对象、类、变量和方法的名称称为标识符。
关键字不能用作标识符,标识符区分大小写;
标识符以字母或下划线开头,后面可以有更多的字母、数字或下划线;
$字符是Scala中的保留关键字,不能在标识符中使用;
注释。Scala使用了与Java相同的单行和多行注释;
换行符。Scala语句可以用分号作为一行的结束,语句末尾的分号通常可以省略,但是如果一行里有多
个语句那么分号是必须的。
小结:
Scala的基础语法与Java比较类似,但是仍然有三点不一样的地方:
1、在Scala中换行符是可以省略的
2、Scala中main方法定义在object中
3、Scala中程序文件名可以不与对象名称相匹配,但是建议仍然遵循Java的规范,二者最好匹配
第4节 常用类型与字面量
Scala和Java一样,有8种数值类型 Byte、Short、Int、Long、Float、Double、Char、Boolean 类型;
和 Java 不同的是 ,这些类型都是类,有自己的属性和方法。
Scala并不刻意的区分基本类型和引用类型。
String 直接引用 Java.lang.String 中的类型,String在需要时能隐式转换为StringOps,因此不需要任何
额外的转换,String就可以使用StringOps中的方法。
每一种数据类型都有对应的Rich类型,如RichInt、RichChar等,为基本类型提供了更多的有用操作。
-- StringOps。 //toInt等方法都定义在StringLike中;StringOps实现了StringLike
"11".toInt
1.max(10)
1.min(10)
1.to(10)
1.until(10)
整数字面量。整数字面量有两种形式,十进制与十六进制(0X/0x开头)
-- 十六进制整数字面量
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
浮点数字面量
-- 十进制数、可选的小数点、可选的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> val a = 'A'
a: Char = A
-- 用字符的Unicode码来表示。Unicode码前128个字符就是ASCII码
scala> val b = '\u0042'
b: Char = B
-- 转义字符
scala> val mark = '\'
<console>:1: error: unclosed character literal
val mark = '\'
^
scala> val mark = '\\'
mark: Char = \
字符串字面量
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只有一个实
例()。
-- null 不能赋值给值类型
scala> val i: Int = null
<console>: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!")
java.lang.Exception: ERROR!
... 32 elided
-- Unit类型只有一个实例(),该实例没有实际意义
scala> val a = ()
a: Unit = ()
第6节 值与变量&自动类型推断
Scala当中的声明变量可以使用以下两种方式:
- val,值 – value,用val定义的变量,值是不可变的
- var,变量 – variable,用var定义的变量,值是可变的
在Scala中,鼓励使用val。大多数程序并不需要那么多的var变量。
声明变量时,可以不指定变量的数据类型,编译器会根据赋值内容自动推断当前变量的数据类型。
备注:简单数据类型可以省略,对于复杂的数据类型建议明确声明;
声明变量时,可以将多个变量放在一起声明。
-- 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类型,代表没有值;
object BlockDemo {
def main(args: Array[String]): Unit = {
val x1=1
val y1=2
val x2=3
val y2=4
val distance={
val dx=x2-x1
val dy=y2-y1
math.sqrt(dx*dx+dy*dy)
}
println(distance)
//赋值语句的值是Unit类型,不要把它们串接在一起。x的值是什么?
var y = 0
val x = y = 1
println(x)
}
}
第9节 输入和输出
通过readLine 从控制台读取一行输入。
如果要读取数字、Boolean或者字符,可以用readInt、readDouble、readByte、readShort、
readLong、readFloat、readBoolean或者readChar。
print、println、printf 可以将结果输出到屏幕;
object ReadLineAndPrintDemo {
def main(args: Array[String]): Unit = {
print("请输入您的姓名:")
val name=scala.io.StdIn.readLine()
print("请输入您的年龄:")
val age=scala.io.StdIn.readInt()
println("您的姓名是:"+name+",年龄:"+age)
//printf是一个带有C语言风格的格式化字符串函数
printf("您的姓名是:%s,年龄:%d",name,age)
println()
println(s"您的姓名:$name,年龄:$age")
}
}
第10节 字符串插值器
Scala 提供了三种字符串插值器:
- s 插值器,对内嵌的每个表达式求值,对求值结果调用toString,替换掉字面量中的那些表达式
- f 插值器,它除s插值器的功能外,还能进行格式化输出,在变量后用%指定输出格式,使用
java.util.Formatter中给出的语法 - raw 插值器,按照字符串原样进行输出
/**
* Scala中的插值器
*/
object InterpolatorDemo {
def main(args: Array[String]): Unit = {
//s插值器,可以通过$获取变量和表达式的值
val subject = "Scala"
val message = s"Hello,$subject"
println(message)
val array: Array[Int] = (1 to 10).toArray
val str = s"array.length=${array.length}"
println(str)
println(s"${10 * 9}")
//f插值器,用%指定输出格式
val year=2020
val month=8
val day=9
println(s"$year-$month-$day")
//以yyyy-MM-dd的方式显示,不足2位用0填充
println(f"$year-$month%02d-$day%02d")
//raw插值器,将字符串按原样输出
println("a\n\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)
== 或 != 还可以比较不同类型的两个对象:
2 == 2.0
List(1,2,3) == "Scala"
object ObjectCompareDemo {
def main(args: Array[String]): Unit = {
println(1==1)
println(1!=2)
println(1==2)
val flag=List(1,2,3)==List(4,5,6)
println(flag)
println(List(1,2,3)!=Array(1,2,3))
//比较不同类型的对象
println(2==2.0)
println(List(1,2,3)=="Scala")
}
}
第二部分 控制结构和函数
第1节 if 表达式
Scala中 if 表达式有返回值。
如果if 和 else 的返回值类型不一样,那么就返回两个返回值类型公共的父类。
package lagou.cn.part02
object IfDemo {
def main(args: Array[String]): Unit = {
//在Scala中不需要添加分号作为语句块的结束符
val num = 20
//在Scala中 if else语句是有返回值的,返回值就是最后一条语句的返回值
if (num > 20) "zhangsan" else "jacky"
//if语句可以嵌套
if (num < 20)
0
else if (num == 20)
1
else
-1
//因为if else语句是有返回值的,所以可以直接将if else语句赋值给一个变量
//注意:返回值不需加return关键字
val result = if (num > 20) "zhangsan" else "jacky"
println("result:"+result)
//如果if else语句中返回值的类型不一样,那么Scala会自动推断出两者的公共父类型,作为表达式的返回值类开型
val result2: Any = if (num == 20) "jacky" else 100
println("result2:"+result2)
//如果if else语句中缺省了else语句块,那么默认else的值是Unit
//Unit用“()”来表示,类似于Java中的void
val result3 = if (num > 20) "jacky"
println("result3:"+result3)
val result4 = if (num > 20) "jacky" else ()
println("result4:"+result4)
}
}
第2节 for 表达式
Scala中,for循环语法结构:for (i <- 表达式 / 集合),让变量 i遍历<-右边的表达式/集合的所有值。
Scala为for循环提供了很多的特性,这些特性被称之为 for守卫式 或 for推导式。
package lagou.cn.part02
/**
* Scala拥有与Java相同的While和do While循环
* 但是没有与Java for循环相对应的语法结构
* Scala中的for:for(i <- 表达式或集合),让循环变量i遍历<-右达表达式或集合的所有值
* 注意:循环变量i前面没有用val 或 var来修饰,这个循环变量的类型是表达式或集合的元素类型
* 循环变量的作用域一直持续到循环结束
*/
object ForDemo {
def main(args: Array[String]): Unit = {
//for基本结构,使用to实现左右两边闭合的访问区间[1,10]
for (i <- 1 to 10) {
println(s"i = $i")
}
//for基本结构,使用until实现左闭右开的访问区间[1,10)
for (i <- 1 until (10)) {
println(s"i = $i")
}
//双重循环,相当于Java中的嵌套循环,条件之间用分号分隔
println("=============双重循环=================")
for (i <- 1 to 3; j <- 1 to 5) {
println(i * j)
}
println("=============循环中使用变量=================")
for (i <- 1 to 3; j = 4 - i) {
println(i * j)
}
println("=============守卫式,增加了if条件语句=================")
//注意:if前面没有分号
for (i <- 1 to 10; j <- 1 to 10 if i == j) {
println(s"i * j = $i * $j =${i * j}")
}
println("=============推导式,使用yield接收返回结果=================")
//如果for循环的循环体以yield开始,那么此循环会构造出一个集合,每次迭代生成集合中的一个值。
//可以使用变量接收产生的新集合
val result = for (i <- 1 to 5) yield i % 2
result.foreach(println(_))
println("=============九九乘法表=================")
for (i <- 1 to 9; j <- 1 to i) {
print(s"$j * $i = ${i * j}\t")
if (i == j) println()
}
println("=============for循环中使用大括号=================")
for {
i <- 1 to 3
from = 4 - i
j <- from to 3
}
println(s"i=$i,j=$j")
println("=============遍历字符串=================")
val message="sparkscala"
for(elem <- message) print(elem+" ")
}
}
第3节 while 表达式
Scala提供了与 Java 类似的while和do…while循环。while语句的本身没有任何返回值类型,即while语
句的返回结果是Unit类型的 () 。
Scala内置控制结构特地去掉了 break 和 continue。
特殊情况下如果需要终止循环,可以有以下三种方式:
- 使用Boolean类型的控制变量
- 使用return关键字
- 使用breakable和break,需要导入scala.util.control.Breaks包
package lagou.cn.part02
object WhileDemo {
def main(args: Array[String]): Unit = {
var num = 1
while (num < 10) {
println(s"num = $num")
num += 1
}
// do {
// println(s"num = $num")
// num += 1
// } while (num < 10)
println("=============使用Boolean类型的控制变量,终止循环=================")
//使用Boolean类型的控制变量,终止循环
var m = 1
var flag = true
while (flag) {
println(s"m = $m")
m += 1
if(m==5) flag=false
}
println("=============使用return关键字终止循环=================")
//使用return关键字终止循环
for (i <- 1 to 10) {
if (i == 5) return
println(s"i=$i")
}
println("=============使用breakable和break终止循环=================")
//使用breakable和break终止循环,需要导入scala.util.control.Breaks包
import scala.util.control.Breaks._
var res = 0
breakable {
for (j <- 1 until (10)) {
if (j == 5) break()
res += j
}
}
println(res)
}
}
第4节 函数
函数体中最后一句为返回值的话,可以将return 去掉;如果一个函数体只有一句代码,大括号可以去掉;
如果一个函数没有返回值,其返回类型为Unit , 并且 “=” 号可以去掉,这样的函数被称为过程;
可以不声明函数的返回类型,返回类型可通过自动类型推断来完成,但递归函数的返回类型必须声明;
备注:建议明确声明函数的返回值,即使为Unit
package lagou.cn.part02
object FunctionDemo {
/**
* 定义函数的语法结构:def 函数名(参数列表):返回值类型={函数体}
* 函数体中最后一条语句的返回值作为整个函数的返回值,返回值不需要使用return关键字
* 也可以不声明函数的返回值类型,Scala会自动根据最后一条语句的返回值推断出函数的返回值类型
* 但是,如果是递归函数,其返回值类型必须声明
*
* @param num1
* @param num2
* @return
*/
def add(num1: Int, num2: Int) = {
num1 + num2
}
// 通过递归的函数来计算阶乘
def factorial(num: Int): Long = {
if (num <= 1)
1
else
num * factorial(num - 1)
}
// 通过递归函数实现一个斐波那契数列:1,1,2,3,5,8.....
def fibonacci(n: Int): Long = {
if (n == 1 || n == 2) {
1
} else {
fibonacci(n - 1) + fibonacci(n - 2)
}
}
//如果函数没有返回值,其返回值类型为Unit,类似于Java中的void,“=”也可以省略
//在Scala中,没有返回值的函数称为过程
def getSum(x: Int, y: Int) {
println(x + y)
}
//函数中的参数可以有默认值,称为默认参数
def add2(x: Int = 10, y: Int = 20): Long = {
x + y
}
//变长参数:参数类型右边加上*号
//变长参数只能出现在参数列表的尾部,并且只能有一个
//在Spark的源码中有大量的变长参数
def addSum(nums: Int*): Int = {
nums.sum
}
def main(args: Array[String]): Unit = {
println(add(1, 2))
println("计算阶乘:" + factorial(4))
println("斐波那契数列:" + fibonacci(6))
//调用函数时,使用函数中的参数的默认值
println(add2())
//调用函数时,给函数的参数重新赋值
println(add2(30, 40))
//在调用函数时,不按照函数定义的参数顺序来传递值,而是使用带名参数的方式来传值
println(add2(y = 60, x = 50))
println(addSum(1))
println(addSum(1, 2, 3))
println(addSum(1, 2, 3, 4, 5))
//使用 parameter:_*的形式,告诉编译器这个参数被当成参数序列处理
println(addSum(1 to 10: _*))
}
}
第5节 懒值
当 val 被声明为lazy时(var不能声明为lazy),它的初始化将被推迟,直到首次对此取值,适用于初始化
开销较大的场景。
package lagou.cn.part02
/**
* 在Scala中提供了lazy的特性
* 如果将一个变量声明为lazy,那么只有第一次使用这个变量时,变量对应的表达式才会发生计算。
* 这种特性对于特别耗时的计算操作特别有用,
* 比如:初始化开销较大的场景,对文件进行IO、进行网络IO的操作等
*/
object LazyDemo {
def main(args: Array[String]): Unit = {
//使用lazy关键字之后,即使文件不存在,也不会报错
//只有第一次使用变量时才会报错
lazy val file1=scala.io.Source.fromFile("src/test.scala")
println("OK!")
file1.getLines().size
}
}
第6节 文件操作
导入scala.io.Source后,可引用Source中的方法读取文本文件的内容
package lagou.cn.part02
import java.io.PrintWriter
import scala.io.{BufferedSource, Source}
/**
* I/O操作是一门编程语言的重要内容
* 相比于Java语言中的I/O类,Scala中的I/O的类数量较少,最常用的是Source这个类。
* 在Scala中,更多的是调用Java中的 I/O类或者通过对Java中的I/O类进行相应的封装来实现I/O操作。
*/
object FileDemo {
//读取文本文件
def readTextFile: Unit = {
val file = Source.fromFile("src\\text.txt")
val lines: Iterator[String] = file.getLines()
for (line <- lines) {
println(line)
}
//关闭文件
file.close()
}
//读取网络资源,需要在联网状态下
def readNetSource: Unit = {
val source: BufferedSource = Source.fromURL("http://www.baidu.com")
val message: String = source.mkString
println(message)
source.close()
}
//写入文本文件
//Scala中没有内建的对写入文件的支持,需要使用java.io.PrintWriter来实现
def writeTextFile: Unit = {
val writer = new PrintWriter("src\\text.txt")
for (i <- 1 to 10) {
//写入文件内容
writer.println(s"i = $i")
//刷新printwriter流的缓冲区
writer.flush()
}
//关闭写入流
writer.close()
}
def main(args: Array[String]): Unit = {
readTextFile
println("==================================")
readNetSource
println("==================================")
writeTextFile
}
}
第三部分 数组和元组
第1节 数组定义
数组几乎是所有语言中最基础的数据结构。数组可索引、类型一致、长度不变。
-- 长度为10的整型数组,初始值为0
val nums = new Array[Int](10)
-- 使用()访问数据元素;下标从0开始
nums(9) = 10
-- 长度为10的字符串数组,初始值为null
val strs = new Array[String](10)
-- 省略new关键字,定义数组,scala进行自动类型推断
val arrays = Array(1, 2, 3)
-- 快速定义数组,用于测试
val numsTest = (1 to 100).toArray
第2节 变长数组
长度按需要变换的数组ArrayBuffer。Scala 中很多数组类型都有可变、不可变两个版本,推荐使用不可
变的数组类型,使用可变数组类型时需要显示声明;
使用ArrayBuffer时,需要导包 import scala.collection.mutable.ArrayBuffer;
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)
数组案例
package lagou.cn.part03
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
/**
* 在Scala中,数组分为定长数组和变长数组。
* 定长数组,Array,长度是不变的
* 变长数组,ArrayBuffer,长度是可变的,它等效于Java中的ArrayList
* 使用变长数组之前需要导入包scala.collection.mutable.ArrayBuffer
*/
object ArrayDemo {
def main(args: Array[String]): Unit = {
println("===========定长数组================")
//定义长度为10的整型数组,初始值为0
val nums = new Array[Int](10)
//定义长度为10的字符串数组,初始值为null
val strs = new Array[String](10)
//访问数组元素,索引从0开始,使用()而不是[]来访问元素
println(strs(0))
//省略了关键字new,那么Scala会进行自动类型推断
val arrays: Array[Int] = Array(1, 2, 3, 4)
val arrays2: Array[Nothing] = Array()
println(arrays.length)
//通过toArray快速定义数组,用于测试
val numsList: Array[Int] = (1 to 10).toArray
println("===========变长数组================")
//定义一个空的Int的变长数组
//注意:后面要有小括号
val numsBuffer = ArrayBuffer[Int]()
//通过+=在尾端添加一个或多个元素
numsBuffer += 1
numsBuffer += (2, 3, 4, 5)
//通过++=在尾端添加集合
numsBuffer ++= Array(6, 7, 8)
// numsBuffer.foreach(println(_))
println(numsBuffer)
//还可通过-= --=对变长数组进行删减
numsBuffer -= 8
numsBuffer --= Array(6, 7)
println("============删减后的变长数组=============")
numsBuffer.foreach(println(_))
//使用append追加一个或多个元素
numsBuffer.append(1)
numsBuffer.append(2, 3, 4)
println("============追加后的变长数组=============")
numsBuffer.foreach(println(_))
//在某索引之前插入元素
numsBuffer.insert(2,10)
numsBuffer.insert(2,20,30)
println("============插入后的变长数组=============")
numsBuffer.foreach(println(_))
//移除元素
//trimEnd移除最后的N个元素
numsBuffer.trimEnd(3)
//trimStart移除去最开始的一个或多个元素
numsBuffer.trimStart(2)
println("============移除后的变长数组=============")
numsBuffer.foreach(println(_))
//通过remove从某索引处移除一个或多个元素
numsBuffer.remove(3)
numsBuffer.remove(2,3)
println("============remove后的变长数组=============")
numsBuffer.foreach(println(_))
//toArray,变长数组转换为定长数组
val numArray: Array[Int] = numsBuffer.toArray
//toBuffer,定长数组转换为变长数组
val arrayBuffer: mutable.Buffer[Int] = arrays.toBuffer
println("==================数组遍历=======================")
println("===============使用until进行数组遍历==============")
for(i <- 0 until(arrays.length)){
println(arrays(i))
}
println("===============使用to进行数组遍历==============")
for (i <- 0 to arrays.length-1){
println(arrays(i))
}
println("===============使用增强for循环的方式进行数组遍历==============")
for(elem <- arrays) println(elem)
println("===============使用foreach进行数组遍历==============")
arrays.foreach(println(_))
}
}
第4节 常见算法
在Scala中对数组进行转换非常简单方便,这些转换动作不会修改原始数组,而是产生一个全新的数组。
任务:将数组中偶数元素加倍,奇数元素丢弃
package lagou.cn.part03
object OperatorDemo {
def main(args: Array[String]): Unit = {
//将数组中的偶数加倍,奇数丢弃
val nums = (1 to 10).toArray
val result1 = for (elem <- nums if elem % 2 == 0) yield elem * 2
val result2 = for (elem <- nums) yield if (elem % 2 == 0) elem * 2 else 0
result1.foreach(println(_))
result2.foreach(println(_))
println("=============================")
//使用Scala中的高阶函数来实现
nums.filter(_ % 2 == 0).map(_ * 2).foreach(println(_))
println("===========首个元素、最后元素、求和、最大最小只、排序、相乘==================")
//获取第一个元素
println(nums.head)
//获取最后一个元素
println(nums.last)
//获取除了第一个元素之外的其他元素
println(nums.tail.toBuffer)
//获取除了最后一个元素之外的其他元素
println(nums.init.toBuffer)
//求和
println(nums.sum)
//求最大值
println(nums.max)
//求最小值
println(nums.min)
val nums2 = Array(2, 1, 4, 3)
//数组元素排序,升序
println(nums2.sorted.toBuffer)
//元素相乘
println(nums2.product)
println("===============去重,所以,连接,计数,过滤=====================")
val nums3 = Array(1, 2, 3, 4, 3, 2, 1)
println(nums3.map(_ * 2).toBuffer)
println(nums3.reduce(_ + _))
//将数组中的数据进行去重
println(nums3.distinct.toBuffer)
println(nums3.length)
println(nums3.size)
//获取数组中每个元素的索引
println(nums3.indices.toBuffer)
//使用mkString进行输出
println(nums3.mkString(" & ")) //数组中元素通过&连接
println(nums3.mkString("<", " & ", ">")) //数组中元素通过&连接,头和尾加上<>
//count计数,注意:count后面必须有条件
println(nums3.count(_ > 2)) //统计数组中>2的元素个数
println(nums3.count(_ % 2 == 0)) //统计数组中偶数个数
//filter过滤出符合条件的数据; filterNot过滤出不符合条件的数据
println(nums3.filter(_ > 2).toBuffer)
println(nums3.filterNot(_ % 2 == 0).toBuffer)
println("=================提取元素=======================")
//take提取前N个元素
println(nums3.take(3).toBuffer)
//takeRight提取后N个元素
println(nums3.takeRight(4).toBuffer)
//takeWhile从左向右进行提取,提取出符合条件的元素,如果条件不成立就终止提取
println(nums3.takeWhile(_ < 4).toBuffer)
println("=================删除元素=======================")
//删除前N个元素
println(nums3.drop(3).toBuffer)
//删除后N个元素
println(nums3.dropRight(3).toBuffer)
//从左向右删除符合条件的元素,如果条件不成立,就终止删除操作
println(nums3.dropWhile(_ < 4).toBuffer)
println("=================切分=======================")
//将数组分为两部分,前N个为一部分,剩下的为另一部分
val tuple: (Array[Int], Array[Int]) = nums3.splitAt(3)
println(tuple._1.toBuffer + " " + tuple._2.toBuffer)
//对数组进行切片操作,取出从索引2到索引4的元素,不包括索引为5的元素
println(nums3.slice(2, 5).toBuffer)
println("=================zip拉链操作=======================")
val array1 = Array("A", "B", "C")
val array2 = Array(1, 2, 3, 4)
//拉链操作,当两个数组的长度不一样时,截取相同的长度
val z1: Array[(String, Int)] = array1.zip(array2)
println(z1.toBuffer)
//拉链操作,当两个数组长度不一样时,array1用*填充,array2用-1填充
val z2 = array1.zipAll(array2, "*", -1)
//拉链操作,当两个数组长度不一样时,array2用*填充,array1用-1填充
val z3 = array2.zipAll(array1, "*", -1)
//用数组索引进行填充
val z4 = array1.zipWithIndex
println(z2.toBuffer)
println(z3.toBuffer)
println(z4.toBuffer)
println("==================unzip拆分数组======================")
//通过unzip进行拆分数组的操作
// 通过unzip把z4拆分成两个数组
val (l1, l2) = z4.unzip
println(l1.toBuffer)
println(l2.toBuffer)
val (l3, l4, l5) = Array((1, "one", '1'), (2, "two", '2'), (3, "three", '3')).unzip3
println(l3.toBuffer)
println(l4.toBuffer)
println(l5.toBuffer)
println("================操作符:+、+:、++========================")
//数组的操作符::+ +: ++
// :+用于在数组的尾部追加元素; +:用于在数组的头部追加元素
// ++用于连接两个集合(比如:数组、列表等)
val num1 = (1 to 4).toArray
val num2 = (5 to 8).toArray
val num3 = 10 +: num1
val num4 = num2 :+ 9
val num5 = num1 ++ num2
println(num3.toBuffer)
println(num4.toBuffer)
println(num5.toBuffer)
println("===========数组排序==============")
val sortNums=Array(1,3,5,2,7,8,6,9)
//升序
println(sortNums.sorted.toBuffer)
//降序
println(sortNums.sorted.reverse.toBuffer)
//降序
println(sortNums.sortWith(_>_).toBuffer)
//升序
println(sortNums.sortWith(_<_).toBuffer)
}
}
第5节 多维数组
通过Array的ofDim方法来定义一个多维的数组,多少行,多少列,都是自己说了算。
package lagou.cn.part03
object MultipleArrayDemo {
def main(args: Array[String]): Unit = {
//定义一个3行4列的二维数组
val dim = Array.ofDim[Double](3, 4)
dim(1)(1) = 12.5
for (i <- 0 to 2; j <- 0 to 3){
print(dim(i)(j)+" ")
if(j==3) println()
}
}
}
第6节 元组及操作
Tuple,元组。Map是键值对的集合。对偶是元组的最简单形态;
元组是不同类型的值的集合,元组中的元素可以是不同的数据类型,元组在Scala中的应用非常广泛。
package lagou.cn.part03
/**
* Tuple元组,可以存放不同数据类型的元素
* 元组的索引从1开始,不是从0开始
* 元组在Scala中应用非常广泛,在Spark的源码中会见到很多元组。
* 在Scala中,已经事先定义好了22个Tuple,从Tuple1~~Tuple22
* 在Tuple22中,最多只能有22个元素
*/
object TupleDemo {
def main(args: Array[String]): Unit = {
//定义一个元组
val tuple = (1, 2.5, "spark", 'a', true)
val tuple2 = (1, 1.2, "scala", 'b')
println(tuple == tuple2)
println(tuple._3)
println("============从元组中接收数据===============")
//从元组中接收数据
val (t1, t2, t3, t4), t5 = tuple2
println(s"$t1 $t2 $t3 $t4")
val (b1,_,b2,_),b5=tuple2
println(s"$b1 $b2")
println("=============for循环遍历元组==============")
//遍历元组
for (t <- tuple.productIterator){
println(t)
}
println("=============foreach遍历元组==============")
tuple.productIterator.foreach(println(_))
}
}
第四部分 类与对象
第1节 类和无参构造器
在Scala中,类并不用声明为public;
Scala源文件中可以包含多个类,所有这些类都具有公有可见性;
val修饰的变量(常量),值不能改变,只提供getter方法,没有setter方法;
var修饰的变量,值可以改变,对外提供getter、setter方法;
如果没有定义构造器,类会有一个默认的无参构造器;
package lagou.cn.part04
//在Scala中,类都有一个无参构造器
class Person {
//声明字段必须进行初始化,Scala编译器会根据初始化值的数据类型自动推断字段的类型,字段类型可以省略
var name = "lagou"
//_表示一个占位符,编译器会根据变量的数据类型赋予相应的初始值
//注意:使用占位符_进行赋初始值时,数据类型必须指定
var nickName: String = _
var numInt: Int = _
var numDouble: Double = _
var boolean: Boolean = _
//val修饰的变量不能使用占位符
// val test:Int=_
val num = 30
var age = 20
//如果赋值为null,就需要添加数据类型。如果不添加数据类型,那么就会认为是Null类型的。
var address: String = null
//类中的私有字段,有私有的getter和setter方法
//可以在类的内部访问,也可以被其伴生对象访问
private var hobby = "旅游"
//对象私有字段,访问权限更小,只能在当前类中访问
private[this] val cardInfo = "10010"
//自定义方法
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 person2 = new Person
println(s"${person.nickName} ${person.numInt} ${person.numDouble} ${person.boolean}")
//给类的属性赋值
person.age = 50
//注意:如果使用对象的属性加上 _= 给var修饰的属性进行赋值,其实就是调用age_=这个settter方法
person.age_=(20)
//调用类的属性,其实就是调用它的getter方法
println(person.age)
//调用类中的方法
person.hello("hello")
println(person.addNum(10, 20))
}
}
第2节 自定义getter和setter方法
对于 Scala 类中的每一个属性,编译后会有一个私有的字段和相应的getter、setter方法生成。
//getter方法
println(person age)
//setter方法
person age_= (18)
//getter方法
println(person.age)
可以不使用自动生成的方式,自己定义getter和setter方法
package lagou.cn.part04
class Dog {
private var _leg = 0
//自定义getter方法
def leg: Int = _leg
//自定义的setter方法
def leg_=(newLeg: Int): Unit = {
_leg = newLeg
}
}
object GetterAndSetterDemo {
def main(args: Array[String]): Unit = {
val dog = new Dog
//调用自定义的setter方法
dog.leg_=(4)
//调用自定义的getter方法
println(dog.leg)
}
}
自定义变量的getter和setter方法需要遵循以下原则:
- 字段属性名以“_”作为前缀,如: _leg
- getter方法定义为:def leg = _leg
- setter方法定义为:def leg_=(newLeg: Int)
第3节 Bean属性
JavaBean规范把Java属性定义为一堆getter和setter方法。
类似于Java,当将Scala字段标注为 @BeanProperty时,getFoo和setFoo方法会自动生成。
使用@BeanProperty并不会影响Scala自己自动生成的getter和setter方法。
在使用时需要导入包scala.beans.BeanProperty
package lagou.cn.part04
//需要导入下面的包
import scala.beans.BeanProperty
class Teacher {
@BeanProperty
var name: String = _
}
object BeanDemo {
def main(args: Array[String]): Unit = {
val teacher=new Teacher
teacher.name="jacky"
teacher.name_=("tom")
println(teacher.name)
//BeanProperty生成的setName方法
teacher.setName("lisi")
//BeanProperty生成的getName方法
println(teacher.getName)
}
}
上述Teacher类中共生成了四个方法:
1. name: String
2. name_= (newValue: String): Unit
3. getName(): String
4. setName (newValue: String): Unit
第4节 构造器
如果没有定义构造器,Scala类中会有一个默认的无参构造器;
Scala当中类的构造器分为两种:主构造器和辅助构造器;
主构造器的定义与类的定义交织在一起,将主构造器的参数直接放在类名之后。
当主构造器的参数不用var或val修饰时,参数会生成类的私有val成员。
Scala中,所有的辅助构造器都必须调用另外一个构造器,另外一个构造器可以是辅助构造器,也可以
是主构造器。
package lagou.cn.part04
//主构造器与类名交织在一起,类名后面的参数就是主构造器的参数
//主构造器直接在类中,其代码不包含在任何方法中
class Animal(name: String, age: Int) {
//下面三行println代码都属于主构造器的代码
println(name)
println(age)
println("=========================")
var gender: String = ""
def this(name: String, age: Int, gender: String) {
//每个辅助构造器必须以主构造器或其他辅助构造器的调用作为第一句代码
this(name, age)
this.gender = gender
}
var color: String = ""
def this(name: String, age: Int, gender: String, color: String) {
//此处调用的是上面的辅助构造器
this(name, age, gender)
this.color = color
}
}
object ConstructorDemo {
def main(args: Array[String]): Unit = {
val animal = new Animal("狗蛋", 4)
val animal2 = new Animal("旺才", 3, "雄性")
val animal3 = new Animal("小六", 5, "雄性", "黑色")
}
}
第5节 对象
5.1 单例对象
Scala并没有提供Java那样的静态方法或静态字段;
可以采用object关键字实现单例对象,具备和Java静态方法同样的功能;
使用object语法结构【object是Scala中的一个关键字】达到静态方法和静态字段的目的;对象本质上可
以拥有类的所有特性,除了不能提供构造器参数;
对于任何在Java中用单例对象的地方,在Scala中都可以用object实现:
- 作为存放工具函数或常量的地方
- 高效地共享单个不可变实例
package lagou.cn.part04
object Object {
println("这是单例对象的代码!")
def printInfo: Unit = {
println("Hello Scala Object!")
}
}
object ObjectDemo {
def main(args: Array[String]): Unit = {
// val object1=Object
// val object2=Object
Object.printInfo
Object.printInfo
}
}
Scala中的单例对象具有如下特点:
1、创建单例对象不需要使用new关键字
2、object中只有无参构造器
3、主构造代码块只能执行一次,因为它是单例的
5.2 伴生类与伴生对象
当单例对象与某个类具有相同的名称时,它被称为这个类的“伴生对象”;
类和它的伴生对象必须存在于同一个文件中,而且可以相互访问私有成员(字段和方法);
package lagou.cn.part04
//伴生类和伴生对象,它们的名字是一样的,并且必须存在于同一文件中
class ClassObject {
private var name = "lagou"
def printInfo: Unit = {
//在伴生类中可以访问伴生对象的私有成员
println(ClassObject.num)
println("Hello Object!")
}
}
object ClassObject {
private val num = 10
def main(args: Array[String]): Unit = {
val classObject = new ClassObject
//在伴生对象中可以访问伴生类的私有成员
println(classObject.name)
classObject.printInfo
}
}
5.3 应用程序对象
每个Scala应用程序都必须从一个对象的main方法开始,这个方法的类型为 Array[String] => Unit;
备注:main方法写在class中是没有意义的,在IDEA中这样的 class 连run的图标都不能显示
除了main方法以外,也可以扩展App特质(trait),类似与Java的接口
package lagou.cn.part04
object AppDemo extends App {
//可以不写在main方法中,直接执行
println("Hello Spark!")
}
5.4 apply方法
object 中有一个非常重要的特殊方法 – apply方法;
- apply方法通常定义在伴生对象中,目的是通过伴生类的构造函数功能,来实现伴生对象的构造函
数功能; - 通常我们会在类的伴生对象中定义apply方法,当遇到类名(参数1,…参数n)时apply方法会被调用;
- 在创建伴生对象或伴生类的对象时,通常不会使用new class/class() 的方式,而是直接使用
class()隐式的调用伴生对象的 apply 方法,这样会让对象创建的更加简洁;
package lagou.cn.part04
class Student(name: String, age: Int) {
private var gender: String = _
def sayHi(): Unit = {
println(s"大家好,我是$name,$gender 生")
}
}
object Student {
//apply方法需要定义在伴生对象中
def apply(name: String, age: Int): Student = new Student(name, age)
def main(args: Array[String]): Unit = {
//直接使用class(参数...)这种方式隐式调用伴生对象中的apply方法来创建class Student对象
val student = Student("jacky", 30)
//伴生类与伴生对象可以相互访问私有成员
student.gender = "男"
student.sayHi()
}
}
问题:在Scala中实现工厂方法,让子类声明哪种对象应该被创建,保持对象创建在同一位置。例如,
假设要创建Animal工厂,让其返回Cat和Dog类的实例,基于这个需求,通过实现Animal伴生对象的
apply方法,工厂的使用者可以像这样创建新的Cat和Dog实例。
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
}
}
第五部分 继承
第1节 继承的概念
Scala中继承类的方式和Java一样,也是使用extends关键字:
class Employee extends Person{
var salary=1000
}
和Java一样,可在定义中给出子类需要而父类没有的字段和方法,或者重写父类的方法。
//Person类
class Person(name:String,age:Int)
//Student继承Person类
class Student(name:String,age:Int,var studentNo:String) extends Person(name,age)
object Demo{
def main(args: Array[String]): Unit = {
val student=new Student("john",18,"1024")
}
}
上面继承部分的代码等效于下面的Java代码
//Person类
class Person{
private String name;
private int age;
public Person(String name,int age){
this.name=name;
this.age=age;
}
}
//Student继承Person类
class Student extends Person{
private String studentNo;
public Student(string name,int age,String studentNo){
super(name,age);
this.sutdentNo=studentNo;
}
}
第2节 构造器执行顺序
Scala在继承的时候构造器的执行顺序:首先执行父类的主构造器,其次执行子类自身的主构造器。
类有一个主构造器和任意数量的辅助构造器,而每个辅助构造器都必须以对先前定义的辅助构造器或主
构造器的调用开始。
子类的辅助构造器最终都会调用主构造器。只有主构造器可以调用父类的构造器。
package lagou.cn.part05
class Person(name: String, age: Int){
println("这是父类Person!")
}
class Student(name: String, age: Int, var stuNo: String) extends Person(name, age){
println("这是子类Student!")
}
object ExtendsDemo {
def main(args: Array[String]): Unit = {
//构造Student对象之前,首先会调用Person的主构造器
val student = new Student("jacky", 30, "1001")
// student.stuNo="1002"
println(student.stuNo)
}
}
第3节 override方法重写
方法重写指的是当子类继承父类的时候,从父类继承过来的方法不能满足子类的需要,子类希望有自己
的实现,这时需要对父类的方法进行重写,方法重写是实现多态的关键。
Scala中的方法重写同Java一样,也是利用override关键字标识重写父类的方法。
package lagou.cn.part05
class Programmer(name: String, age: Int) {
def coding(): Unit = {
println("我在写代码。。。")
}
}
class ScalaProgrammer(name: String, age: Int, workNo: String) extends Programmer(name, age) {
override def coding(): Unit = {
//调用父类的方法
super.coding()
//增加自己实现
println("我在写Scala代码。。。")
}
}
object OverrideDemo {
def main(args: Array[String]): Unit = {
val scalaProgrammer = new ScalaProgrammer("jacky", 30, "10010")
scalaProgrammer.coding()
}
}
需要强调一点:如果父类是抽象类,则override关键字可以不加。如果继承的父类是抽象类(假设抽象
类为AbstractClass,子类为SubClass),在SubClass类中,AbstractClass对应的抽象方法如果没有实
现的话,那SubClass也必须定义为抽象类,否则的话必须要有方法的实现。
//抽象的Person类
abstract class Person(name:String,age:Int){
def walk():Unit
}
//Student继承抽象Person类
class Student(name:String,age:Int,var studentNo:String) extends Person(name,age)
{
//重写抽象类中的walk方法,可以不加override关键字
def walk():Unit={
println("walk like a elegant swan")
}
}
object Demo{
def main(args: Array[String]): Unit = {
val stu=new Student("john",18,"1024")
stu.walk()
}
}
第4节 类型检查与转换
要测试某个对象是否属于某个给定的类,可以用isInstanceOf方法。如果测试成功,可以用
asInstanceOf方法进行类型转换。
if (p.isInstanceOf[Employee] ) {
//s的类型转换为
Employee val s = p.asInstanceOf[Employee]
}
如果p指向的是Employee类及其子类的对象,则p.isInstanceOf[Employee]将会成功。
如果p是null,则p.isInstanceOf[Employee]将返回false,且p.asInstanceOf[Employee]将返回null。
如果p不是一个Employee,则p.asInstanceOf[Employee]将抛出异常。
如果想要测试p指向的是一个Employee对象但又不是其子类,可以用:
if(p.getClass == classOf[Employee])
classOf方法定义在scala.Preder对象中,因此会被自动引入。
不过,与类型检查和转换相比,模式匹配通常是更好的选择。
p match{
//将s作为Employee处理
case s: Employee => ...
//p不是Employee的情况
case _ => ....
}
package lagou.cn.part05
class Person2
class Student2 extends Person2
object InstanceDemo {
def main(args: Array[String]): Unit = {
val p: Person2 = new Student2
var s: Student2 = null
//如果对象s是null,那么isInstanceOf会返回false
println(s.isInstanceOf[Student2])
if (p.isInstanceOf[Student2]) {
s = p.asInstanceOf[Student2]
}
println(s.isInstanceOf[Student2])
println(p.getClass == classOf[Person2])
println(p.getClass == classOf[Student2])
println("=====================================")
p match {
case s: Student2 => println("它是Student2类型的对象")
case _ => println("它啥也不是!")
}
}
}