2018年第42周-scala入门-基本语法

让事情变得更加简单方便, 注意是简单方便, 而事情内在的复杂性并没有降低.

变量定义

变量是一种使用方便的占位符,用于引用计算机内存地址。
Scala有两种变量,valvar。val类似于java的final变量。var则为非final变量。
在scala程序中, 通常建议使用val, 也就是常量, 因为类似于spark的大型复杂系统中, 需要大量的网络传输数据, 如果使用var, 可能会担心值被错误的更改.
在Java的大型复杂系统的设计和研发中, 也使用了类似的特性, 我们通常会将传递给其他模块/组件/服务的对象, 设计成不可变类(Immutable Class). 在里面也会使用java的常量定义, 比如final, 阻止变量的值被改变. 从而提高系统的健壮性(robust, 鲁棒性), 和安全性.
简单的说, 就是让事情变得不可能发生, 那这错误就永远不会发生.

声明val变量

声明val变量来存放表达式的计算结果.
例如, val result = 1 + 1
后续这些常量是可以继续使用的, 例如, 2 * result
但是常量声明后, 是无法改变它的值的, 例如, result=1, 会返回error: reassignment to val 的错误信息.

声明var变量

如果要声明值可以改变的引用, 可以使用val变量.
例如, val myresult = 1, myresult =2

类型推断

无论声明val变量, 还是声明var变量. 都可以手动指定类型, 如果不指定的话, scala会自动根据值, 进行类型的推断, 这种称为类型推断(type inference)能力,它能让Scala自动理解你省略了的类型。.
例如, var some = 2.0
例如, val name: String = null
例如, val name: Any = "jc"
第一个会自动判断为浮点, 而第二三个, 变量类型可以定义为值的父类.

数据类型

数据类型,除了Unit、Nothing、Any、AnyRef,其他都是Java有的概念,值范围也一样。

数据类型 描述
Byte 8位有符号补码整数。数值区间为 -128 到 127
Short 16位有符号补码整数。数值区间为 -32768 到 32767
Int 32位有符号补码整数。数值区间为 -2147483648 到 2147483647
Long 4位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807
Float 32位IEEE754单精度浮点数
Double 64位IEEE754单精度浮点数
Char 16位无符号Unicode字符, 区间值为 U+0000 到 U+FFFF
String 字符串
Boolean 布尔类型
Unit 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。
Null null或空引用
Nothing Nothing类型在Scala的类层级的最低端;它是任何其他类型的子类型。
Any Any是所有其他类的超类
AnyRef AnyRef类是Scala里所有引用类(reference class)的基类

类型的加强版类型

scala使用很多加强类给数据类型增加了上百种增强的功能或函数.
例如, String类通过StringOps类型增强了大量的函数, "Hello".intersect("World")
例如, Scala还提供了RichInt, RichDouble, RichChar等类型, RichInt就提供了to函数, 1.to(10), 此处Int先隐式转换为RichInt, 然后再调用其to函数.

基本操作符

scala的算术操作符与Java的算术操作符也没有什么区别, 比如+, -, *, /, %等, 以及&, |, ^, >>, <<等.
但是, 在scala中, 这些操作符其实是数据类型的函数, 比如 1 + 1, 可以写做1.+(1)
例如, 1.to(10), 又可以写做 1 to 10
scala中没有提供++, --操作符,我们只能使用+=和-=, 比如counter=1, counter++ 是错误的, 必须写做counter +=1

控制流语句

if表达式

在scala中, if表达式是有值的, 就是if或者else中最后一行语句返回的值. 简单的理解就是scala不想参数传来传去, 约定由于配置, 所以就直接默认最后一句就是返回值, 在后面的函数也有所体现, 不用return, 直接最后一句就是返回值. 这样确实对比Java来说, 敲键盘的次数少了很多, 临时变量也不需要到处都是.
例如, val age = 30; if(age > 18) 1 else 0
可以将if表达式赋予一个变量, 例如, val isAdult = if(aget > 18) 1 else 0
另外一种写法, var isAdult=-1; if(age>18) isAdult=1 else isAdult = 0, 但是通常使用上一种写法.
还有一种多语句的写法:

if(age>18){   "adult" }else if(age > 12) "teenage" else "children"  

if表达式的类型推断

由于if表达式是有值的, 而if和else子句的值类型可能不同, 此时if表达式的值是什么类型呢? scala会自动进行推断, 取两个类型的公共父类型.
例如, if(age > 18) 1 else 0, 表达式的类型是Int, 因为1和0都是Int
例如, if(age > 18) "adult" else 0, 此时if和else的值分别是String和Int, 则表达式的值是Any, Any是String和Int的公共父类型.
如果if后面没有跟else, 则默认else的值是Unit, 也用()表示, 类似于java中的void或者null. 例如, val age = 12; if(age > 18) "adult", 此时就相当于if(age > 18) "adult" else()

语句终结符, 块表达式

默认情况下, scala不需要语句终结符, 默认将每一行作为一个语句
一行放多条语句: 如果一行要放多条语句, 则必须使用语句终结符
例如, 使用分号作为语句终结符, var a,b,c=0; if(a < 10){b = b+1; c=c+1}
通常来说, 对于多行语句, 还是会使用花括号的方式

if(a<10){       b = b + 1       c = c + 1   }  

块表达式: 块表达式, 指的就是{}中的值, 其中可以包含多条语句, 最后一条语句的值就是块表达式的返回值.
例如, var d=if(a<10){b=b+1; c+1}

循环

while do循环

while do循环: scala有while do循环, 基本语义与java相同.

var n = 10 while(n>0){   println(n);   n-=1 }

scala没有for循环

scala没有for循环, 只能使用while替代for循环, 或者使用简易版的for语句
简易版for语句(包括n):

var n=10; for(i <- 1 to n) println(i)

或者使用until, 表达式不达到上限, for(i <- 1 until n) println(i), 没执行一次pirntln(i), i会往上+1, 直至n停止(不包括n)
也可以对字符串进行变量, 类似于java的增强for循环, for(c <- "Hello World") print(c)

跳出循环语句

scala没有提供类似于java的break语句
但是可以使用boolean类型变量, return或者Breaks的break函数来替代使用.

import scala.util.control.Breaks._ breakable{   var n = 10   for(c <- "Hello World"){      if(n == 5) break;      print(c)      n -= 1   } }

高级for循环

多重for循环: 九九乘法

for(i <- 1 to 9; j <- 1 to 9){   if(j==9){    printf("%d * %d = %d", i,j,i*j)    println()   }else{    printf("%d * %d = %dt", i,j,i*j)   } }

if守卫: 取偶数

for(i <- 1 to 100 if i % 2 ==0 ) println(i)

for推导式: 构造集合  

for(i <- 1 to 10) yield i

函数

函数调用与apply()函数

先不看函数是如何定义的, 先使用起函数, 先体会, 后面再去理解函数.

函数调用方式

在scala中, 函数调用也很简单, 例如使用数学的函数:

scala> import scala.math._ import scala.math._  scala> sqrt(2) res0: Double = 1.4142135623730951   scala> pow(2,4) res2: Double = 16.0   scala> min(3,Pi) res4: Double = 3.0 

不同的一点是, 如果调用函数时, 不需要传递参数,则scala允许调用函数时省略括号, 例如, "Hello World".distinct

apply函数

scala中的apply函数是非常特殊的一种函数, 在scala的object中, 可以声明apply函数. 而使用"对象名()"的形式, 其实就是"对象名.apply()"的一种缩写. 通常使用这种方式来构造类的对象, 而不是使用"new 类名()"的方式(注意, 这里的对象名和类名我没搞错, 这个是伴生对象的特性, 后面会讲解).

例如, "Hello World"(6), 因为在StringOps类中有def apply(n: Int): Char的函数定义, 所以"Hello World"(6), 实际是"Hello World".apply(6)的缩写.
例如, Array(1,2,3,4), 实际上是用Array object的apply()函数来创建Array类的实例, 也就是一个数组.

定义函数  

在scala中定义函数时, 需要定义函数的函数名, 参数, 函数体.
我们的第一个函数如下所示:

def sayHello(name:String, age:Int)={   if(age>=18) {      printf("hi %s, you are a big boyn",name)      age   }   else {      printf("hi %s, you are a little boyn",name)      age   } } sayHello("jevoncode",29)

scala要求必须给出所有参数的类型,但是不一定给出函数返回值的类型, 只要右侧的函数体中不包含递归的语句, scala就可以自己根据右侧的表达式推断出返回类型.

单行函数

单行的函数: def sayHello(name: String)= print("Hello, "+name)

在代码块中定义函数体

如果函数体中有多行代码, 则可以使用代码块的方式包裹多行代码, 代码块中最后一行的方绘制就是整个函数的返回值. 与Java不同, 不是使用return返回值的.
比如下面的函数, 实现累加的功能:

def sum(n: Int)={   var sum=0;   for(i <-1 to n) sum+=i   sum }

递归函数

如果在函数体内递归调用函自身, 则必须手动给出函数的返回类型.
例如, 实现经典的斐波那契数列:
1 1 2 3 5 8 13
简单的说斐波那契数列就是一个数是前面两个数值之和的数列.
此函数求第n个(从0开始)斐波那契数列的值

def fab(n:Int): Int={   if(n<=1) 1   else fab(n-2)+fab(n-1) }

默认参数  

在scala中, 有时我们调用某些函数时, 不希望给出参数的具体值, 而希望使用参数自身默认的值, 此时就在定义函数时使用默认参数.

def sayHello(firstName: String, middleName: String = "William", lastName: String = "Croft") = firstName + " " + middleName + " " + lastName  \调用方式 scala> sayHello("a") res1: String = a William Croft  scala> sayHello("a","b") res2: String = a b Croft   scala> sayHello("a","b","c") res3: String = a b c 

如果给出的参数不够, 则会从左往右依次应用参数.

Java与scala实现默认参数的区别

public void sayHello(String firstName, String middleName, String lastName){     if(middleName == null)         middleName = "William";     if(lastName == null)         lastName = "Croft";      System.out.println(firstName + " " + middleName + " " + lastName); }  

对比上面的scala的代码,

  1. 从代码上对比, 代码量少很多.
  2. 调用Java的sayHello函数需全部字段传入, 如sayHelle(a,null,null), 这就显得有点麻烦

虽然Java有这样的缺点, 但是Java提供了代理模式, 可以通过注解+proxy的方式动态的给参数注入值, 代理模式提供很大的灵活性. 但写代码的便利性还不如scala, 因为无聊使不使用代理, 传参时都得写全.

带名参数

在调用函数时, 也可以不按照函数定义的参数顺序来传递参数, 而是使用带名参数的方式来传递.

sayHello(firstName = "Mick", lastName="Nina", middleName="Jack")

还可以混合使用未命名参数和带名参数, 但是未命名参数必须排在带名参数的前面.

sayHello("Mick", lastName="Nina", middleName="Jack")

变长参数

在scala中, 有时我们需要将函数定义为参数个数可变的形式, 则可以使用变长参数来定义函数.

def sum(nums: Int*)={   var res = 0   for(num <-nums) res+=num   res } \调用方式 scala> sum(1,2,3,4,5,6) res6: Int = 21 

使用序列调用变长参数

在如果想要将一个已有的序列直接调用变长参数函数, 则不对的. 比如val s=sum(1 to 5). 此时需要使用scala特殊的语法将参数定义为序列, 让scala解析器能够识别.

val s = sum(1 to 5:_*)

案例: 使用递归函数实现累加

def sum2(nums: Int*): Int={   if(nums.length == 0) 0   else nums.head + sum2(nums.tail:_*) }

过程

在scala中, 定义函数时, 如果函数体直接包含在花括号里面, 而没有使用=链接, 则函数返回值类型就是Unit. 这样的函数就被称为过程. 过程通常用于不需要返回值的函数.
过程还有一种写法, 就是将函数的返回值类型定义为Unit.

def sayHello(name: String) = "Hello, " + name     //非过程 def sayHello(name: String) {print("Hello, "+ name); "Hello, " + name}    //过程 def sayHello(name: String): Unit = "Hello, " + name            //过程    

就是概念的定义,暂时还没看到这概念带来思想的升华.  

lazy值

在scala中, 提供lazy值的特性, 也就是说, 如果将一个变量声明为lazy, 则有在第一次调用该变量时, 变量对于的表达式才会发生计算.这种特性对于特别耗时的计算操作特别有用, 比如打开文件进行IO, 进行网络IO等.

import scala.io.Source._ lazy val lines = fromFile("/home/gucci/Desktop/helloworld.txt").mkString

即使文件不存在, 也不会报错, 只有第一个使用变量时会报错, 证明了表达式计算的lazy特性.

scala> val lines2 = fromFile("/home/gucci/Desktop/helloworld2.txt").mkString java.io.FileNotFoundException: /home/gucci/Desktop/helloworld2.txt (No such file or directory)   at java.io.FileInputStream.open0(Native Method)   at java.io.FileInputStream.open(FileInputStream.java:195)   at java.io.FileInputStream.<init>(FileInputStream.java:138)   at scala.io.Source$.fromFile(Source.scala:91)   at scala.io.Source$.fromFile(Source.scala:76)   at scala.io.Source$.fromFile(Source.scala:54)   ... 36 elided  scala> lazy val lines = fromFile("/home/gucci/Desktop/helloworld2.txt").mkString lines: String = <lazy>    scala> lines java.io.FileNotFoundException: /home/gucci/Desktop/helloworld2.txt (No such file or directory)   at java.io.FileInputStream.open0(Native Method)   at java.io.FileInputStream.open(FileInputStream.java:195)   at java.io.FileInputStream.<init>(FileInputStream.java:138)   at scala.io.Source$.fromFile(Source.scala:91)   at scala.io.Source$.fromFile(Source.scala:76)   at scala.io.Source$.fromFile(Source.scala:54)   at .lines$lzycompute(<console>:14)   at .lines(<console>:14)   ... 36 elided  

异常

在scala中, 异常处理和捕获机制与Jav是非常相似的.

try{   throw new IllegalArgumentException("x should not be negative")  }catch{   case _:IllegalArgumentException => println("Illegal Argument!") }finally{   print("release resource!") }

除了异常捕获, 这里还有模式匹配和匿名函数知识点, 后面高级语法会讲到.

数据结构

Array

在scala中, Array代表的含义与Java中类似, 也是长度不可改变的数据. 此外, 由于scala与Java都是运行在JVM中, 双方可以互相调用, 因此scala数组的底层实际上是Java数组. 例如字符串数组的底层就是Java的String[], 整数数组底层就是Java的Integer[]
数组初始化后, 长度就固定下来了, 而且元素全部根据其类型初始化. Int就是0, String就是null

val a = new Array[Int](10) val a = new Array[String](10)

可以直接使用Array()创建数组, 元素类型自动推断

val a = Array("hello", "world") a(0) = "hi"

ArrayBuffer

在Scala中, 如果需要类似于Java的ArrayList这种长度可变的集合类, 则可以使用ArrayBuffer

// 如果不想每次都是用全限定名, 则可以预先导入ArrayBuffer类 import scala.collection.mutable.ArrayBuffer //使用ArrayBuffer()的方式可以创建一个空的ArrayBuffer val b = ArrayBuffer[Int]() //使用+=操作符, 可以添加一个元素, 或者多个元素 b+=1 b+=(2,3,4,5) //使用++=操作符, 可以添加其他集合中的所有元素   b++=Array(6,7,8,9,10) //使用trimEnd()函数, 可以从尾部截断指定个数的元素 b.trimEnd(5) //使用insert()函数可以在指定位置插入元素 //但这种操作效率很低, 因为需要移动指定位置后的所有元素   b.insert(5,6) b.insert(6,7,8,9,10) //使用remove()函数可以移除指定位置的元素 b.remove(1) b.remove(1,3) //Array与ArrayBuffer可以互相进行转换 b.toArray a.toBuffer

遍历Array和ArrayBuffer

//使用for循环和until遍历Array/ArrayBuffer //使用until是RichInt提供的函数 for(i <- 0 until b.length)   println(b(i))  //跳跃遍历Array/ArrayBuffer for(i <- 0 until (b.length,2))   println(b(i))  //从尾部遍历Array/ArrayBuffer for(i <-(0 until b.length).reverse)   println(b(i))  //使用"增强for循环"遍历Array/ArrayBuffer for(e <- b)   println(e)

数组常见操作

//元素求和 val a = Array(1,2,3,4,5) val sum = a.sum  //获取数组最大值 val max = a.max  //对数组进行排序 scala.util.Sorting.quckSort(a)  //获取数组中所有元素内容 a.mkString a.mkString(",") a.mkString("<",",",">")  //toString函数 a.toString b.toString

使用yield和函数式编程(初体验, 暂不需要理解)转换数组

//对Array进行转换, 获取的还是Array val a = Array(1,2,3,4,5) val a2 = for(ele <- a) yield ele * ele  //对ArrayBuffer进行转换, 获取的还是ArrayBuffer  val b = ArrayBuffer[Int]() b+=(1,2,3,4,5) val b2 = for(ele<-b) yield ele* ele  //结合if守卫, 仅转换需要的元素 val a3 = for(ele <-b if ele % 2 == 0) yield ele * ele  //使用函数式编程转换数组(通常使用是一种方式) a.filter(_%2==0).map(2*_) a.filter{_%2==0}map{2*_} 

算法案例: 移除第一个负数之后的所有负数

// 构建数组 val a = ArrayBuffer[Int]() a += (1,2,3,4,5,-1,-3,-5,-9)  //每发现一个负数(不包括第一个负数), 进行移除, 但这个性能比较差, 需多次移动数组 var isFoundFirstNegative = false var arrayLength = a.length var index = 0 while(index < arrayLength){   if(a(index)>0){     index+=1    }else{      if(!isFoundFirstNegative){isFoundFirstNegative = true; index+=1}      else{ a.remove(index); arrayLength-=1}    } }

算法案例: 移除第一个负数之后的所有负数(改良版)

// 构建数组 val a = ArrayBuffer[Int]() a += (1,2,3,4,5,-1,-3,-5,-9)  //记录所有不需要移除的元素的所有, 稍后一次性移除所有需要移动的元素 //性能比较高, 数组内的元素迁移只需要执行一次即可 var isFoundFirstNegative = false val keepIndexes = for(i<-0 until a.length if !isFoundFirstNegative || a(i) >=0) yield{   if(a(i) < 0) isFoundFirstNegative = true   i } for(i <-0 until keepIndexes.length) {a(i) = a(keepIndexes(i))} a.trimEnd(a.length - keepIndexes.length) 

创建Map

//创建一个不可变的Map val ages = Map("Jevoncode"->29, "Jen"->25, "Jack"->23) ages("Jevoncode") = 30    //出错value update is not a member of scala.collection.immutable.Map[String,Int]   //创建一个可变的Map val ages = scala.collection.mutable.Map("Jevoncode"->29, "Jen"->25, "Jack"->23) ages("Jevoncode") = 30  //使用另外一个种方式定义Map元素 val ages = Map(("Jevoncode",29),("Jen",25),("Jack",23))  //创建一个空的HashMap val ages = new scala.collection.mutable.HashMap[String,Int] //添加元素 scala> ages += "jevoncode" ->30 res6: ages.type = Map(jevoncode -> 30)  scala> ages res7: scala.collection.mutable.HashMap[String,Int] = Map(jevoncode -> 30)  scala> ages += "jevoncode2" ->29 res8: ages.type = Map(jevoncode2 -> 29, jevoncode -> 30) 

访问Map的元素

//获取指定key对应的value, 如果key不存在, 会报错 scala> val jcAge = ages("jc") java.util.NoSuchElementException: key not found: jc   at scala.collection.MapLike.default(MapLike.scala:232)   at scala.collection.MapLike.default$(MapLike.scala:231)   at scala.collection.AbstractMap.default(Map.scala:59)   at scala.collection.mutable.HashMap.apply(HashMap.scala:65)   ... 36 elided  scala> val jcAge = ages("jevoncode") jcAge: Int = 30   //使用container函数检查key是否存在 val jcAge = if(ages.contains("jc")) ages("jc") else 0  //getOrElse函数 val jcAge = ages.getOrElse("jc",0) 

修改Map的元素

//更新Map的元素 ages("jevoncode") = 29   //增加多一个元素 ages += ("Mike"->35, "Tom"->40)  //移除元素 ages -="Mike"  //更新不可变的Map val ages2 = ages + ("Mike"->36, "Tom"->41)  //移除不可变Map的元素 val ages3 = ages-"Tom"  

遍历Map

// 遍历map的entrySet for((key,value) <- ages) println(key + " " +value)  // 遍历map的key for(key <- ages.keySet) println(key)  // 遍历map的value for(value <- ages.values) println(value)  //生成新map, 反转key和value for((key,value) <- ages) yield(value,key) 

SortedMap和LinkedHashMap

//SortedMap可以自动对Map的key的排序 val ages = scala.collection.immutable.SortedMap("jevoncode"->29, "alice"->15, "jen"->25)  //LinkedHashMap可以记住插入entry的顺序 val ages = new scala.collection.mutable.LinkedHashMap[String,Int] ages("jevoncode")=30 ages("alice")=15     ages("jen")=25

元组Tuple

//简单Tuple val t=("jevoncode",29)  //访问Tuple t._1  //zip操作, zip有拉链的意思 val names = Array("jevoncode","jack","mike") val ages = Array(29,24,26) val nameAges = names.zip(ages) for((name,age) <- nameAges) println(name + ": "+age)

以上

点下最先开头的那句话:

让事情变得更加简单方便, 注意是简单方便, 而事情内在的复杂性并没有降低.

我个人体会就是scala把java一些繁琐的东西给简化, 还有常用的功能也写进去. 如 1 to 10数列, 元组Tuple, 还有后续的可直接定义object, extends"接口"等等.
还有就是让语言更加语义化, 这或许对熟悉英语的人才更加有体会吧, 如:

var n=10; for(i <- 1 to n) println(i)

如多个"接口", 用with链接.

最后引用知乎上《Scala 是一门怎样的语言,具有哪些优缺点?》那几段话

Java的模块化,给企业、大公司带来了第一道曙光,模块化之后,这些公司不再给程序员一整个任务,而是一大块任务的一小块。接口一定义,虚拟类一定义,换谁上都可以,管你是保罗·格雷厄姆这样的明星程序员,还是一个新来的大学生,程序员不听话就直接开除,反正模块化之后,开除程序员的成本大大降低,这也是为什么谷歌、甲骨文(这货最后收购了Java)一类的公司大规模的推崇Java,还一度提出了模块化人事管理的理念(把人当模块化的积木一样随时移进移出)。
过度企业化后,这延展出了Java的第二个特性,束缚手脚。保罗·格雷厄姆在《黑客与画家》中写道,Java属于B&D(捆绑与束缚)类型的语言。为何束缚手脚?因为要让新手和明星程序员写出类似质量的代码,尽可能的抹消人的才华对程序的影响。不同于C/C++,老手和新手写出的Java代码不会有上百倍的耗时差距。但同样也导致了Java的一个弱点——不容易优化。很多优化Java代码的程序员必须要对JVM(虚拟机)进行优化,实际上增大了很多任务难度。
Scala不把程序员当傻子。

在这就不评判这几段话观点是否政治正确, 因为不同的立场, 问题的答案就有不同. 但我想表达是, 这几段话给出了学习scala的思路, 它是一门靠经验积累的语言, 直白的说就是语法少了很多条条框框, 让程序员更自由.

脚本宝典为你提供优质服务
脚本宝典 » 2018年第42周-scala入门-基本语法

发表评论

提供最优质的资源集合

立即查看 了解详情