目录
10 map|flatten|flatMap|foreach方法的使用
一 Scala概述
1.1 什么是scala
函数式编程:
函数式编程是一种编程思想,主要的思想把运算过程尽量写成一系列的函数调用。
Scala是一种多范式的编程语言,其设计的初衷是要集成面向对象编程和函数式编程的各种特性。
scala之父:Martin Odersky
helle.scala -> .class 运行在jvm上
Scala运行于Java平台(Java虚拟机),并兼容现有的Java程序。
scala是对java的进一步封装,基于java来开发的。
也就是说,scala的代码最终会被编译为字节码文件,并运行在jvm上。
1.2 为什么要学scala
- 优雅:这是框架设计师第一个要考虑的问题,框架的用户是应用开发程序员,API是否优雅直接影响用户体验。
- 速度快:Scala语言表达能力强,一行代码抵得上Java多行,开发速度快。
scala语言风格简洁,也很可能降低了可读性,所以学习及以后开发过程中,都需要有良好的代码规范。
3.Spark的开发语言,掌握好scala,就能更轻松的学好spark。
4.能融合到Hadoop生态圈:Hadoop现在是大数据事实标准,Spark并不是要取代Hadoop,而是要完善Hadoop生态。JVM语 言大部分可能会想到Java,但Java做出来的API太丑,或者想实现一个优雅的API太费劲。
1.3 Spark函数式编程初体验
hadoop中wordcount流程:
- 读数据,切分
- word,1
- 拿到一组key相同的数据,计数
Q1: 对上述文件内容使用Spark进行单词个数统计?
Q2: 对上述输出结果进行降序 ?
二 Scala开发环境
2.1 安装JDK
因为Scala是运行在JVM平台上的,所以安装Scala之前要安装JDK
使用 # java -version 来验证
确保已安装jdk1.8+
2.2 安装Scala
2.2.1 Windows安装Scala编译器
访问Scala官网Scala 2.11.8 | The Scala Programming Language 下载Scala编译器安装包
安装方式:直接使用免安装版的,解压即可。
安装完成之后,配置环境变量SCALA_HOME和PATH:
可以在cmd窗口下验证:输入scala -version查看scala版本
输入scala可以进入scala shell交互模式
输入:q 或:quit退出scala交互命令行。
该交互模式,有一个高大上的名称:REPL
Read Evaluate Print Loop
(读取-求值-打印-循环)
2.2.2 Linux中安装Scala编译器
下载Scala地址https://downloads.lightbend.com/scala/2.11.8/scala-2.11.8.tgz
1,上传并解压Scala到指定目录
# tar -zxvf scala-2.11.8.tgz -C /usr/local/
2,创建一个软连接(可选项)
# ln -s 源文件目录 软连接目录
# ln -s /usr/local/scala-2.11.8 /usr/local/scala
3,配置环境变量,将scala加入到PATH中
# vi /etc/profile
export SCALA_HOME=/usr/local/scala export PATH=$PATH:$JAVA_HOME/bin:$SCALA_HOME/bin |
2.3 IDEA安装
目前Scala的开发工具主要有两种:Eclipse和IDEA,这两个开发工具都有相应的Scala插件,如果使用Eclipse,直接到官网下载即可
Download Scala IDE for Eclipse - Scala IDE for Eclipse 不推荐使用该种方式
IDEA的Scala插件更优秀,有逼格的Spark攻城狮都选择IDEA(只需一次,就会爱上她)
下载地址:Download IntelliJ IDEA – The Leading Java and Kotlin IDE
下载社区免费版,点击下一步安装即可,安装时如果有网络可以选择在线安装Scala插件。这里我们使用离线安装Scala插件:
- 安装IDEA,点击下一步即可。由于我们离线安装插件,所以点击Skip All and Set Default
- 下载IEDA的scala插件,地址JetBrains Marketplace
2.4 Scala插件离线安装
- 安装Scala插件:Configure -> Plugins -> Install plugin from disk -> 选择Scala插件 -> OK -> 重启IDEA
2.5 IDEA创建Scala工程
安装完成后, 双击打开IDEA, 创建一个新的项目(Create New Project)
选中左侧的Scala -> IDEA -> Next
输入项目名称 -> 点击Finish完成即可
2.6 IDEA创建maven工程
2.7 maven工程设置
创建一个源码包 scala
在pom.xml文件中设置源码包:
重新加载pim.xml文件
添加scalaDK:
ctrl+alt+shift+s
创建包,新建scala object
编写代码
运行程序
2.8 IDEA中的第一个Scala程序
2.9 IDEA常用配置
Ctrl+Alt + s 进入到settings配置页面。
2.9.1 修改字体:
2.9.2 修改字符集
2.9.3 去除自动进入上次项目
如果每次启动IDEA,直接进入到项目页面,没有引导页面,修改配置如下:
修改安装目录的idea.properties文件
该文件在idea的安装目录下:
把idea的缓存及配置移动到其他的盘中,去C盘化
# idea.config.path=${user.home}/.IdeaIC/config
idea.config.path=d:/profiles/develop/IDEACache/config
#---------------------------------------------------------------------
# Uncomment this option if you want to customize path to IDE system folder. Make sure you're using forward slashes.
#---------------------------------------------------------------------
# idea.system.path=${user.home}/.IdeaIC/system
idea.system.path=d:/profiles/develop/IDEACache/system
三 Scala基础
3.1 常用类型
Scala和java一样,
AnyVal
有7种数值类型:Byte、Char、Short、Int、Long、Float和Double(没有基本类型和包装类型的区分)
2种非数值类型: Boolean 和 Unit
注意:Unit表示无值,相当于java中的void。用作不返回任何结果或者结果为空的类型。Unit类型只有一个实例值,写成()。(小括号)
String是属于引用类型
AnyRef
3.2 声明变量
定义变量使用var 或者 val关键字
语法: var|val 变量名称 (: 数据类型) = 变量值
使用val定义的变量是不可变的,相当于java中用final修饰的变量
使用var定义的变量是可变的,推荐使用val
优先使用val,在循环的时候,会使用到var
Scala编译器会自动推断变量的类型,必要的时候可以指定类型
lazy val 变量名
lazy关键字修饰的变量,是一个惰性变量,实现延迟加载(懒加载),在使用的时候才会加载。
lazy关键字不能修饰 var类型的变量
可以使用通配符(占位符)_来指定变量
var name:String = _
需要注意:
- 使用占位符的时候,必须指定变量的类型
- 变量只能使用var来修饰。
- 使用占位符定义变量的时候,不能在main方法中定义。
object VariableTest {
def main(args: Array[String]) {
// 使用val定义的变量值是不可变的,相当于java里用final修饰的变量
//变量名在前,类型在后
val name: String = “nvshen”
// 使用var定义的变量是可变的,在Scala中鼓励使用val
var age = 18
//Scala编译器会自动推断变量的类型,可以省略变量类型
val str = "world"
// 声明多个变量
var age,fv = 18
//var str: String = _
}
}
可以同时声明多个变量,可以使用通配符声明变量:
java中的通配符是*,scala中的通配符是_
定义一个变量,必须赋予初始值,如果没有初始值,可以使用_占位符代替,但是变量必须指定类型。而且占位符变量不能定义在main方法内部。
3.3 条件表达式
表达式都是有返回值的。
条件表达式的值可以赋值给一个变量
支持混合类型的表达式。
Scala的条件表达式比较简洁,例如:
object ConditionTest {
def main(args: Array[String]) {
val x = 1
//判断x的值,将结果赋给y
val y = if (x > 0) x else -1
//打印y的值
println(y)
//如果缺失else,相当于if (x > 2) 1 else ()
val m = if (x > 2) 1
println(m)
//在scala中每个表达式都有返回值,scala中有个Unit类,写做(),相当于Java中的void
val n = if (x > 2) 1 else ()
println(n)
//支持混合类型表达式
val z = if (x > 1) 1 else "error"
//打印z的值
println(z)
混合类型会返回父类类型。
//if和else if
val k = if (x < 0) 0 else if (x >= 1) 1 else -1
println(k)
}
}
条件表达式总结:
1.条件表达式,是有返回值的,可以使用变量接收条件表达式的值
2.条件表达式的返回值是由谁来决定的?
由每一个分支,最后一行的值来决定的。
(比如最后一行是3>2 ,返回值是true;val a = 123 ,返回值是())
3.如果缺少某一个分支,默认的返回值类型是Unit,值是()
if (age>10) age == if(age>10) age else ()
4.在混合类型中,返回值的类型,一般情况下是所有分支返回值类型的一个父类(如果两个分支的数据类型可以转换,)
val result = if(x >10){
x // Int
}else {
99.9
}
result: Double
5.当每一个分支,只有一行内容的时候,就可以省略大括号,而且可以写在一行。推荐大家都写上{}
3.4 块表达式
当每一个分支,只有一行内容的时候,就可以省略大括号,而且可以写在一行。推荐大家都写上{}
object BlockExpressionTest {
def main(args: Array[String]) {
val x = 0
//在scala中{}中可包含一系列表达式,块中最后一个表达式的值就是块的值
//下面就是一个块表达式
val result = {
if (x < 0){
-1
} else if(x >= 1) {
1
} else {
"error"
}
}
// result的值就是块表达式的结果
println(result)
}
}
3.5 循环
在scala中有for循环和while循环, for循环最常用
3.5.1 for循环
语法结构:for (i <- 表达式/数组/集合)
java for循环方式:
// for(i=1;i<10;i++) // 传统for循环
// for(Int I :arr) // 增强for循环
object ForTest {
def main(args: Array[String]) {
//for(i <- 数组)
val arr = Array("a", "b", "c")
// 遍历打印数组中的每个元素
for (i <- arr) // 类似Java中的增强for
println(i)
// 通过角标获取数组中的元素
val index = Array(0,1,2)
// 遍历打印数组中的每个元素
for (i <- index) // 类似Java中的传统for
println(arr(i)) // 获取元素的方式是(),java中是[]
//for(i <- 表达式),表达式1 to 10返回一个Range(区间)
//每次循环将区间中的一个值赋给i
for (i <- 1 to 6)
println(i)
println(arr(i)) // 报错,如果不加{},只会把for后面的一行当做循环的内容。
for (i <- 1 to 6){
println(i)
println(arr(i))
}
for(i <- 1 until 6) { // 0 until 6 => 会生成一个范围集合Range(0,1,2,3,4,5)
println(array(i))
}
// 打印数组中的偶数元素
// 在for循环中,通过添加守卫来实现输出满足特定条件的数据
for(e <- arr if e % 2 == 0) { // for表达式中可以增加守卫
println(e)
}
//高级for循环
//每个生成器都可以带一个条件
for(i <- 1 to 3; j <- 1 to 3 if i != j){
print((10 * i + j) + " ")
}
//for推导式:如果for循环的循环体以yield开始,则该循环会构建出一个集合
//每次迭代生成集合中的一个值
val v = for (i <- 1 to 10) yield i * 10
println(v)
}
}
两个生成器: to until
1 to 10 生成 Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 左闭右闭区间 [ ]
1 until 10 生成 Range(1, 2, 3, 4, 5, 6, 7, 8, 9) 左闭右开区间 [ )
for循环总结:
循环的标识: <-
增强for循环: for(I <- arr)
普通for循环: to until
带守卫的for循环 (if 条件)
嵌套for循环 (for(i<- 1 to 3 ;j <- 1 to 3 if( I != j ))))
yield推导式 返回一个满足条件的数组
3.5.2 while循环
Scala 的 while 循环和其它语言如 Java 功能一样,它含有一个条件,和一个循环体,只要条件满足,就一直执行循环体的代码。
语法结构:while(condition){ 循环体内容 }
var i = 0
while(i<5) {
println(i)
i += 1 // i = i + 1
}
Scala 也有 do-while 循环,它和 while 循环类似,只是检查条件是否满足在循环体执行之后检查。
i = 0
// while 直接判断
while(i>0 && i<=5) {
println(i)
i += 1
}
i = 0
// do while 先执行一次循环,再进行判断
do{
println(i)
i += 1
}while(i>0 && i<=5)
循环也有返回值,只不过都是Unit
val res = while(i>0 && i<=5) {
println(i)
i += 1
}
println(s"res = $res")
3.6 函数式编程再体验
map:对集合或者数组中的每一个元素进行操作,该方法接收一个函数,具体的业务逻辑是自己定义的。
filter: 过滤,过滤出满足条件的元素。
3.7 调用方法(运算符重载为方法)
Scala中的+ - * / %等操作符的作用与Java一样。只是有一点特别的:
这些操作符实际上是方法。操作符被重载为方法。
例如:
a + b
是如下方法调用的简写:
a.+(b)
a 方法 b可以写成 a.方法(b)
四 方法和函数(重难点)
方法:一段业务逻辑的综合。
4.1 定义方法
java中的方法:
public int add(int a,int b){
return a + b;
}
def methodName ([list of parameters]) : [return type] = {}
/**
* 方法的定义及调用
*
* 定义方法的格式为:
* def methodName ([list of parameters]) : [return type] = {}
* 如果不使用等号和方法体,则隐式声明抽象(abstract)方法。
*/
object ScalaMethod extends App{
// 定义个sum方法, 该方法有2个参数, 参数类型为整型, 方法的返回值为整型
def sum(a:Int, b: Int): Int = {
a + b
}
// 定义有可变参数的方法,
def sumAll(b: Int*): Int = {
var v = 0
for (i<- b){
v += i
}
v // 返回值
}
// 调用
val result1 = sum(1, 5)
println(result1)
println(sumAll(1,11,13))
// 该方法没有任何参数, 也没有返回值
def sayHello1 = println("Say BB1")
def sayHello2() = println("Say BB2")
sayHello1 // 如果方法没有() 调用时不能加()
sayHello2 // 可是省略(), 也可以不省略
}
如果有return关键字,必须要有返回值类型,否则报错如下:
方法总结:
- 定义方法的关键字,def
格式: def 方法的名称(参数列表):返回值类型 = {方法体内容}
- 方法的返回值,最后一行的内容,如果是循环,那么返回值是Unit
- 如果空参方法,定义的时候有(),调用的时候可以省略(),但是如果定义的时候没有(),调用方法的时候,不能加()
- 方法的返回值类型,可以省略,但是特殊情况下,必须加上:
4.1,方法有return关键字
4.2,递归调用的方法。
5,方法不能最为最终的表达式存在,(空参的方法调用除外)
4.2 定义函数
和方法类似,基本能实现相同的功能
val| var 函数名称=(函数的参数列表) => 函数体
函数可以作为最终的表达式存在,返回的内容就是函数的签名
签名(函数的名称,函数的参数,函数的返回值类型)
这种定义方式不需要指定返回值类型,编译器会自动推断
第二种定义方式:
复杂全面的定义
val | var 函数名称:(输入参数类型)=> 返回值类型 = (参数的引用)=> 函数体
定义一个无参的函数
不同于方法,没有参数的函数定义,也必须加()
val f2:()=>Unit =() => println(123) val f2 =() => println(123) 返回值类型为Unit
val f2:()=>Int =() => 123 val f2=() => 123 返回值类型为Int
4.3 方法和函数的区别
在函数式编程语言中,函数是“头等公民”,它可以像任何其他数据类型一样被传递和操作
区别和联系:
- 方法用def关键字定义,函数的标识 =>
- 方法不能作为最终的表达式存在,但是函数可以,返回函数的签名信息
- 方法和函数调用的时候都需要显示的传入参数
- 函数可以作为方法的参数,和返回值类型。
案例:首先定义一个方法,再定义一个函数,然后将函数传递到方法里面
object MethodAndFunctionTest {
//定义一个方法
//方法m2参数要求是一个函数,函数的参数必须是两个Int类型
//返回值类型也是Int类型
def m1(f: (Int, Int) => Int) : Int = {
f(2, 6)
}
//定义一个函数f1,参数是两个Int类型,返回值是一个Int类型
val f1 = (x: Int, y: Int) => x + y
//再定义一个函数f2
val f2 = (m: Int, n: Int) => m * n
//main方法
def main(args: Array[String]) {
//调用m1方法,并传入f1函数
val r1 = m1(f1)
println(r1)
//调用m1方法,并传入f2函数
val r2 = m1(f2)
println(r2)
}
}
// 定义一个普通方法
def max(x:Int,y:Int) = if(x>y)x else y
// 定义一个方法,参数是一个函数,参数只需要函数签名,在调用的时候具体再传入函数体
def max1(f:(Int,Int)=>Int) = f(20,10)
def max2(f:(Int,Int) => Int,x:Int,y:Int)= f(x,y)
// 定义一个方法,方法返回值是函数
def max3()= (x:Int,y:Int)=> if (x>y) x else y
def main(args: Array[String]): Unit = {
println(max(10,20))
println(max1((x:Int,y:Int)=>if(x>y) x else y))
println(max2((x:Int,y:Int)=>if(x>y) x else y,10,20))
println(max3()(10,20))
}
4.4 将方法转换成函数(神奇的下划线)
五 集合框架
5.1 集合综述
Scala的集合有三大类:序列Seq、集Set、映射Map,所有的集合都扩展自Iterable特质
在Scala中集合有可变(mutable)和不可变(immutable)两种类型(两个不同的包),
包的全局路径:scala.collection.immutable 默认使用的是该包中的集合
如果想使用可变的集合,就需要导包 scala.collection.mutable
immutable类型的集合初始化后就不能改变了(注意与val修饰的变量进行区别)
使用val这个关键字修饰一个变量(相当于java中的final),那么就意味着该变量的引用不可变,该引用中的内容是不是可变,取决于这个引用指向的集合的类型
val 集合 ,该集合中的内容是否可变,取决于集合的特性。
5.2 集合框架整体架构(了解)
顶级抽象类或父类:
不可变集合(scala.collection.immutable):
可变集合(scala.collection.mutable):
六 数组
6.1 定长数组和变长数组
Nothing是所有类型的子类型,表示程序出错了。
Null ,所有引用类型的子类型,只有一个值,就是null
定长数组,是不可变的,长度不可变,内容可变,默认类型
变长数组,可变的,长度可变,内容可变
创建定长数组:
指定数组类型,并赋值,类型可省略,编译器会自动推导。
val arr1 = Array[Int](1,2,3,4,5)
通过new关键字创建的数组,必须指定数组的类型和长度。
val arr2 = new Array[Int](10)
取值赋值,下标从0开始:
arr1(0)
arr1(0) =100
object ArrayTest {
def main(args: Array[String]) {
//初始化一个长度为8的定长数组,其所有元素均为0
val arr1 = new Array[Int](8)
//直接打印定长数组,内容为数组的hashcode值
println(arr1)
//将数组转换成数组缓冲,就可以看到原数组中的内容了
//toBuffer会将数组转换长数组缓冲
println(arr1.toBuffer)
//注意:如果不是new,相当于调用了数组的apply方法,直接为数组赋值
//初始化一个长度为1的定长数组
val arr2 = Array[Int](10)
println(arr2.toBuffer)
//定义一个长度为3的定长数组
val arr3 = Array("hadoop", "storm", "spark")
//使用()来访问元素
println(arr3(2))
//
//变长数组(数组缓冲)
//如果想使用数组缓冲,需要导入import scala.collection.mutable.ArrayBuffer包
val ab = ArrayBuffer[Int](1,3,4)
val ab = new ArrayBuffer[Int]()
//向数组缓冲的尾部追加一个元素
//+=尾部追加元素
ab += 1
//追加多个元素
ab += (2, 3, 4, 5)
ab -= (3, 4)
//追加一个数组++=
ab ++= Array(6, 7)
//追加一个数组缓冲
ab ++= ArrayBuffer(8,9)
//减少一个数组++=
ab --= Array(6, 7)
//在数组某个位置插入元素用insert,第一个参数为插入元素的位置,后面的可变参数为插入的元素
ab.insert(0, -1, 0)
//删除数组某个位置的元素用remove,第一个参数为要删除的元素位置,第二个参数为删除几个元素
ab.remove(8, 2)
println(ab)
// 清空
ab1.clear()
println(ab1)
}
}
6.2 遍历数组
1.增强for循环
2.生成器 to或者until,0 until 10 包含0不包含10
object ForArrayTest {
def main(args: Array[String]) {
//初始化一个数组
val arr = Array(1,2,3,4,5,6,7,8)
//增强for循环
for(i <- arr)
println(i)
//好用的until会生成一个Range
//reverse是将前面生成的Range反转
for(i <- (0 until arr.length).reverse)
println(arr(i))
}
}
for(i <- (arr.length-1 until (-1,-1)))
println(arr(i))
}
6.3 数组转换
toArray 变长数组转换成定长数组
toBuffer 定长数组转换成变长数组
yield关键字将原始的数组进行转换会产生一个新的数组,原始的数组不变
object ArrayYieldTest {
def main(args: Array[String]) {
//定义一个数组
val arr = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)
//将偶数取出乘以10后再生成一个新的数组
val res = for (e <- arr if e % 2 == 0) yield e * 10
println(res.toBuffer)
//更高级的写法,用着更爽
//filter是过滤,接收一个返回值为boolean的函数
//map相当于将数组中的每一个元素取出来,应用传进去的函数
val r = arr.filter(x => x % 2 == 0).map(x => x * 10)
println(r.toBuffer)
}
}
6.4 数组常用方法
在Scala中,数组上的某些方法对数组进行相应的操作非常方便!
sorted默认是升序排序的。
arr. 然后输入Tab,可查看所有的方法。
val arr = Array(1,3,4,6,8)
数组反转:arr1.reverse
数组切片:arr.slice(1,4) 第一个起始位置,包含;第二个终止位置,不包含 [ )
七 元组Tuple
与数组或列表不同,元组可以容纳不同类型的对象,但它们也是不可变的。
元组是不同类型元素的集合
7.1 创建元组
定义元组时,使用小括号将多个元素括起来,元素之间使用逗号分隔,元素的类型可以不同,元素的个数任意多个(不超过22个)
注意:元组没有可变和不可变之分,都是不可变的。
val t = (12.3, 1000, "spark")
val t1 = new Tuple1(1) // 必须1个元素
val t4 = new Tuple4(1,2.0,"",3) // 必须4个元素
7.2 获取元组中的值
获取元组的值使用下标获取,但是元组的下标时从1开始的,而不是0
7.3 将对偶的集合转换成映射
7.4 拉链操作
zip命令可以将多个值绑定在一起,生成元组
val name=Array("xx1","xx2","xx3",”xx4”)
val values=Array(1,2,3)
name.zip(values)
多个zip # name zip values zip values.map(_*10)
zipWithIndex 类似于zip,自带了索引,索引是从0 开始的。
注意:如果两个数组的元素个数不一致,拉链操作后生成的数组的长度为较小的那个数组的元素个数
7.5 元素交换
可以使用 Tuple.swap 方法来交换对偶元组的元素。
// 定义元组
var t = (1, "hello", true)
// 或者
val tuple3 = new Tuple3(1, "hello", true)
// 访问tuple中的元素
println(t._2) // 访问元组总的第二个元素
// 对偶元组
val tuple2 = (1, 3)
// 交换元组的元素位置, tuple2没有变化, 生成了新的元组
val swap = tuple2.swap
元组类型Tuple1,Tuple2,Tuple3等等。目前Scala中只能有22个上限,如果需要更多个元素,那么可以使用集合而不是元组。
八 序列List
不可变list 长度不可变,内容也不可变 List
可变list 长度和内容都可变 ListBuffer
8.1 不可变序列
默认就是不可变序列,长度和内容都不可变
构造列表的两个基本单位是 Nil 和 ::
Nil 表示为一个空列表。
创建List集合的两种方式:
val list1= List[Int](1,2,3)
val list2 = 9::5::2::Nil
注意::: 操作符是右结合的,该操作符就是将给定的头和尾创建一个新的列表
如9 :: 5 :: 2 :: Nil相当于 9 :: (5 :: (2 :: Nil))
注意:在Scala中列表要么为空,要么是一个head元素加上一个tail列表。
列表的连接:
可以使用++ 或 ::: 运算符或或 List.concat() 方法来连接两个或多个列表
注意:对不可变List的所有的操作,全部生成新的List
object ImmutListTest {
def main(args: Array[String]) {
//创建一个不可变的集合
val lst1 = List(1,2,3)
//将0插入到lst1的前面生成一个新的List
val lst2 = 0 :: lst1
val lst4 = 0 +: lst1
//将一个元素添加到lst1的后面产生一个新的集合
val lst6 = lst1 :+ 3
val lst0 = List(4,5,6)
//将2个list合并成一个新的List
val lst7 = lst1 ++ lst0
val lst8 = lst1 ++: lst0
val lst9 = lst1 ::: lst0
val lst10 = List.concat(lst1,lst0)
// 列表反转
lst1.reverse
// 列表头元素
lst1.head
// 列表的尾列表
lst1.tail
}
}
List和Array的对比:
list不可变,表示长度不可变,内容也不可变
array 长度不可变,但是内容可变
不可变list和可变List的比较(List和ListBuffer):
不可变List,长度不可变,值不可变
可变List,长度可变,值可变
8.2 可变序列
ListBuffer
需要显示导包 import scala.collection.mutable._
创建ListBuffer的两种方式:
val lb1 = ListBuffer[Int]() // 创建的同时可赋初值
val lb2 = new ListBuffer[Int]() // 类型必须显示指定
添加元素:
+=方法和append方法,都可以添加多个元素。
import scala.collection.mutable.ListBuffer
object MutListTest {
def main(args: Array[String]) {
//构建一个可变列表,初始有3个元素1,2,3
val lst0 = ListBuffer[Int](1,2,3)
//创建一个空的可变列表
val lst1 = new ListBuffer[Int]
//向lst1中追加元素,注意:没有生成新的集合
lst1 += (4,6)
lst1.append(5)
//将lst0和lst1合并成一个新的ListBuffer 注意:生成了一个新集合
val lst2 = lst0 ++ lst1
//将lst1中的元素追加到lst0中, 注意:没有生成新的集合
lst0 ++= lst1
//将元素追加到lst0的后面生成一个新的集合
val lst3 = lst0 :+ 5
//将元素追加到lst0的前面
val lst4 = 5 +: lst0
// 去除元素
lb2 -= (1,3)
lb2 --= List(7,9)
lb2.remove(1,2) //去除元素 第一个参数是下标,第二个参数的个数
// 判断集合是否为空
Lb2.isEmpty()
}}
List的转换:
可变list可以通过toList方法,生成新的List
list可以通过toBuffer,转变成ArrayBuffer
九 映射Map
在Scala中,把哈希表这种数据结构叫做映射
映射是K/V对 类型的值。
可变的map,还是不可变的map,都是Map
9.1 定义map
在Scala中,有两种Map,一个是immutable包下的Map,该Map中的内容不可变;另一个是mutable包下的Map,该Map中的内容可变。
默认是immutable包下的map,
// 默认是immtable包下的Map
val mp1 = Map(("a",1),("b",2))
val mp2 = Map("a"->1,"b"->2)
// 添加元素之后生成新的map
val mp3 = mp2+("c"->1)
可使用mutable.Map
// 导包
import scala.collection.mutable
// 创建集合
val mp4 = new mutable.HashMap[String,Int]()
val mp5 = mutable.Map[String,Int]()
9.2 添加元素
针对可变集合,多种赋值方式
mp4 += ("e"->8, "f"->9)
mp4+= (("b1",121))
mp4.put("lyf",21)
mp4("nvshen")=18
9.3 获取映射值
判断key是否存在 contains
mp4.contains(“b”)
mp4(“a”)
mp4.get(“xxoo”)
如果没有值,赋予默认值:
mp4.getOrElse(“xxoo”,9527)
9.4 赋值和修改值
m1.getOrElse("d", 0)
m1("b") = 22
m1.updated("b",22) // 如果是不可变的Map,那么会生成一个新的map集合
9.5 删除元素
根据key来删除一个或多个值
m1 -= ("a")
m1.remove("a")
去除多个key:
m1 -= ("a","b")
m1 --= List(“key”)
9.6 map遍历
for(i <- mp) println(i)
for((k,v) <- mp){println(k)}
for((_,v) <- mp){println(v)}
_是占位符,如果只需要遍历value,不需要遍历key,就可以用占位符
交换k,v
for((k,v) <- mp) yield (v,k)
mp.map(x=>(x._2,x._1))
mp.map(x=>x.swap)
9.7 获得keys和values
合并
使用 ++ 运算符或 mp.++() 方法来连接两个 Map,Map 合并时会移除重复的 key。
// 合并时,相同的key元素会被覆盖
val colors1 = Map("nvshen" ->18,"nanshen" -> 35)
val colors2 = Map("bq" -> 40,"nvshen" -> 20)
var colors = colors1 ++ colors2
10 map|flatten|flatMap|foreach方法的使用
map 迭代集合中的每一个元素,然后按照自己的逻辑来修改元素
map方法操作集合中的每一个元素,返回值类型。
map方法有一个参数,是函数类型,该函数作用于集合的每一个元素上,对集合的每一个元素执行操作。有返回值。
map方法的内层返回值,是由函数的返回值决定的。
flatten 压平 如果有嵌套集合,就会把内层的嵌套去掉了
flatMap 先map 再flatten 先map 再压平
foreach 对每一个元素执行操作,相当于遍历 println 返回值是Unit
filter 过滤
map和foreach的区别:
1,底层实现不同,map方法利用隐式转换实现的Builder,foreach底层使用的是迭代器。
2,返回值不同,map返回值类型一般由函数返回值类型决定(map方法的参数就是一个函数),foreach返回值为Unit
怎么选择?如果要求有返回值,就用map,如果是打印或者没有返回值的要求,使用foreach。
// 定义一个数组
val array = Array[Int](2,4,6,9,3)
// map方法是将array数组中的每个元素进行某种映射操作, (x: Int) => x * 2 为一个匿名函数, x 就是array中的每个元素
val y = array map((x: Int) => x * 2)
// 或者这样写, 编译器会自动推测x的数据类型
val z = array.map(x => x*2)
// 亦或者, _ 表示入参, 表示数组中的每个元素值
val x = array.map(_ * 2)
println(x.toBuffer)
println("--------分割线--------")
// 定义一个数组
val words = Array("hello tom hello jim hello jerry", "hello Hatano")
// 将数组中的每个元素进行分割
// Array(Array(hello, tom, hello, jim, hello, jerry), Array(hello, Hatano))
val splitWords: Array[Array[String]] = words.map(wd => wd.split(" "))
// 此时数组中的每个元素经过split之后变成了Array, flatten是对splitWords里面的元素进行扁平化操作
// Array(hello, tom, hello, jim, hello, jerry, hello, Hatano)
val flattenWords = splitWords.flatten
// 上述的2步操作, 可以等价于flatMap, 意味先map操作后进行flatten操作
val result: Array[String] = words.flatMap(wd => wd.split(" "))
// 遍历数组, 打印每个元素
result.foreach(println)
11 set
11.1 不可变的set
长度和值都不可变,set中的元素不能重复
object ImmutSetTest {
def main(args: Array[String]) {
// 默认是immtable包下的Set
val set1 = Set (1,4,6)
// 执行添加,删除的操作,都是生成了新的Set集合
val s2: Set[Int] = set1 + (10,12)
set1 - (1)
println(set1)
// 查看set集合内容
set1.foreach(println)
//set中元素不能重复
val set3 = set1 ++ Set(5, 6, 7)
val set0 = Set(1,3,4) ++ set1
}
}
11.2 可变的Set
可变Set中,remove方法,移除的是元素,而不是下标
ListBuffer中,remove方法,参数是下标
import scala.collection.mutable
object MutSetTest {
def main(args: Array[String]) {
import scala.collection.mutable.Set // 可以在任何地方引入 可变集合
val mSet = new mutable.HashSet[Int]()
val mutableSet = Set(1,2,3)
mutableSet.add(4)
// mutableSet += 5
mutableSet += (5,15)
// 添加set集合
mutableSet ++= Set(12,14)
// mutableSet -= 4
mutableSet -= (4,2)
// remove方法,删除的不是下标,而是元素
mutableSet.remove(2)
println(mutableSet)
// 转换为不可变集合
val another = mutableSet.toSet
println(another.getClass.getName) // scala.collection.immutable.Set
}
}
12 Map和Option
Option 表示可以有值,也可能没有值的返回类型,有两个子类,
Some,有值的结果 Some(值) 想要获取值,使用 get方法
None,没有值
getOrElse
在Scala中Option类型样例类用来表示可能存在或也可能不存在的值(Option的子类有Some和None)。Some包装了某个值,None表示没有值。
/ Option是Some和None的父类
// Some代表有(多例),样例类
// None代表没有(单例),样例对象
val mp = Map("a" -> 1, "b" -> 2, "c" -> 3)
val r: Int = mp("d")
// Map 的get方法返回的为Option, 也就意味着 rv 可能取到也有可能没取到
val rv: Option[Int] = mp.get("d")
// 如果rv=None时, 会出现异常情况
val r1 = rv.get
// 使用getOrElse方法,
// 第一个参数为要获取的key,
// 第二个参数为默认值, 如果没有获取到key对应的值就返回默认值
val r2 = mp.getOrElse("d", -1)
println(r2)
13 集合常用方法
map
filter
过滤出满足条件的所有元素,并返回一个集合
find
过滤出满足条件的一个元素,并返回一个Option
如果说有结果值,返回值的是Some(元素值),通过get方法来获取值
sorted
按元素的升序排序
sortBy
按照指定的条件排序
sortWith
接收两个参数,并进行比较
mapValues
类似于map,只是处理的是k-v类型中的v值,只能作用于map类型集合
groupBy
按照指定条件分组
grouped
按照指定元素个数进行分组
count
统计满足条件的元素个数
reduce *****
元素归并
参数是一个函数,这个函数有两个参数 累加值 元素值 调用的就是reduceLeft
val arr=Array("aa","bb","cc","dd")
arr.reduce((x,y)=>自定义操作)
自定义操作的方法,必须是x数据类型上支持的方法。x可以是任意类型
arr.reduce(_ + _)
arr.reduce(_ ++ _)
val lst = List(List("a"),List("b"),List("c"))
lst.reduce(_++_)
lst.reduce(_:::_)
reduceLeft reduceRight
reduce底层调用的就是reduceLeft,只不过,reduce要求函数的输入类型和返回值类型必须一致,而reduceLeft,可以不一致。
fold foldLeft foldRight
fold有两个参数,第一个参数是默认值,第二个参数是一个函数,该函数有两个参数 累加值 元素值 调用的就是reduceLeft
fold 要求函数的2个输入参数类型必须一致,foldLeft 可以允许函数的2个输入参数类型不一致
示例:求平均值
val d1 = Array(("bj",28.1), ("sh",28.7), ("gz",32.0), ("sz", 33.1))
val d2 = Array(("bj",27.3), ("sh",30.1), ("gz",33.3))
val d3 = Array(("bj",28.2), ("sh",29.1), ("gz",32.0), ("sz", 30.5))
// 1,需要把数据组装到一起
val data1: Array[(String, Double)] = d1.union(d2).union(d3) // d1 union d2 union d3
//d1 ++ d2 ++ d3
// 2 分组 按照城市名称来分组
val data2: Map[String, Array[(String, Double)]] = data1.groupBy(t=>t._1)
// 统计
val data4 = data2.mapValues({
kv=>
// kv数据类型: Array[(String,Double)]
// t的数据类型是元组(String,Double)
})
println("--------111------------")
println(data4)
// 3 统计 拿到这几个月份的温度的总值,然后再求平均
val result: Map[String, Double] = data2.map {
t =>
// t (String,Array[(String, Double)])
val city = t._1
// foldLeft 第一个是累加值 第二个是元素值
val wendu: Double = t._2.foldLeft(0d)({
// total是Double a是(String, Double)
(total, a) =>
total + a._2
}) / t._2.length
(city, wendu)
}
println(result)
交集,并集 差集
intersect union diff
union是一个轻量级的方法
//比较常用的方法:先union,然后再分组
distinct 元素去重
mkString 把集合中的所有元素拼接成字符串
mkString(分隔符)
take(n) 获取集合中前几个元素 没有排序
slice(from,until) 截取元素,提取元素列表中的from 到until位置的元素
聚合 aggregate
val arr = List(List(1, 2, 3), List(2))
val result = arr.aggregate(0)(_+_.sum, _+_)
14 面向对象
14.1 对象
14.1.1 单例对象
Scala中没有静态方法和静态字段,没有static,
java中,没有关键字修饰的方法,只能用new class().方法
so 对于一个class来说,所有的方法和成员变量在实例被 new 出来之前都无法访问
虽然可以在class中定义main方法,然并卵…
但是可以使用object这个语法结构来达到同样的目的
用object关键字修饰的对象是单例的,称为单例对象,静态对象。
//单例对象
object ScalaSingleton {
def saySomething(msg: String) = {
println(msg)
}
}
object test {
def main(args: Array[String]): Unit = {
ScalaSingleton.saySomething("singleton....")
println(ScalaSingleton)
println(ScalaSingleton)
// 输出结果:
// cn.demo.ScalaSingleton$@28f67ac7
// cn.demo.ScalaSingleton$@28f67ac7
}
}
14.1.2 伴生对象
伴生对象是一种特殊的单例对象。是一种相对概念,需要满足两个条件:
条件1:在同一个源文件中,
条件2:对象名和类名相同
这样的单例对象,被称作是这个类的伴生对象。类被称为是这个单例对象的伴生类。
结论:类和伴生对象之间可以相互访问私有的方法和属性
class Dog {
val id = 1
private var name = "xiaoqing"
def printName(): Unit ={
//在Dog类中可以访问伴生对象Dog的私有属性
println(Dog.CONSTANT + name )
}
}
/**
* 伴生对象
*/
object Dog {
//伴生对象中的私有属性
private val CONSTANT = "汪汪汪 : "
def main(args: Array[String]) {
val p = new Dog
//访问私有的字段name
p.name = "123"
p.printName()
}
}
14.1.3 apply方法
通常我们会在类的伴生对象中定义apply方法,当遇到对象名(参数1,...参数n)时apply方法会被调用
正常情况下,对象调用时是不能带参数的,但是如果能找到对应的apply方法,就能调用成功。
当使用对象(参数列表)来调用对象时,会去对象中找对应参数的apply方法,如果找到就执行相应的逻辑,如果找不到,就报错。
注意:只能找到和参数列表对应的apply方法。
要和对象区分开来
ApplyDemo // 对象
ApplyDemo() // ApplyDemo.apply() 方法
该语法的目的:不需通过new关键字,更方便的完成类和实例对象的初始化。
object ApplyDemo {
def apply(msg:String) = {
print(s"主食 油泼面,小菜:$msg")
}
def apply(i:Int):Int = {
i * i
}
def main(args: Array[String]) {
//调用了Array伴生对象的apply方法
//def apply(x: Int, xs: Int*): Array[Int]
//arr1中只有一个元素5
val arr1 = Array(5)
println(arr1.toBuffer)
//new了一个长度为5的array,数组里面包含5个null
var arr2 = new Array(5)
println(ApplyDemo("海参炒面"))
println(ApplyDemo.apply("油炸煎饼"))
println(ApplyDemo(1))
}
}
14.1.4 应用程序对象
Scala程序都必须从一个对象的main方法开始,可以通过扩展App特质,不写main方法。
object AppObjectDemo extends App{
//不用写main方法
println("I love you Scala")
}
14.2 类
类和对象,应该怎么使用???
优先使用对象,如果需要构造器,那么只能使用类。
如果需要封装数据的时候,Person,需要使用到类。
object,class 伴生类
14.2.1 类的定义
在Scala中,类并不用声明为public。
Scala源文件中可以包含多个类,所有这些类都具有公有可见性。
var 修饰的变量, 这个变量对外提供getter setter方法
val 修饰的变量,是只读属性 对外提供了getter方法,没有setter(相当于java中用final修饰的变量)
class Student {
val id = 666
// _ 表示一个占位符, 编译器会根据变量的具体类型赋予相应初始值
// 注意: 使用_ 占位符是, 变量类型必须指定
var name: String = _
//用var修饰的变量既有getter又有setter
var age: Int = 20
}
object Test{
val name: String = "zhangsan"
def main(args: Array[String]): Unit = {
// 调用空参构造器,
val student = new Student()
student.name = "laowang"
// 类中使用val修饰的变量不能更改
// student.age = 20
println(s"student.name ====== ${student.name} ${student.age}")
println("Test.name ======" + Test.name)
}
}
14.2.2 构造器
1,主构造器
2,辅助构造器
同java中的构造方法
构造器分为两类:主构造器,辅助构造器
主构造器直接在类名后面定义。
每个类都有主构造器,主构造器的参数直接放置类名后面,与类交织在一起。
如果没有定义构造器, 类会有一个默认的空参构造器
辅助构造器自定义,使用def this关键字,而且必须调用主构造器,或者其他的辅助构造器
注意:主构造器会执行类定义中的所有语句
/**
*每个类都有主构造器,主构造器的参数直接放置类名后面,与类交织在一起
*/
class Person(val name: String, val age: Int){
//主构造器会执行类定义中的所有语句
println("执行主构造器")
private var gender = "male"
//用this关键字定义辅助构造器
def this(name: String, age: Int, gender: String){
//每个辅助构造器必须以主构造器或其他的辅助构造器的调用开始
this(name, age)
println("执行辅助构造器")
this.gender = gender
}
var account1:String=_
def this(name:String,age:Int,gender:String){
this(name,age)
this.gender=gender
}
def this(name:String,age:Int,gender:String,account:String){
this(name,age,gender)
this.account1=account
}
println("尼玛,这里还是主构造器")
}
object Person {
def main(args: Array[String]): Unit = {
val s = new Person ("laoduan", 38)
println(s"${s.name} ${s.age}")
val s1 = new Person (“dingding", 18, "female")
println(s"${s1.gender}")
val p2 =new Person("xx2",12,"female","9527")
println(s"${p2.age},${p2.account1}")
}
}
scala的构造器
主构造器和辅助构造器
创建对象的同时对属性赋值,初始化赋值
主构造器,和类的定义交织在一起,如果主构造器的变量使用了val或var修饰,变成了类的一个成员属性。
辅助构造器,def this(参数列表),
辅助构造器的第一行,必须调用其他的构造器(主,辅助。。。)
辅助构造器和主构造器的参数列表,不能完全一致,参数类型和个数一致。
如果想定义空参的辅助构造器,调用的主构造器的参数必须赋值(初始化)
作用域范围:
主: 类中除了属性和方法之外,都是构造器的作用域
辅助: 只在自己的方法范围之内
14.2.3 访问权限
成员变量的访问权限
默认权限是public 任何地方都可以访问
private 作用域 类和其伴生对象中
private [this] ,作用域为当前类中,伴生对象中无效
private [packageName] 指定包及其子包有效
/*
* private var age
* age 在这个类中是有getter setter方法的
* 但是前面如果加上了private 修饰, 也就意味着, age只能在这个类的内部以及其伴生类对象中可以访问修改
* 其他外部类不能访问
* */
class Student3 private (val name: String, private var age: Int) {
var gender: String = _
// 辅助构造器, 使用def this
// 在辅助构造器中必须先调用类的主构造器
def this(name: String, age:Int, gender: String){
this(name, age)
this.gender = gender
}
// private[this]关键字标识该属性只能在类的内部访问, 伴生类不能访问
private[this] val province: String = "北京市"
def getAge = 18
}
// 类的伴生对象
object Student3 {
def main(args: Array[String]): Unit = {
// 伴生对象可以访问类的私有方法和属性
val s3 = new Student3("Angelababy", 30)
s3.age = 29
println(s"${s3.age}")
// println(s"${s3.province}") 伴生类不能访问
}
}
方法的访问权限
通用于主构造器,辅构造器,以及普通方法
默认权限是共有的
private 作用域为类和其伴生对象
private [this] ,作用域为当前类中,伴生对象中无效
private [packageName] 指定包及其子包有效 包名的写法,直接写报名,不需要层级路径
主构造器上一样适用于该方法的访问权限
private [cn.ed.day03] 错误的
private [day03] 正确的
/*
* private 加在主构造器前面标识这个主构造器是私有的, 外部不能访问这个构造器
* */
class Student2 private (val name: String, var age: Int) {
var gender: String = _
// 辅助构造器, 使用def this
// 在辅助构造器中必须先调用类的主构造器
def this(name: String, age:Int, gender: String){
this(name, age)
this.gender = gender
}
}
object Student2 {
def main(args: Array[String]): Unit = {
val s1 = new Student2("laoYang", 18, "male")
println(s"${s1.gender}")
}
}
类包的访问权限
private 作用域为当前包及其子包 同 private [this]
private [packageName] 作用域为指定包及其子包
/*
* private[包名] class 放在类最前面, 是修饰类的访问权限, 也就是说类在某些包下不可见或不能访问
* private[edu360] class 代表student4在edu360包下及其子包下可以见, 同级包中不能访问
* */
private[this] class Student4(val name: String, private var age: Int) {
var xx: Int = _
}
object Student4{
def main(args: Array[String]): Unit = {
val s = new Student4("张三", 20)
println(s"${s.name}")
}
}
14.2.4 抽象类
在Scala中, 使用abstract修饰的类称为抽象类. 在抽象类中可以定义属性、未实现的方法(抽象方法)和具体实现的方法。
/*
* abstract修饰的类是一个抽象类
* */
abstract class Animal {
println("Animal's constructor ....")
// 定义一个name属性
val name: String = "animal"
// 没有任何实现的方法
def sleep()
// 带有具体的实现的方法
def eat(f: String): Unit = {
println(s"$f")
}}
类和对象,该怎么选择?
可以认为,对象本质上拥有类的所有特性,但是object没有构造器,必须是无参的。
如果不需要构造器封装数据,都优先使用object。
14.3 特质Trait
scala中没有interface implements
Trait(特质)相当于 java的接口。比接口功能更强大。特质中可以定义属性和方法的实现。
Scala的类只能够继承单一父类,但是可以实现(继承,混入)多个特质(Trait)使用的关键字是 with和extends
特质不能有任何的类参数,即传递给类的主构造器的参数。
trait PointTest(x: Int, y: Int) // 编译不过
trait T1 {
// 定义普通方法,有方法实现
def youcanfly()={
println("tai feng lai le you can fly")
}
}
trait T2 {
// 定义一个属性
val className: String = "NB大神班"
// 定义一个没有实现的方法,默认就是抽象方法
def teacherSay(name: String)
// 定义带有具体的实现的方法
def doSomething() = {
println("群主开始发红包了...")
}
}
动态混入特质
object test{
def main(args: Array[String]): Unit = {
// 动态混入特征,让类有了特质的方法
val t1 = new Teacher with T1
println(t1.youcanfly())
// 动态混入特质不能使用extends关键字,可同时混入多个特质
val t = new Teacher() with T1 with T2{
// 如果特质中有抽象方法,则必须重写该抽象方法,可以不使用override关键字
def teacherSay(name:String)={
println(s"最高face,${name}")
}
// 重写一个有具体的实现的方法,必须使用关键字override
override def doSomething() = {
println("群主:抢到红包继续接龙...")
}
}
println(t.teach("laozhao"))
println(t.doSomething)
println(t.youcanfly())
}
}
class Teacher{
}
比较:scala的trait和java中的interface的异同?
1,java的interface只定义方法名称和参数列表,不能定义方法体。而trait则可以定义方法体。
2,在java中实现接口用implements,而在scala中,实现trait用extends和with。
3,java的interface和scala的trait的最大区别是,scala可以在一个class实例化的时候动态混入trait。
用特质还是用抽象类??
1,优先使用特质。一个类可以扩展多个特质,但却只能扩展一个抽象类。
2,如果需要构造方法,使用抽象类。因为抽象类可以定义带参数的构造器,而特质不行。
14.4 继承
14.4.1 扩展类
在Scala中扩展类的方式和Java一样都是使用extends关键字
14.4.2 重写方法
在Scala中重写一个非抽象的方法必须使用override修饰符
14.4.3 示例
/ 定义一个抽象类
abstract class Animal(val age: Int) {
println("Animal`s main constructor invoked" + age)
//定义一个抽象方法
def run()
def breath(): Unit = {
println("呼吸氧气")
}
}
// 定义一个特质,定义一个普通方法
trait Fightable {
def fight(): Unit = {
println("用嘴咬")
}
}
// 定义一个特质,一个抽象方法
trait Flyable {
// 定义一个抽象方法
def fly()
}
定义一个bird类,实现多个特质
//在scala中,不论是继承还是实现特质,第一个都用extends关键字
class Bird extends Flyable with Fightable {
override def fly(): Unit = {
println("用翅膀飞")
}
}
object Bird {
def main(args: Array[String]): Unit = {
val b = new Bird
b.fly()
}
}
定义一个类,继承类,并实现多个trait
class Monkey(age: Int) extends Animal(age) with Flyable with Fightable {
//重写抽象的方法, 可以不加override关键字
def run(): Unit = {
// super()
println("跳着跑")
}
//重写非抽象的方法,必须加override关键字
override def breath(): Unit = {
println("猴子呼吸")
}
override def fly(): Unit = {
println("乘着筋斗云飞")
}
override def fight(): Unit = {
println("用棒子打")
}
println("monkey`s main constructor invoked" + age)
}
object Monkey {
def main(args: Array[String]): Unit = {
val a: Animal = new Monkey(100)
a.breath()
}
}
14.4.4 总结
在继承抽象类或者特质的时候,继承抽象类使用extends,实现特质用with和extends,当类只实现了特质的时候,第一个关键字必须是extends
实现类还是特质,抽象方法都必须被实现,而且可以不使用override关键字
重写非抽象方法,必须使用override关键字
特质支持动态混入,在类实例化的时候,可以通过with关键字混入特质。
特质不能有构造器,抽象类可以有。
样例类/样例对象
样例类:使用case关键字 修饰的类,重要的特征就是支持模式匹配,多例
样例object:使用case关键字修饰的对象,支持模式匹配,单例
case class 和 class的一些区别:
case class在初始化的时候,不用new,而普通类初始化时必须要new。
case class 重写了toString方法。
默认实现了equals和hashCode
case class 实现了序列化接口
case class 支持模式匹配(最重要的特征),所有case class 必须要有参数列表
有参数用case class,无参用case object
/*
* 样例类,使用case 关键字 修饰的类, 其重要的特征就是支持模式匹配
* */
case class Message(msg: String)
/**
* 样例object, 不能封装数据, 其重要特征就是支持模式匹配
*/
case object CheckHeartBeat
object TestCaseClass extends App{
// 可以不使用new 关键字创建实例
val msg = Message("hello")
println(msg.msg)
}
15 模式匹配
Scala有一个十分强大的模式匹配机制,可以应用到很多场合:如switch语句、类型检查等。
并且Scala还提供了样例类,对模式匹配进行了优化,可以快速进行匹配
模式匹配就是match 和 一系列的case 语法实现
其中,每一个case 里面就是一个匿名函数 =>
模式匹配的基本关键字 就是 match case
15.1 匹配字符串
import scala.util.Random
object CaseDemo01 extends App{
val arr = Array("YoshizawaAkiho", "YuiHatano", "AoiSola")
val name = arr(Random.nextInt(arr.length))
name match {
case "YoshizawaAkiho" => println("xx老师...")
case "YuiHatano" => println("oo老师...")
case _ => println("真不知道你们在说什么...")
}
}
15.2 匹配类型
import scala.util.Random
object CaseDemo02 extends App{
//val v = if(x >= 5) 1 else if(x < 2) 2.0 else "hello"
val arr = Array("hello", 1, 2.0, CaseDemo2)
val v = arr(Random.nextInt(arr.length))
println(v)
v match {
case x: Int => println("Int " + x)
case y: Double if(y >= 0) => println("Double "+ y) // if 守卫
case z: String => println("String " + z)
case CaseDemo02 => {
println("case demo 2")
//throw new Exception("not match exception")
}
case _ => throw new Exception("not match exception")
}
}
注意:case y: Double if(y >= 0) => ...
模式匹配的时候还可以添加守卫条件。如不符合守卫条件,将掉入case _中
15.3 匹配数组,元组,集合
object CaseDemo03 extends App{
val arr = Array(1, 3, 5)
arr match {
case Array(1, x, y) => println(x + " " + y)
case Array(0) => println("only 0")
case Array(0, _*) => println("0 ...")
case _ => println("something else")
}
val lst = List(3, -1)
lst match {
case 0 :: Nil => println("only 0")
case x :: y :: Nil => println(s"x: $x y: $y")
case 0 :: tail => println("0 ...")
case _ => println("something else")
}
val tup = (2, 3, 5)
tup match {
case (2, x, y) => println(s"1, $x , $y")
case (_, z, 5) => println(z)
case _ => println("else")
}
}
15.4 样例类
在Scala中样例类是一中特殊的类,可用于模式匹配。case class是多例的,后面要跟构造参数,case object是单例的,无需参数
import scala.util.Random
case class SubmitTask(id: String, name: String)
case class HeartBeat(time: Long)
case object CheckTimeOutTask
object CaseDemo04 extends App{
val arr = Array(CheckTimeOutTask, HeartBeat(12333), SubmitTask("0001", "task-0001"))
arr(Random.nextInt(arr.length)) match {
case SubmitTask(id, name) => {
println(s"$id, $name")
}
case HeartBeat(time) => {
println(time)
}
case CheckTimeOutTask => {
println("check")
}
}
}
15.5 Option类型
在Scala中Option类型样例类用来表示可能存在或也可能不存在的值(Option的子类有Some和None)。Some包装了某个值,None表示没有值
object OptionDemo {
def main(args: Array[String]) {
val map = Map("a" -> 1, "b" -> 2)
val v = map.get("b") match {
case Some(i) => i
case None => 0
}
println(v)
//更好的方式
val v1 = map.getOrElse("c", 0)
println(v1)
}
}
15.6 偏函数
被包在大括号内没有match的一组case语句是一个偏函数,它是PartialFunction[A, B]的一个实例,A代表输入参数类型,B代表返回类型,常用作输入模式匹配
object PartialFuncDemo {
def func1(num: String) : Int = num match {
case "one" => 1
case "two" => 2
case _ => -1
}
def func2: PartialFunction[String, Int] = {
case "one" => 1
case "two" => 2
case _ => -1
}
def main(args: Array[String]) {
println(func1("one"))
println(func2("one"))
}
}
偏函数本质上是由多个case语句组成的针对每一种可能的参数分别进行处理的一种“结构较为特殊”的函数,就是一个参数的函数。(Function1)
object PartialFunctionDemo2 {
def f:PartialFunction[Any,Int]={
case i:Int => i *10
case _ => i *10
}
def main(args: Array[String]): Unit = {
val arr = Array(1,3,5,"seven")
// arr.map{case t:Int =>t*10}
val collect: Array[Int] = arr.collect {
case t: Int
=> t * 10
}
println(collect)
arr.collect(f).foreach(println)
}
}
偏函数最常用的就是方法中要求传入偏函数的类型。