文章目录
(本文章参考自林子雨老师的spark编程基础scala版)
scala基本语法
1. 控制结构
1.1 if条件表达式
基本语法结构为:
if(表达式){
语句块1
}
else{
语句块2
}
执行if语句时,会首先检查if条件表达式是否为真,如果为真,就执行语句块1,如果为假,就执行语句块2。
scala> val x = 6
x: Int = 6
scala> if(x>0) println("This is a positive number" ) else println("This is a negative number")
This is a positive number
scala与java很相似,if结构中else是可选的而且if子句和else子句都支持多层结构,而且表达式中必须返回一个布尔型的字面量,或者直接给一个布尔型的字面量。例如:
scala> if(1) println(1)
<console>:12: error: type mismatch;
found : Int(1)
required: Boolean
if(1) println(1)
^
scala> if(true) println(1)
1
与java不同的是,scala的中的if表达式会返回一个值,因此,可以将if表达式赋值给一个变量,这与java中的三元操作符"?:"有些类似,例如:
scala> val a =if(6>0) 1 else -1
a: Int = 1
1.2 while循环
scala的while循环结构和java中的完全一样,包括while和do-while两种结构,只要表达式为真,循环体就会被重复执行,do-while循环至少被执行一次
while(表达式){
语句块
}
do{
循环体
}while(表达式)
和java一样,当只有一条语句的时候{}可以省略。
scala> var a =3
a: Int = 3
scala> while(a>0) println(a=a-1)//scala的赋值表达式返回的结果是Unit类型
()
()
()
scala> do println(1) while(false)//至少执行一次
1
1.3 for循环表达式
与java的for循环相比,scala的for循环在语法表示上有较大的区别,同时,for也不是while循环的一个替代者,而是提供了各种容器遍历的强大功能,用法也更灵活。for循环最简单的用法就是对一个容器的所有元素进行枚举,基本语法结构为:
for(变量<-表达式){语句块}
其中,”变量<-表达式“被称为”生成器(Generator)“,该处的变量不需要关键字var或val进行声明,其类型为后面的表达式对应的容器中的元素类型,每一次枚举,变量就被容器中的一个新元素所初始化。例如:
scala> val range = new Range(2,5,1)//2为起点,5为终点,1为步长,所以长度为5-2=3,元素为2、3、4
range: scala.collection.immutable.Range = Range 2 until 5//Range型容器会在后面讲
scala> for(i<-range) println(i)
2
3
4
for循环可对任何类型的容器类进行枚举。
for循环不仅仅可以对一个集合进行完全枚举,还可以通过添加过滤条件对某一个子集进行枚举,这些过滤条件被称为“守卫式(Guard)”,基本语法结构为:
for(变量<-表达式 if 条件表达式) 语句块
此时,只有当变量取值满足if后面的条件表达式时,语句块才被执行。例如:
scala> for(i<- 1 to 5 if i%2==0) println(i)
2
4
上面语句执行时,只输出1到5之间能被2整除的数。如果需要添加多个过滤条件,可以增加多个if语句,并用分号隔开。从功能上讲,上诉语句等同于:
scala> for(i<- 1 to 5)
| if(i%2==0) println(i)
2
4
可以通过添加多个生成器实现嵌套的for循环,其中,每个生成器之间用分号隔开,例如:
scala> for(i<- 1 to 5;j<- 1 to 3) println(i*j)
1
2
3
2
4
6
3
6
9
4
8
12
5
10
15
其中,外循环为1到5,内循环为1到3。与单个生成器类似,在多个生成器中,每个生成器可以通过if子句添加守卫式进行条件过滤。
for循环还可以在每次执行的时候创造一个值,然后将包含了所有产生值的容器作为对象为for循环表达式的结果返回。为了做到这一点,只需要在循环体前加上yield关键字,即for结构为:
for(变量<-表达式) yield {语句块}
其中yield后的语句块中最后一个表达式的值作为每次循环的返回值,例如:
scala> val r = for(i<-Array(1,2,3,4,5) if i%2==0) yield{println(i);i}
2
4
r: Array[Int] = Array(2, 4)
执行结束后,r为包含元素2和4的新数组。这种带有yield关键字的for循环,被称为“for推导式”。也就是说,通过for循环遍历一个或多个集合,对集合中的元素进行“推导”,从而计算得到新的集合,用于后续的其他处理。
1.4 对循环的控制
为了提前终止整个循环或者跳到下一个循环,java提供了break和continue两个关键字,但是,scala没有提供这两个关键字,而是通过一个名称为Breaks的类来实现类似的功能,该类位于包scala.util.control下,Breaks类有两个方法用于对循环结构进行控制,即breakable和break,通常都是放在一起配对使用,其基本使用方法如下:
breakable{
…
if(…)break
…
}
即将需要控制的语句块作为参数放在breakable后面,然后,其内部在某个条件满足时调用break方法,程序将跳出breakable方法。通过这种通用的方式,就可以实现java循环中的break和continue功能。下面通过一个例子来说明,在Idea中创建一个TestBreak.scala,内容如下:
import util.control.Breaks._
/**
* Name:BXF
* Description:
* Date:2021/7/8
**/
object TestBreak {
def main(args: Array[String]): Unit = {
val array = Array(1,3,10,5,4)
breakable(//这里的()可以换成{},暂时没发现区别
for(i<-array){
if(i>5)break()
println(i)
}
)
}
}
//上面的语句将输出1,3
import util.control.Breaks._
/**
* Name:BXF
* Description:
* Date:2021/7/8
**/
object TestBreak {
def main(args: Array[String]): Unit = {
val array = Array(1,3,10,5,4)
for(i<-array) {
breakable {//这里必须用{},使用()无法使用println()
if (i > 5) break
println(i)
}
}
}
}
//上面的语句将输出1,3,5,4
2. 数据结构
scala中常用的数据结构有数组(Array)、元组(Tuple)、列表(List)、映射(Map)、集合(Set)等
2.1 数组
数组(Array)是一种可变的、可索引的、元素具有相同类型的数据集合,它是各种高级语言中最常用的数据结构。scala提供了参数化类型的通用数组类Array[T],其中,T可以是任意的scala类型。scala数组与java数组是一一对应的。即scala的Array[Int]可看作java的Int[],Array[Double]可看作java的Double[],Array[String]可看作java的String[]。可以通过显式指定类型或者通过隐式推断来实例化一个数组,例如:
scala> val intValueArr = new Array[Int](3)
intValueArr: Array[Int] = Array(0, 0, 0)
scala> val myStrArr = Array("BigData","Hadoop","Spark")
myStrArr: Array[String] = Array(BigData, Hadoop, Spark)
上面的通过显式给出类型参数Int定义一个长度为的3的整型数组,数组的每个元素默认初始化为0.第二行省略了数组类型,而通过具体的3个字符串来初始化,scala自动推断出为字符串数组,因为scala会选择初始化元素的最近公共类型作为Array的参数类型。需要注意的是,第二行中没有像java那样使用new关键字来生成一个对象,实际是因为使用了scala中的伴生对象的apply方法。
另外,不同于java的方括号,scala使用圆括号来访问数组元素,索引也是从零开始。
需要注意的是,尽管两个数组变量都用val关键字进行定义,但是,这只是表明这两个变量不能再指向其他的对象,而对象里面的元素是可以被改变的,因此可以对数组内容进行改变。
scala> myStrArr = Array("a","b")
<console>:12: error: reassignment to val
myStrArr = Array("a","b")
^
scala> myStrArr(0)="a"
scala> myStrArr
res11: Array[String] = Array(a, Hadoop, Spark)
既然Array[T]类是一个通用的参数化类型,那么就可以很自然地通过给定T也为Array类型来定义多维数组。Array提供了函数ofDim来定义二维和三维数组,用法如下:
scala> val myMatrix = Array.ofDim[Int](3,4)
myMatrix: Array[Array[Int]] = Array(Array(0, 0, 0, 0), Array(0, 0, 0, 0), Array(0, 0, 0, 0))
scala> val myMatrix = Array.ofDim[String](3,2,4)
myMatrix: Array[Array[Array[String]]] = Array(Array(Array(null, null, null, null), Array(null, null, null, null)), Array(Array(null, null, null, null), Array(null, null, null, null)), Array(Array(null, null, null, null), Array(null, null, null, null)))//3个二维数组,每个二维数组有2行4列
2.2 元组
scala元组是对多个不同类型对象的一种简单封装。scala提供了TupleN类(N的范围为1~22),用于创建一个包含N个元素的元组。构造一个元组的语法很简单,只需把多个元素用逗号分开并用圆括号包围起来就可以了。例如:
scala> val tuple = ("Bigdata",2015,45.0)
tuple: (String, Int, Double) = (Bigdata,2015,45.0)
可以使用下划线“_”加上从1开始的索引值,来访问元组的元素,还可以一次性提取出元组中的元素并赋值给变量,但是tuple里面的元素无法被修改。
scala> tuple._1
res12: String = Bigdata
scala> val (t1,t2,t3) = tuple
t1: String = Bigdata
t2: Int = 2015
t3: Double = 45.0
scala> var tuple = ("Bigdata",2015,45.0)
tuple: (String, Int, Double) = (Bigdata,2015,45.0)
scala> tuple._1 = "aaa"
<console>:12: error: reassignment to val
tuple._1 = "aaa"
2.3 列表
列表(List)是不可变的对象序列,被定义在scala.collection.immutable包,这是一个抽象类,无法被实例化,但是可以调用中它的apply方法创建实例。不同于java.util.List,scala的List一旦被定义,其值就不能被改变。
对于包括List在类的所有容器类型,如果没有显示指定元素类型,scala会自动选择所有初始值的最近公共类型来作为元素的类型。因为scala的所有对象都来自共同的根Any。
scala> val x = List(1.0,3.4,"hadoop")
x: List[Any] = List(1.0, 3.4, hadoop)
列表有头和尾的概念,可以分别使用head和tail方法来获取,head返回的是列表第一个元素的值,tail返回的是除了第一个元素以外的其他值构成的新列表。
scala> x.head
res31: Any = 1.0
scala> x.tail
res32: List[Any] = List(3.4, hadoop)
列表常用的操作符有“::”、“+:”、“:+”,前两个都是右结合的,最后一个是左结合的,他们不会改变列表里的值,只会产生一个新的列表
scala> val list=List(1,2,3)
list: List[Int] = List(1, 2, 3)
scala> "spark"::list//右结合,等价于list.::("spark")
res55: List[Any] = List(spark, 1, 2, 3)
scala> "spark"+:list//右结合,等价于list.+:("spark")
res56: List[Any] = List(spark, 1, 2, 3)
scala> list:+"spark"//左结合,等价于list.:+("spark")
res54: List[Any] = List(1, 2, 3, spark)
scala> list.::("spark")
res60: List[Any] = List(spark, 1, 2, 3)
scala> list.+:("spark")
res59: List[Any] = List(spark, 1, 2, 3)
scala> list.:+("spark")
res58: List[Any] = List(1, 2, 3, spark)
scala> val intlist = 1::2::3::Nil
intlist: List[Int] = List(1, 2, 3)//Nil不能省略,因为“::”是右结合的
注意“:”号的位置,这个":"号在哪边list就在哪边
列表除了head和tail操作的时间复杂度是O(1)其他的诸如按索引访问的操作都需要从头到尾开始遍历,时间复杂度是O(n),为实现所有操作都是常数时间,可以使用向量(Vector),用法与List差不多,我目前已知的一种区别是Vector无法使用“::”进行。
scala> val x = Vector("aaa",3)
x: scala.collection.immutable.Vector[Any] = Vector(aaa, 3)
scala> x::"aaaaa"
<console>:13: error: value :: is not a member of String
x::"aaaaa"
2.4 Range
林子雨老师的Spark编程基础(Scala版) p35
2.5 集合
林子雨老师的Spark编程基础(Scala版) p36
2.6 映射
林子雨老师的Spark编程基础(Scala版) p36
3. 类方法和函数
3.1 类方法
def 方法名(参数列表): 返回结果类型={方法体}
上面是scala定义类方法的基本语法,这里只说明几点我认为比较重要的地方
①这里不需要依靠return语句来为方法体返回一个值;对于scala而言,方法里面的最后一个表达式的值就是方法的返回值。
这里可以看到scala是可以用return的,但是必须要指定返回值的类型,所以我们完全没有必要去用。
class Test {
var value = 0
def increment (step:Int):Unit={
value+=step
}
def current():Int = {value}//用return的时候必须指定返回值类型,scala不建议用return
}
②scala中有类似于java中的getter和setter方法,分别是value和value_=
③当value和value_=方法成对出现的时候,它允许用户去掉下划线,而采用类似赋值表达式的形式。
class Counter {
private var privateValue = 0
def value= privateValue
def value_= (newValue:Int){//“value_=”是一个整体,它只是一个函数名,这里只是省略了返回结果类型和等号,与下面注释掉的内容相同
if(newValue>0) privateValue = newValue
}
// def value_= (newValue:Int):Unit={
// if(newValue>0) privateValue = newValue
// }
def increment(step:Int){
value+=step//value不是一个函数名吗?为什么这里可以这样操作?
}
def current: Int = value
}
object TestCounter{
def main(args: Array[String]): Unit = {
val mycount = new Counter
mycount increment 5//中缀操作符,下面有说的
println(mycount.value)
mycount.value=(3)//去掉括号也可以
println(mycount.current)
}
}
//运行结果为5和3
对于这里的increment方法中的“value+=step”我相信大家或多或少都会感到有点疑惑,我个人的理解是这样的:
value+=step可以看作value=value+step,右边的“value”调用方法value得到当前privateValue的值,然后将value加上step得到一个值,而左边的“value=”相当于调用了value_=,所以就将这个值赋给了privateValue。
④scala中,方法参数前不能加var与val限定,所有参数均是不可变类型
⑤无参数的方法可以省略括号。
⑥若无参数的方法在定义时省略了括号,那么在调用时也不能带有括号。
⑦若无参数的方法在定义时带有括号,则调用时可以带括号也可以不带。
⑧在调用方法时,方法名后面的圆括号可以用{}来代替
⑨若方法只有一个参数,可以省略点号".",采用中缀操作符即:调用者 方法名 参数
scala> 3+5
res1: Int = 8
scala> 3.+(5)//3是调用者,+是方法5是参数
res2: Int = 8
⑩当方法的返回结果可以从最后的表达式推断出时,方法定义中可以省略结果类型,同时如果方法体只有一条语句,还可以省略方法体两边的大括号。
⑪当方法的返回类型为Unit,可以同时省略返回结果类型和等号,如果你这么做了,那么就算方法体里只有一条语句也不可以省略。
⑫递归的函数必须指定返回的类型。
scala> def pwt(x:Int): Int = {if(x==0) 1 else 2*pwt(x-1)}
pwt: (x: Int)Int
scala> def pwt(x:Int) = {if(x==0) 1 else 2*pwt(x-1)}//人类很容易推断出返回结果是Int型
<console>:12: error: recursive method pwt needs result type
def pwt(x:Int) = {if(x==0) 1 else 2*pwt(x-1)}
3.2 函数
scala中的函数也有“类型”和“值”的区分,“类型”需要明确函数接受多少个参数、每个参数的类型以及函数返回结果的类型,也就是说函数的输入类型以及返回值的类型一起构成了函数类型。
定义整型:
val num:Int = 5
定义函数:
val func:(Int)=>Int = {(value)=>{value+1}} //等号前面是类型,后面是值,只有一个参数时()可省,只有一个语句时{}可省
val func:Int=>Int = {value=>value+1}
val func = {(value:Int)=>{value+1}} //省略返回类型因为可以推断出来
val func = {value:Int=>value+1}
val func = (value:Int)=>value+1
而函数的“值”则是函数的一个具体实现,所以函数的更一般的定义方式如下:
val(var) 函数名:(类型1,类型2, … 类型n)=>(返回值类型) = {(参数1:类型1,参数2:类型2, … ,参数n:类型n)=>{函数体}}
scala> val func:(Int,Int)=>Int = {(x:Int,y:Int)=>{x+y}}
func: (Int, Int) => Int = $$Lambda$1182/934352027@1187dc82
scala> val pwt:(Int)=>Int = {(x:Int)=>{if(x==0) 1 else 2*pwt(x-1)}}
pwt: Int => Int = $$Lambda$1188/2118050088@7d64a960
val(var) 函数名:(类型1,类型2, … ,类型n)=>(返回值类型) = {(参数1,参数2, … ,参数n)=>{函数体}}
scala> val func:(Int,Int)=>Int = {(x,y)=>{x+y}}
func: (Int, Int) => Int = $$Lambda$1189/1057418208@1752e45c
scala> val pwt:(Int)=>Int = {(x)=>{if(x==0) 1 else 2*pwt(x-1)}}
pwt: Int => Int = $$Lambda$1190/1321185349@77a2688d
val(var) 函数名 = {(参数1:类型1,参数2:类型2, … ,参数n:类型n)=>{函数体}}
scala> val func= {(x:Int,y:Int)=>{x+y}}
func: (Int, Int) => Int = $$Lambda$1191/1958462067@370a8b6e
scala> val pwt= {(x:Int)=>{if(x==0) 1 else 2*pwt(x-1)}}//哦吼,推断不出来返回类型了
<console>:12: error: recursive value pwt needs type
val pwt= {(x:Int)=>{if(x==0) 1 else 2*pwt(x-1)}}
我知道的就这三种方式
Lambda表达式
定义方式为:(参数)=>表达式 //若参数只有一个括号可以省略
scala> (value:Int)=>value+1
res0: Int => Int = $$Lambda$1073/1847865717@2dfeb141
当函数的每个参数在函数字面量内仅出现一次,记住仅出现一次,可以省略“=>”,并用下划线作为参数的占位符来简化函数字面量的表示,第一个下划线代表第一个参数,第二个下划线代表第二个参数,依次类推。
import java.io.File
import collection.mutable.Map
import scala.io.Source
/**
* Name:BXF
* Description:
* Date:2021/7/8
**/
object WordCount {
def main(args: Array[String]): Unit = {
val file = new File("datas/1.txt")
val results = Map.empty[String,Int]
val data = Source.fromFile(file)
val strs = data.getLines().flatMap(_.split(" "))//下划线代替
strs foreach {
word=>if(results.contains(word)) results(word)+=1 else results(word)=1
}
results foreach{
x=>println(x._1+" "+x._2)//出现了两次无法用下划线代替
}
}
}
//1.txt里面的内容
//hello spark
//hello world
//hello scala