REPL — 交互式解释器环境
R(read)、E(evaluate)、P(print)、L(loop)
输入值,交互式解释器会读取输入内容并对它求值,再返回结果,并重复此过程。
val: 定义值 (常量), 即不可改变引用的指向 (不可对其进行赋值操作), 指向的对象能否可变取决于变量自身类型
var: 定义变量, 可以改变引用的指向
与 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 类的方法.
其他数值类型还有:
在 Scala 中, 我们使用方法而不是强制类型转换来做数值类型之间的转换 比如说 1.4.toInt 得到 1, 99.toChar 得到 c
在 Scala 中, 操作符有一点特别, 操作符实际上都是方法; 说的更加激进点, 就是 Scala 中没有操作符, 一切都是方法调用, 不过为了表述方便, 我们还是称其为操作符, 不过心里要明白它们都是方法.
比说表达式 a + b 就是方法调用 a.+(b) 的简写
Scala 的标识符命名极其灵活, 不会对方法名中出现非字母, 数字, 下划线的做法有偏见, 你几乎可以使用任何符号为方法命名
下面来看看 Scala 一个很有用的技巧, 这会让我们的编程风格更加 Scala 化
a. 方法 (b) 可以简写为 a 方法 b
这里方法是一个带有两个参数的方法 (一个隐式的和一个显示的)
- + -*/ %
- & | ^ >> <</
Scala 没有提供
和
- ++
操作符, 使用 +=1 和 -= 1 就可以. 为何没有提供呢? 因为 Scala Int 类是不可变的, 这样我们没法通过一个简单的方法实现 ++ . Scala 的设计者认为不值得为了少按一个按键而额外增加一个特例
- --
在介绍 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 方法通常可以不使用圆括号
记住上面说的是方法而不是对象 :)
当对一个对象使用类似方法调用的语法时, 就会触发 apply 方法, 比如 "Good"(2) 实际上就是方法调用 "Good".apply(2); 你可以把这种用法当做 () 操作符的重载
Javadoc 按照字母顺序列出类的清单, 而 Scaladoc 按照包 (package) 列出类的清单
- // 前缀操作符 unary_-, 即负操作符 -x
- scala> BigInt(1).unary_-
- res24: scala.math.BigInt = -1
- "Harry".patch(1, "ung", 2) // hungry

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

点击 Source, 可以跳转到 Github 上的 Scala 源码库去
在介绍 Scala 中的条件表达式, 循环和函数之前, 先来看一组概念, 在 Java 中, 表达式 (如 3+4) 和语句(如 if 语句) 是不同的东西, 那区别是什么呢? 表达式求值并返回值, 而语句执行动作, 但是不返回值; 在 Scala 中几乎所有构造出来的语法结构都有值, 比如 for 循环语句也有返回值 Unit
- scala> val l = for(i <- 1 to 2){i}
- l: Unit = ()
介绍内容如下:
- =
在 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 有一个表示无值的值
Scala 中, 行尾的位置分号㔻必须的, 只要能从上下文判断出这里是语句的终止即可. 不过如果你想在单行写多个语句, 就需要将它们用分号分开
多行时, 语句终止编码风格:
空悬操作符: 一行代码的最后一个非空字符是一个操作符
在 Scala 中, {} 块包含一系列值表达式, 其结果也是表达式. 块中最后一个表达式的值就是块的值, 即 {} 块的值取决于最后一个表达式
Scala 中, 赋值动作本身没有值, 严格的说, 它们的值是 Unit 类型, 比如块 {r= r*n; n -= 1} 的值为 (). 由于赋值语句是 Unit 类型, 所以别将赋值语句串接起来 , 比如 想 x = y =1, 由于 Scala 中大多都用的 val, val 定义的值没法被赋值, 所以基本上不会出现这个错误, 不过还是要注意, 不要把 Java 的习惯全盘带到 Scala 中.
最后关于块 {} 在多说一点, 当你使用一个块 {} 的时候, 你就创建了一个作用域, 而作用域是可以嵌套的, 外层作用域的实体 (值, 变量, 对象等) 在内层作用域是可见的.
输出到控制台:
- 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)
在介绍循环前, 先介绍下 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, 可以这样做:
可以是用
的形式提供多个生成器, 用分号将它们隔开; 每个生成器都可以带一个守卫, 以 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)
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
当 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")
懒值不是没有额外开销, 我们每次访问懒值, 都会有一个方法被调用, 这个方法以线程安全的方式检查值是否已被初始化
Scala 异常的工作机制和 Java 一样
- throw new IllegalArgumentException(" have a exception")
和 Java 一样, 抛出的对象必须是 java.lang.Throwable 的子类. 不过, 与 Java 不同的是, Scala 没有受检异常, 即你不需要声明说函数或者方法可能会抛出某种异常
Throwable 表达式类型为 Nothing, Nothing 是 Scala 中一切其他类型的子类
也可以使用 try/catch/finally 语句:
- try { ... } catch { ... } finally { ... }
scala.Array 是定长的可变的索引型集合, JVM 中, Scala 的 Array 是以 Java 数组方式实现 String 对应 java.lang.String, Int, Double 或其他与 Java 中基本类型对应数组都有基本类型数组, 比如说 Array(1, 2) 在 JVM 中就是一个 int[].
- val arr1 = new Array[Int](10) val arr2 = Array(1, 3) // 调用apply方法
- arr2(1) // 3, apply 方法
- arr2(1) = 2 // 可以改变 Array 中的值 , update 方法
scala.collection.mutable.ArrayBuffer 是变长的可变的索引型集合, 在 ArrayBuffer 的尾部添加和删除元素是一个高效的操作, 你也可以在任何位置插入或者移除元素, 但是这样的操作并不那么高效
- import scala.collection.mutable.ArrayBuffer
- val b = ArrayBuffer[Int]() // 或 val b = ArrayBuffer[Int]
- b += 1 // += 在尾部添加元素 1
- b += (2 ,3) // += 尾部添加多个元素, 用括号括起来 1, 2, 3
- b ++= Array(4, 5) // ++= 用来追加任何集合 1 2 3 4 5
- b.trimEnd(2) // 删除尾部2个元素; 1 2 3
- b.trimStart(1) // 删除头部2个元素 2 3
- b.insert(1, 4) // 在下标1之前插入4; 2 4 3
- b.insert(1, 6, 7) // 在下标1之前插入6, 7; 2 6 7 4 3
- b.remove(2) // 删除 b(2); 2 6 4 3
- b.remove(2, 2) // 删除 b.slice(2, 2+2); 2 6
如果你需要一个 Array, 但是不知道最终要装多少个元素, 你可以向使用 ArrayBuffer, 然后调用 b.toArray; 反过来你也可以使用 toBuffer 将数组转换为 ArrayBuffer
遍历数组
- for ( i <- 0 until a.length)
- for (i <- (0 until a.length).reverse)
- for (elem <- a)
数组转换
利用 for (...) yield 循环会创建 一个类型和原始集合相同的集合的特点
- val res = for(elem <- a if elem % 2 == 1) yield 2 * elem
上面的写法等价于
- val res = a.filter(_ % 2 == 1).map(2 * _)
这里只是展示下 for 推导式在集合上的应用, 实际上, 在 Scala 中很少使用这种显示的循环结构, 更多的是利用函数来解决问题, 比如上面用到的 map 和 filter
- val a = Array(1, 4, 3) // 或 val a = ArrayBuffer(1, 4, 3)
- a.sum // 8
- a.max // 4
- a.sortWith(_ < _) // 1 3 4, 返回一个新集合, a 并没有改变
- scala.util.Sorting.quickSort(a)
下面看一下 toString 和 mkString 方法
mkString 方法允许你指定元素之间的分隔符, 该方法的两个重载版本还可以指定前缀和后缀
- val a = Array(2, 3)
- val b = ArrayBuffer(2, 3)
- scala> a.toString
- res52: String = [I@52fa6742
- scala> b.toString
- res53: String = ArrayBuffer(2, 3)
- scala> a.mkString
- res60: String = 23
- scala> a.mkString(" and ")
- res61: String = 2 and 3
- scala> a.mkString("(", ",", ")")
- res62: String = (2,3)
从上面可以看出来, 如果是 Array 的话, 可以使用 mkString 方法将 Array 的内容打印到控制台
在 Scala 中, 多维数组通过数组数组来实现. 比如说 Double 的二维数组类型为 Array[Array[Double]]
要构造这样的数组可以使用 ofDim 方法:
- val ndim = Array.ofDim[Double](3, 4)
访问其中元素:
ndim(row)(column)
也可以自己创建一个不规则的数组
- val triangle = new Array[Array[Int]](4) // 4 是二维数组的行数
- for (i < -0 until triangle.length) {
- triangle(i) = new Array[Int](i + 1)
- }
来源: http://www.cnblogs.com/nowgood/p/scalastartup.html