Scala菜鸟教程

Scala 变量

变量vs常量

在 Scala 中,使用关键词 “var” 声明变量(可以修改),使用关键词 “val” 声明常量(不能修改)。

1
2
3
4
5
# 变量(可以修改)
var myVar: String = "hello"
myVar = "world"//可以修改
# 常量(不能修改)
val myVar: String = "hello"// 不能修改

变量类型声明

var VariableName : DataType [= Initial Value]

变量类型引用

Scala 中声明变量和常量不一定要指明数据类型,在没有指明数据类型的情况下,其数据类型是通过变量或常量的初始值推断出来的

1
2
var myVar = 10;
val myVal = "Hello, Scala!";

多个变量声明

1
2
val xmax, ymax = 100  // xmax, ymax都声明为100
val pa = (40,"Foo")

Scala 访问修饰符

分别有:private,protected,public,没有指定默认是public,

私有(Private)成员

Scala 中的 private 限定符,比 Java 更严格,在嵌套类情况下,外层类甚至不能访问被嵌套类的私有成员

1
2
3
4
5
6
7
8
9
class Outer{
class Inner{
private def f(){println("f")}
class InnerMost{
f() // 正确
}
}
(new Inner).f() //错误
}

(new Inner).f( ) 访问不合法是因为 f 在 Inner 中被声明为 private,而访问不在类 Inner 之内。但在 InnerMost 里访问 f 就没有问题的,因为这个访问包含在 Inner 类之内

保护(Protected)成员

在 scala 中,对保护(Protected)成员的访问比 java 更严格一些。因为它只允许保护成员在定义了该成员的的类的子类中被访问。而在java中,用protected关键字修饰的成员,除了定义了该成员的类的子类可以访问,同一个包里的其他类也可以进行访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package p {

class Super {
protected def sayHello() {
print("hello")
}
}

class Sun extends Super {
sayHello()
}

class Other {
(new Super).sayHello()
}

}

Other 对 f 的访问不被允许,因为 other 没有继承自 Super

公共(Public)成员

Scala中,如果没有指定任何的修饰符,则默认为 public。这样的成员在任何地方都可以被访问。

作用域保护

1
private[x] 或 protected[x]

这里的x指代某个所属的包、类或单例对象。如果写成private[x],读作”这个成员除了对[…]中的类或[…]中的包中的类及它们的伴生对像可见外,对其它所有类都是private。这种技巧在横跨了若干包的大型项目中非常有用,它允许你定义一些在你项目的若干子包中可见对于项目外部的客户却始终不可见的东西。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Hello {

class World {
private var world: String = "xxxxx"

class A1 {
print(world)
}

}

class C1 extends World {
print(world)//异常
}
}

由于私有变量不能被继承,执行会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Hello {

class World {
private[Hello] var world: String = "xxxxx"

class A1 {
print(world)//正常
}

}

class C1 extends World {
print(world)
}

}

重新定义了world的作用域,就可以访问了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package scopeA {
class C1 {
private[scopeA] val scopeA_privateField = 1
protected[scopeA] val scopeA_protectedField = 2
private[C1] val c1_privateField = 3
protected[C1] val c1_protectedField = 4
private[this] val this_privateField = 5
protected[this] val this_protectedField = 6
}
class C2 extends C1 {
val field1 = scopeA_privateField
val field2 = scopeA_protectedField
// val field3 = c1_privateField // 错误
val field4 = c1_protectedField
// val field5 = this_privateField // 错误
val field6 = this_protectedField
}
}
package scopeB {
class C3 extends scopeA.C1 {
// val field1 = scopeA_privateField // 错误
val field2 = scopeA_protectedField
// val field3 = c1_privateField // 错误
val field4 = c1_protectedField
// val field5 = this_privateField // 错误
val field6 = this_protectedField
}
}

Scala 为用户提供了一些额外方法,以帮助用户以更小的粒度对可见性的作用域进行调整。从这一点看,Scala 超过了大多数的语言。Scala 提供了作用域内私有(scoped private)可见性声明和作用域内受保护(scoped protected)可见性声明。请注意,在具有继承关系的情况下,`对类成应用这两类可见性后表现不同。但除此之外,这两类可见性的表现完全一致,因此在同一个作用域内,私有可见性可以和受保护可见性交换使用。

Scala 可见性

Scala 运算符

符号 描述
/ 除号
% 取余
&& 逻辑与
`
! 逻辑非
>>> 无符号右移
>> 右移

右移与无符号右移区别:

  • 右移>> :该数对应的二进制码整体右移,左边的用原有标志位补充,右边超出的部分舍弃。
  • 无符号右移>>> :不管正负标志位为0还是1,将该数的二进制码整体右移,左边部分总是以0填充,右边部分舍弃

运算符优先级
Scala 运算符

操作符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
scala> "A"::"B"::Nil
res0: List[String] = List(A, B)

scala> "A"+:"B"+:Nil
res1: List[String] = List(A, B)

scala> Nil:+"A":+"B"
res2: List[String] = List(A, B)

scala> res0 ++ res1
res3: List[String] = List(A, B, A, B)

scala> res0 ::: res1
res4: List[String] = List(A, B, A, B)

scala> res0 :: res1
res5: List[java.io.Serializable] = List(List(A, B), A, B)

scala中:: , +:, :+, :::, +++的区别

Scala 方法与函数

Scala 中的函数则是一个完整的对象,Scala 中的函数其实就是继承了 Trait 的类的对象。
Scala 中使用 val 语句可以定义函数,def 语句定义方法。

1
2
3
4
class Test{
def m(x: Int) = x + 3
val f = (x: Int) => x + 3
}

如果方法没有返回值,可以返回为 Unit,这个类似于 Java 的 void, 实例如下:

1
2
3
4
5
object Hello{
def printMe( ) : Unit = {
println("Hello, Scala!")
}
}

函数

Scala 函数传名调用(call-by-name)

Scala的解释器在解析函数参数(function arguments)时有两种方式:

  • 传值调用(call-by-value):先计算参数表达式的值,再应用到函数内部;
  • 传名调用(call-by-name):将未计算的参数表达式直接应用到函数内部

在进入函数内部前,传值调用方式就已经将参数表达式的值计算完毕,而传名调用是在函数内部进行参数表达式的值计算的。这就造成了一种现象,每次使用传名调用时,解释器都会计算一次表达式的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
object Test {
def main(args: Array[String]) {
delayed(time());
}

def time() = {
println("获取时间,单位为纳秒")
System.nanoTime
}

def delayed(t: => Long) = {
println("在 delayed 方法内")
println("参数: " + t)
print(t)
}
}
# 结果
在 delayed 方法内
获取时间,单位为纳秒
参数: 27696238732400
获取时间,单位为纳秒
27696238800842

Scala 指定函数参数名

我们也可以通过指定函数参数名,并且不需要按照顺序向函数传递参

1
2
3
4
5
6
7
8
9
object Test {
def main(args: Array[String]) {
printInt(b=5, a=7);
}
def printInt( a:Int, b:Int ) = {
println("Value of a : " + a );
println("Value of b : " + b );
}
}

Scala 函数 - 可变参数

Scala 通过在参数的类型之后放一个星号来设置可变参数(可重复的参数)

1
2
3
4
5
6
7
8
9
10
11
12
object Test {
def main(args: Array[String]) {
printStrings("Runoob", "Scala", "Python");
}
def printStrings( args:String* ) = {
var i : Int = 0;
for( arg <- args ){
println("Arg value[" + i + "] = " + arg );
i = i + 1;
}
}
}

Scala 递归函数

Scala 函数 - 默认参数值

Scala 可以为函数参数指定默认参数值,使用了默认参数,你在调用函数的过程中可以不需要传递参数,这时函数就会调用它的默认参数值,如果传递了参数,则传递值会取代默认值

1
2
3
4
5
6
7
8
9
10
11
object Test {
def main(args: Array[String]) {
println( "返回值 : " + addInt() );
}
def addInt( a:Int=5, b:Int=7 ) : Int = {
var sum:Int = 0
sum = a + b

return sum
}
}

Scala 高阶函数

Scala 中允许使用高阶函数, 高阶函数可以使用其他函数作为参数

1
2
3
4
5
6
7
8
9
10
11
12
object Test {
def main(args: Array[String]) {

println( apply( layout, 10) )

}
// 函数 f 和 值 v 作为参数,而函数 f 又调用了参数 v
def apply(f: Int => String, v: Int) = f(v)

def layout[A](x: A) = "[" + x.toString() + "]"

}

Scala 函数嵌套

我们可以在 Scala 函数内定义函数,定义在函数内的函数称之为局部函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
object Test {
def main(args: Array[String]) {
println( factorial(0) )
println( factorial(1) )
println( factorial(2) )
println( factorial(3) )
}

def factorial(i: Int): Int = {
def fact(i: Int, accumulator: Int): Int = {
if (i <= 1)
accumulator
else
fact(i - 1, i * accumulator)
}
fact(i, 1)
}
}

Scala 匿名函数

1
var inc = (x:Int) => x+1

Scala 偏应用函数

Scala 偏应用函数是一种表达式,你不需要提供函数需要的所有参数,只需要提供部分,或不提供所需参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Date

object Test {
def main(args: Array[String]) {
val date = new Date
val logWithDateBound = log(date, _ : String)

logWithDateBound("message1" )
Thread.sleep(1000)
logWithDateBound("message2" )
Thread.sleep(1000)
logWithDateBound("message3" )
}

def log(date: Date, message: String) = {
println(date + "----" + message)
}
}
Scala 函数柯里化(Currying)

闭包

闭包出现是因为lexical scope,闭包是由函数和环境组成,Scala应该支持函数作为参数或返回值,这时如果没有闭包,那么函数的free 变量就会出错

闭包源于λ表达式,它的概念核心分为两块,1.上下文环境 2.控制流程。进一步地说,闭包是绑定了自由变量的函数实例。通常来讲,闭包地实现机制是定义一个特殊的数据结构,保存了函数地址指针与闭包创建时的函数的词法环境以及绑定自由变量。对于闭包最好的解释,莫过于《流程的Python》里给出的“它是延伸了作用域的函数,其中包括函数定义体引用,但是不在定义体定义的非全局变量。核心在于闭包能够访问定义体之外定义的非全局变量。

闭包 = 代码 + 用到的非局部变量

1
2
3
4
5
6
scala> var more =1
more: Int = 1
scala> val addMore = (x:Int) => x + more
addMore: Int => Int = <function1>
scala> addMore (100)
res1: Int = 101

它“捕获”自身的自由变量(more)从而“闭合”该匿名函数

在Scala里“捕获”的是变量本身,而不是变量本身引用的值。

1
2
3
4
5
scala> more = 100
more: Int = 100

scala> add(2)
res2: Int = 102

当然,反过来也是成立的,闭包也可以修改其自由变量

1
2
3
4
5
6
7
scala> val minusOne = (x:Int) => {more = more - x}
minusOne: Int => Unit = <function1>

scala> minusOne(1)

scala> more
res4: Int = 99

那么问题来了,如果more这个变量随着程序的运行被修改了很多次,那么闭包会选择哪一个呢?Scala的答案是,闭包被创建时这个变量最新的那个。(根据定义函数的词法作用域计算自由变量)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
scala> def Increase(more:Int) = (x:Int) => x + more
Increase: (more: Int)Int => Int

scala> val Inc1 = Increase(1)
Inc1: Int => Int = <function1>

scala> val Inc9 = Increase(9)
Inc9: Int => Int = <function1>

scala> Inc1(10)
res5: Int = 11

scala> Inc9(10)
res6: Int = 19

闭包
Python和Scala里的闭包

Scala 字符串

  1. 在 Scala 中,字符串的类型实际上是 Java String,它本身没有 String 类
  2. String 对象是不可变的,如果你需要创建一个可以修改的字符串,可以使用 String Builder 类
    1
    2
    3
    4
    5
    6
    7
    8
    object Test {
    def main(args: Array[String]) {
    val buf = new StringBuilder;
    buf += 'a'
    buf ++= "bcdef"
    println( "buf is : " + buf.toString );
    }
    }

Scala 数组

声明数组

1
2
var z:Array[String] = new Array[String](3)
var z = new Array[String](3)

赋值

1
2
z(0) = "Runoob"; z(1) = "Baidu"; z(2) = "Google"
var z = Array("Runoob", "Baidu", "Google")

数组遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
object Closures {
def main(args: Array[String]): Unit = {
val myList = Array(1, 2, 3, 4, 5)
for (x <- myList) {
println(x)
}
var total: Int = 0
for ( i <- myList.indices) {
total += myList(i)
}
println("总和为 " + total)
}
}

多维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import scala.Array._

object Closures {
def main(args: Array[String]): Unit = {
val myMatrix = ofDim[Int](3, 3)

for (i <- 0 to 2) {
for (j <- 0 to 2) {
myMatrix(i)(j) = j
}
}
for (i <- 0 to 2) {
for (j <- 0 to 2) {
println(myMatrix(i)(j))
}
}
}
}

Scala Collection

Scala 集合分为可变的和不可变的集合

  • 可变集合可以在适当的地方被更新或扩展。这意味着你可以修改,添加,移除一个集合的元素。
  • 不可变集合类,相比之下,永远不会改变。不过,你仍然可以模拟添加,移除或更新操作。但是这些操作将在每一种情况下都返回一个新的集合,同时使原来的集合不发生改变
1
2
3
4
5
val x = List(1, 3, 4, 5)
val y = Set(1, 3, 4, 5)
val z = Map("one" -> 1, "two" -> 2, "three" -> 3)
val w = (10, "rexxx")
var t: Option[Int] = Some(5)

Scala Iterator(迭代器)

Scala Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法。迭代器 it 的两个基本操作是 next 和 hasNext。

模式匹配

语法:

1
2
3
4
5
6
7
8
9
import scala.util.Random

val x: Int = Random.nextInt(10)
x match {
case 0 => "zero"
case 1 => "one"
case 2 => "two"
case _ => "other"
}

模式匹配

javap命令
Scala 教程