Scala 入门介绍

1 基础

1.1 Scala 解释器

REPL — 交互式解释器环境
R(read)、E(evaluate)、P(print)、L(loop)
输入值,交互式解释器会读取输入内容并对它求值,再返回结果,并重复此过程。

1.2 声明 val(值) 和 var(变量)

val: 定义值(常量), 即不可改变引用的指向(不可对其进行赋值操作), 指向的对象能否可变取决于变量自身类型
var: 定义变量, 可以改变引用的指向

  1. 声明值或者变量的同时, 必须初始化;
  2. 可以不指定类型, Scala可以利用初始化值进行类型推断
  3. Scala中, 仅当同一行代码存在多条语句才需要用分号隔开
  4. 可以同时将多个值或者变量放在一起声明 val x, y = 10

1.3 数值类型

与 Java 一样 Scala也有8中数值类型:
Byte, Char, Short, Int, Long, Float , Double Boolean

这些数值类型都是类, 并且 Scala 通过隐式转换为这些类型提供了很多常用方法. 比如说 Scala 提供了 RichInt, RichChar, RichDouble 等, 分别为 Int, Char, Double 提供其所不具备的方法

比如说表达式 1.to(10), Int 值1首先被隐式转换为 RichInt 然后应用 RichInt 类的方法.

其他数值类型还有:

  • BigInt 背后为 java.math.BigInteger
  • Decimal 背后为 java.math.BIgDecimal

在 Scala 中, 我们使用方法而不是强制类型转换来做数值类型之间的转换 比如说 1.4.toInt得到 1, 99.toChar 得到 c

1.4 操作符

在 Scala 中, 操作符有一点特别, 操作符实际上都是方法; 说的更加激进点, 就是 Scala 中没有操作符, 一切都是方法调用, 不过为了表述方便, 我们还是称其为操作符, 不过心里要明白它们都是方法.

比说表达式 a + b 就是方法调用 a.+(b) 的简写

Scala的标识符命名极其灵活, 不会对方法名中出现非字母, 数字,下划线的做法有偏见, 你几乎可以使用任何符号为方法命名

下面来看看 Scala 一个很有用的技巧, 这会让我们的编程风格更加 Scala 化

a.方法(b) 可以简写为 a 方法 b
这里方法是一个带有两个参数的方法(一个隐式的和一个显示的)

+ - * / %
& | ^ >> <<

Scala 没有提供 ++-- 操作符, 使用 +=1 和 -= 1 就可以. 为何没有提供呢? 因为 Scala Int类是不可变的, 这样我们没法通过一个简单的方法实现 ++ . Scala的设计者认为不值得为了少按一个按键而额外增加一个特例

1.5 函数和方法

在介绍 Scala 的函数和方法之前先来简单看一下方法和函数有什么区别吧, 简单的说就是 方法和对象相关;函数和对象无关

除了方法外, Scala 还提供了函数( Java 只有方法), 相比 Java, 在 Scala 中使用数学函数更加简单, 你不需要从某个类中调用它的静态方法.

Scala 的数学函数都在 Scala.math 包中定义. 通过下面语句引入

import scala.math._  或者 import math._
sqrt(2)
pow(2, 3) // 8
min(3, Pi) // 3

Scala 中没有静态方法, 不过它有个类似的特性, 叫做单例对象, 通常一个类对应一个伴生对象, 伴生对象中定义的方法和 Java 中的静态方法一样

不带参数的 Scala 方法通常可以不使用圆括号
记住上面说的是方法而不是对象 😃

1.6 apply方法

当对一个对象使用类似方法调用的语法时, 就会触发 apply 方法, 比如 "Good"(2) 实际上就是方法调用 "Good".apply(2); 你可以把这种用法当做 () 操作符的重载

1.7 Scaladoc

Javadoc 按照字母顺序列出类的清单, 而Scaladoc 按照包(package)列出类的清单

// 前缀操作符 unary_-, 即负操作符 -x

scala> BigInt(1).unary_-
res24: scala.math.BigInt = -1

"Harry".patch(1, "ung", 2) // hungry

markdown-img-paste-20170803225447379

上面的图片, C 表示这是一个类, 我们可以看到图片上半圆和下半圆颜色不一样, 表示该类有伴生对象, 点击这个图标就能切换到伴生对象界面了.

markdown-img-paste-20170803230431396

点击 Source, 可以跳转到 Github 上的 Scala源码库去

2. 控制结构和函数

在介绍 Scala 中的条件表达式, 循环和函数之前, 先来看一组概念, 在 Java中, 表达式(如 3+4)和语句(如 if 语句) 是不同的东西, 那区别是什么呢? 表达式求值并返回值, 而语句执行动作, 但是不返回值; 在 Scala 中几乎所有构造出来的语法结构都有值, 比如 for循环语句也有返回值 Unit

scala> val l = for(i <- 1 to 2){i}
l: Unit = ()

介绍内容如下:

  • if 表达式有值
  • void 类型是 Unit
  • for 循环返回 Unit
  • 分号不是必须的(绝大多数情况下)
  • 避免在函数中使用 return
  • 函数定义一定不要漏掉了等于号 =
  • Scala没有受检异常, 常使用模式匹配处理异常

2.1 条件表达式

在Scala中, if/else 是表达式, 所以有返回值, 又因为 Scala 中每个表达式都有一个类型, 所以如果 if /else 与句的两个分支类型相同的话, 表达式就是该类型, 如果两个分支类型不同的话, 返回值就是来个分支类型的公共超类型, 当然这种情况用 case 语句来处理是个更好的选择.

if/else 表达式如果 else 部分缺失了, 那么有可能表达式没有输出值, 但是 Scala 中所有表达式都应该有某种返回值, 解决方法就是引入 Unit 类, 写作 (). 不带 else 的 if 表达式等同于 if(x>0) 1 else ()

scala> val s1 = if (1 > 0) 1 else -1
s1: Int = 1

scala> val s2 = if (1 < 0) 1 else "H"
s2: Any = H

scala> val s3 = if (1 < 0) 1
s3: AnyVal = ()

Unit 可以看成 Java 中的 void, 该类型只有一个值, 技术上说, void 没有值而 Unit 有一个表示无值的值

2.2 语句终止

Scala中, 行尾的位置分号㔻必须的, 只要能从上下文判断出这里是语句的终止即可. 不过如果你想在单行写多个语句, 就需要将它们用分号分开

多行时, 语句终止编码风格:

  • 同行左大括号
  • 使用空悬操作符或者括号

空悬操作符: 一行代码的最后一个非空字符是一个操作符

2.3 块表达式与赋值

在 Scala 中, { } 块包含一系列值表达式, 其结果也是表达式. 块中最后一个表达式的值就是块的值, 即 {} 块的值取决于最后一个表达式

Scala中, 赋值动作本身没有值, 严格的说, 它们的值是 Unit 类型, 比如块 {r= r*n; n -= 1} 的值为 (). 由于赋值语句是 Unit 类型, 所以别将赋值语句串接起来 , 比如 想 x = y =1, 由于Scala 中大多都用的 val, val 定义的值没法被赋值, 所以基本上不会出现这个错误, 不过还是要注意, 不要把 Java 的习惯全盘带到 Scala 中.

最后关于块 {} 在多说一点, 当你使用一个块 {} 的时候, 你就创建了一个作用域, 而作用域是可以嵌套的, 外层作用域的实体(值, 变量, 对象等) 在内层作用域是可见的.

2.4 输入与输出

输出到控制台:

println("Hi " + "Jim")
printf("Hi, %s\n", "Jim")

从控制台读取输入:

scala> import  scala.io.StdIn._
import scala.io.StdIn._

scala> read
readBoolean   readFloat   readShort   readf3
readByte      readInt     readf
readChar      readLine    readf1
readDouble    readLong    readf2

// 与其他方法不同,  readLine 可以带有一个参数作为提示字符串
scala> readLine("input>")  // 输入 dog
input>res0: String = dog

// readf()可以接收任意数量的值,返回值为List[Any]类型
scala> val list =  readf("{0}")   // 输入 1
list: List[Any] = List(1)  

//readf1()仅能接收一个值,返回接收的值
scala> val num = readf1("is {0}")  // 输入 is me
num: Any = me

// readf2() , readf3() 分别接收两个, 三个值,返回值为 Tuple 类型
scala>  val tuple = readf3("{0} + {1} + {2}")  // 输入  c + a + t
tuple: (Any, Any, Any) = (c,a,t)

2.5 循环

在介绍循环前, 先介绍下 to 和 until 方法

1 to n: 返回 1 到 n 的区间, 包含 n
1 until n: 返回 1 到 n 的区间, 不包含 n

while循环:
while(循环变量) {
statements
}

一般在 Scala 中不会用 while 循环

for循环:
for (i<- 表达式) {
statements
}

让变量 i 遍历 表达式的所有值, 至于遍历具体如何执行取决于表达式的类型, 返回值为 Unit

Scala 中没有提供 break 和 continue 语句来退出循环, 如果需要 break, 可以这样做:

  1. 使用 Boolean 型的控制变量
  2. 使用 scala.util.control.Breaks 对象的 break 方法
  3. 函数中可以把 return 当做 break 使用

2.6 高级for循环和for推导式

可以是用 变量 <- 表达式 的形式提供多个生成器, 用分号将它们隔开; 每个生成器都可以带一个守卫, 以 if 开头的 Boolean 表达式; Scala中 有这样一个编程风格, 如果 () 中有多个表达式的话, 可以使用 {} 代替 () 来写一个块表达式

  for  {
      i <- 1 to 3 if (i != 2)  //注意 if 前也没有分号   
      j <- 1 to 3 if (i != j)
  } {
      print (i * 10 + j + "  ")
  }
12  13  31  32

从上面可以看出, 可以将生成器, 守卫(和定义) 包含在大括号中, 可以以换行的方式来隔开它们;

如果 for 循环的循环体以 yield 开始, 则该循环会构造出一个集合, 每次迭代生成集合中的一个, 这类循环叫做 for 推导式

for 推导式 生成的集合和它的第一个生成器是类型兼容的.

scala> for (c <- "Hi"; i <- 0 to 1) yield (c+i).toChar
res5: String = HIij

scala> for (i <- 0 to 1; c <- "Hi" ) yield (c+i).toChar
res6: scala.collection.immutable.IndexedSeq[Char] = Vector(H, i, I, j)

2.7 函数

Scala 除了方法之外还支持函数

定义函数需要给出函数名, 函数, 函数体; 你必须给出所有参数的类型, 只要函数不是递归的, 就不需要指定返回类型. Scala 可以通过等于号 = 右侧的表达式类型自动推断返回值类型.

// 递归必须制定返回值类型  
def fac(n: Int): Int = if (n <= 0) 1 else n * fac(n -1)

如果函数体需要过个表达式完成, 使用代码块 { }, 块的最后一个表达式就是函数的返回值. 比如说:

def fac(n: Int) = {
    var r = 1
    for (i <- 1 to n)  r = r * i
    i
}

Scala 中一般不使用 return 来返回函数返回值, 一般讲 return当做 函数版的 break 语句

过程

Scala对于不返回值的函数有特殊的表示方法. 如果函数体包含在花括号当中但没有前面的等于号 = , 那么返回值类型就是 Unit, 这样的函数被称为 过程. 过程不返回值, 我们调用仅仅为了它的副作用.

默认参数和带名参数

带有默认参数的函数, 在调用函数时, 我们可以不显式地给出所有的参数值

def foo (str: String, l: String = "(", r: String = ")") = l + str + r  

调用 foo("smile"), 得到 (smile).

如果你给出的值不够, 给出的值从前往后依次匹配参数(没有带名参数的话), 对于还没有匹配到值的参数使用默认参数.

foo("smile", ":(")  // :(smile)

函数调用 在提供参数值的时候指定带名参数, 正常参数按照位置匹配, 而带名参数不需要和参数列表的顺序完全一致, 来看一个例子

foo("smile", r = ":)")  //(smile:)

变长参数

先来看一下变长参数的语法吧

def sum(plus:  Int*) = {
    var res =  0
    for (p <- plus) res+= p
    res
}

可以使用任意多的参数来调用该函数, 函数得到类型是 Seq 的参数. 但是如果传入单个参数, 参数必须是 Int 型, 而不可以是一个 Int 型集合要实现这个做法,你需要在数组参数后添加 : _* , 这是编译器的一个 annotation(标记), 当编译器遇到这个标记时会把集合中的每个元素当作参数传递给 sum. 这个标记只在解决变长参数传递问题

sum(1)  // 1
sum(1, 2)  // 3
sum(1, 2, 3)  // 6

sum(Seq(1, 2), _*)  //3
sum(List(1, 2): _*)  //3

2.8 懒值

当 val 被声明为 lazy, 它的初始化将被延迟, 直到我们首次对它求值

lazy val words = scala.io.Source.fromFile("/home/nowgood/happy.txt")

如果程序从不访问 words, 那么文件也不会被打开.

你可以吧懒值当做是介于 val 和 def 的中间状态

// words 被定义时即被取值
val words = scala.io.Source.fromFile("/home/nowgood/happy.txt")  
// words 被首次使用时取值
lazy val words = scala.io.Source.fromFile("/home/nowgood/happy.txt")

// 每一次 words 被使用时取值
def words = scala.io.Source.fromFile("/home/nowgood/happy.txt")

懒值不是没有额外开销, 我们每次访问懒值, 都会有一个方法被调用, 这个方法以线程安全的方式检查值是否已被初始化

2.9 异常

Scala 异常的工作机制和 Java一样

throw new IllegalArgumentException(" have a exception")

和 Java一样, 抛出的对象必须是 java.lang.Throwable 的子类. 不过, 与 Java 不同的是, Scala没有受检异常, 即你不需要声明说函数或者方法可能会抛出某种异常

Throwable表达式类型为 Nothing, Nothing是 Scala 中一切其他类型的子类

也可以使用 try/catch/finally 语句:

try { ... } catch { ... } finally { ... }
posted @ 2017-08-04 21:00  nowgood  阅读(1574)  评论(0编辑  收藏  举报