快学Scala

Posted by BY KiloMeter on February 28, 2019

Scala基础

声明值和变量

Scala声明变量有两种方式,val和var

val/var 变量名 [: 变量类型] = 变量值

val定义的值不可改变,类似于常量

var和val只是标识了引用本身能否指向不同的对象,类似于Java中final定义的引用,不能指向别的引用对象,但是引用对象的属性是可变的。

val的标识并不意味着对象的属性是不变的。因此为了减少对象可变性引起的BUG,val最好指向不可变对象。

var和val声明变量时都必须初始化,变量类型可以省略,解析器会根据值进行判断。

常用类型

Scala有八种数据类型,分别是:Byte,Char,Short,Int,Long,Float,Double,Boolean

Scala和Java不同的是,Scala中并没有区分引用类型和基本类型,上面这些类型都是对象,可以调用相应的方法,String使用的是java.lang.String,由于是不可变集合,在Scala中会隐式转换成Scala.collection.immutable.StringOps,该类型提供了许多方法,此外,其他类型也提供了相应的Rich* 类型,如RichInt,RichChar,为基本类型提供了丰富的操作。

Scala中没有强制类型转换,类型的转换需要通过方法来进行

上面提到Scala中没有区分基本类型和引用类型,因此所有的值都是对象,所有的类都继承自一个统一的根类型Any,Scala中还定义了几个底层类,比如NULL和Nothing

1、NULL是所有引用类型的子类型,而Nothing是所有类型的子类型,NULL类只有一个实例对象,就是null,类似于Java中的null引用,null可以赋值给任意引用类型,但不能赋值给值类型。

2、Nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于 Nothing 是其他任意类型的子类,还能跟要求返回值的方法兼容。

3、Unit 类型用来标识过程,也就是没有明确返回值的函数。 由此可见, Unit类似于 Java 里的 void。 Unit 只有一个实例, (),这个实例也没有实质的意义 。

调用函数和方法

除了方法, Scala 还提供了函数,比如数学函数。

使用数学函数需要和Java一样导入包,需要引入函数包 import 包名._ _是通配符, 等同于 java 中的 *

调用静态方法:**Scala 中没有静态方法, 一般通过单例对象或者伴生对象进行实现。 **

apply、 update 方法

apply 方法是调用时可以省略方法名的方法。 用于**构造和获取元素 **

print("Hello"(4)) //打印出 'o' 该语句等价于"hello".apply(4)

update同上,用于元素的更新

option 类型

Scala 为单个值提供了对象的包装器, 表示为那种可能存在也可能不存在的值。只有两个有效的子类对象,一个是 Some,表示某个值,另外一个是None,表示为空,通过 Option 的使用,避免了使用 null、空字符串等方式来表示缺少某个值的做法。

控制结构和函数

**在 Scala 中, 几乎所有语法结构都有值。 **

表达式的值取决于语句块的最后一句。

If else表达式

如果 if 或者 else 返回的类型不一样, 就返回 Any 类型(所有类型的公共超类型,相当于Java中的object)

如果缺少一个判断, 什么都没有返回, 但是 Scala 认为任何表达式都会有值, 对于空值, 使用 Unit 类, 写做()

While表达式

Scala 提供和 Java 一样的 while 和 do 循环, 与 If 语句不同, While 语句本身没有值,即整个 While 语句的结果是 Unit 类型的()。

**scala 并没有提供 break 和 continue 语句来退出循环,如果需要 break,可以通过几种方法来做 1、使用 Boolean 型的控制变量 2、使用嵌套函数,从函数中return 3、 使用 Breaks 对象的 break 方法。 **

for表达式

for表达式的用法相比于Java来说,提供了更丰富的功能,像下面 变量名 <- 集合的表达式称为生成器表达式

for (i <- 1 to 2;j <- 1 to 2;k <- 1 to 2) println((i*100+j*10+k)+"")

根据分号的个数进行多重循环,循环由外至内。

循环中还可以引入变量

for (i <- 1 to 10;from = i*10) println(from+"")

scala也提供了条件判断,在scala中的条件判断叫做保护式,也叫守卫

for (i <- 1 to 3;j <- 1 to 3 if i!=j) println((i*10+j)+"")

也能有多个守卫

for (i <- 1 to 3;j <- 1 to 3 if i!=j if i==j) println((i*10+j)+"")

for循环的返回值可以使用yield关键字导出

val result = for (i <- 1 to 3;j <- 1 to 3 if i!=j) yield i*10+j
for(i <- result) println(i)
//这里如果没有使用yield关键字的话会报错,因为for循环没有返回值。

for循环的表达式既可以使用圆括号(),也可以使用花括号{}

for推导式有一个不成文的约定:当for 推导式仅包含单一表达式时使用原括号,当其包含多个表达式时使用大括号。

函数

scala中函数的定义方式如下:

注:[]的意思是可带可不带,scala中的函数可以不指定返回类型,编译器会根据函数内容进行推断。

def 函数名 (参数名:参数类型) [:返回类型] = 函数体

def abs (n:Int) = if(n>0) n else -n;

递归函数

def recursiveFac(n:Int) = if(n<=0) 1 else n*recursiveFac(n-1);

函数默认值

def decorate(str: String, left: String = "[", right: String = "]") =left + str + right
println(decorate("Hello"))
println(decorate("Hello", "<<<", ">>>"))

函数命名参数

println(decorate(left = "<<<", str = "Hello", right = ">>>"))

边长参数

def sum(args: Int*) = {
    var result = 0;
    for(arg <- args) result+=arg
    result
}

1、 Scala 可以通过=右边的表达式 推断出函数的返回类型。如果函数体需要多个表达式,可以用代码块{}。

2、 可以把 return 当做 函数版本的 break 语句。

**3、 递归函数一定要指定返回类型。 **

4、变长参数通过* 来指定, 所有参数会转化为一个 seq 序列

5、 _* 告诉编译器 Range 当做参数序列化处理。

6、 Head 是获得首元素, tail 是获取剩下元素的序列。

过程

不返回函数值的函数称之为过程,返回类型是Unit,没有等号

def box(s:String){
    val border = "-" * s.length + "--\n"
    println("\n" + border + "|" + s + "|\n" + border)
}

或者按照函数的方式直接定义

def box(s : String) : Unit = { // Look carefully: no =
    val border = "-" * s.length + "--\n"
    println("\n" + border + "|" + s + "|\n" + border)
}

懒值

当 val 被声明为 lazy 时, 他的初始化将被推迟, 直到首次对此取值。

数据结构

Scala中几乎所有的集合类都有可变类型和不可变类型。

两个主要的包:

1) 不可变集合:scala.collection.mutable

2) 可变集合:scala.collection.immutable

Scala 优先采用不可变集合。 集合主要分为三大类: 序列(list)、 集(set)、 映射(map)

每个集合类都有一个带有apply方法的伴生对象(类似于静态方法),这个apply方法可以用于构建集合对象

数组

定长数组

定长数组的实现可以使用Array,Array以Java数组的方式实现

val num = new Array[Int](10)

变长数组

变长数组可以使用 ArrayBuffer

val b = ArrayBuffer[Int]()

多维数组

可以用 ofDim 方法创建多维数组

val matrix = Array.ofDim[Double](3, 4)
matrix(row)(column) = 17.29

拉链操作

val test1 = Array(1,2,3)
val test2 = Array("<","-",">")
val pair = test1.zip(test2)
for(s <- pair)
   println(s)
/*
(1,<)
(2,-)
(3,>)
*/

映射

映射就是 key-value 的集合, 类似于 Java 中的 Map。

// 不可变构造映射
val scores = Map("Alice" -> 10, "Bob" -> 3, "Cindy" -> 8)
// 可变映射
val scores1 = scala.collection.mutable.Map("Alice" -> 10, "Bob" -> 3, "Cindy" -> 8)
// 空映射
val scores2 = new scala.collection.mutable.HashMap[String, Int]
// 对偶
"Alice" -> 10
// 对偶元组
val scores3 = Map(("Alice", 10), ("Bob", 3), ("Cindy", 8))
// 获取值
val bobsScore1 = scores("Bob")
val bobsScore2 = scores.get("Bob")

对偶元组的操作和Map的操作一致

获取值时,返回的对象类型是option类型,要么是None,要么是Some

//更新值:
scores1("Bob") = 10
//如果 Bob 没有则新增。
//或者
scores1 += ("Bob" -> 10, "Fred" -> 7)
scores1 -= "Alice"

不能更新一个不可变映射,对不可变映射进行增加和删除操作不会影响到原映射,但是会产生一个新的映射。

//映射迭代
for ((k, v) <- scores) println(k + " is mapped to " + v)
for (v <- scores.keys) println(v)
for (v <- scores.values) println(v)

元组

元组是不同类型的值的聚集

元组使用 _1,_2,_3 来访问

val test = (1,2.0,"abc")
println(test._1,test._2,test._3)

队列

val queue = new mutable.Queue[Int]
//添加元素
queue+=1
queue++=List(2,3)
queue.enqueue(4,5,6)
//获取并弹出首元素
queue.dequeue()
//获取首元素
queue.head
//获取除首元素外其他所有元素
queue.tail

堆栈

堆栈的操作和Java的差不多,这里就不再赘述

集(Set)

同上

集合操作汇合

注:

1、seq在scala中是抽象类

2、Nil是空的List,:: 冒号操作用于List,如果要使用::构造List,在构造中需要把Nil也给加上

val myVIPList = Nil::"Ted" :: "Amanda" :: "Luke" :: "Don" :: "Martin"

将函数映射到集合

map 应用于集合中的每一个元素,并产生转换后的一个新元素。

flatmap 同样应用于集合的每一个元素,对于每一个元素产出一个集合,并将集合中的元素串接在一起。

简单来说就是flatmap把map后的可迭代结果合并在一块。

val list = "abc"::"def"::"ghi"::Nil
val news = list.map(_.toUpperCase())
println(news)
//List(ABC, DEF, GHI)
val news2 = list.flatMap(_.toUpperCase())
println(news2)
//List(A, B, C, D, E, F, G, H, I)