scala基本语法(主要是函数和类方法)

(本文章参考自林子雨老师的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
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值