Scala将面向对象和函数式编程结合在一种简洁的高级语言中。Scala的静态类型有助于避免复杂应用程序中的bug,它的JVM允许您构建高性能系统,并可以轻松访问庞大的库生态系统。
Scala 和 Java 一样提供八种基础数据类型。如下所示:
另外,比较特殊的是,Scala 的字符串类型和 Java 的字符串类型是共用同一个类,也就是:java.lang.String
所谓的字面量,就是从字面就能看明白类型和值的数据标量。
0
10
100L
3.14
3.14f
true
false
'A'
'\n'
"Hello"
在 Scala 中可以使用三双引号表示多行字符串,其作用:可以在字符串中不需要使用 \n \t \r 等转义字符能自动识别文本中的特殊符号,例如:
println("""
Hello
World!
""");
在普通字符串前加一个字母 s,Scala 会解析这个字符串中的变量或常量。例如
var res1 = 100;
println(s"res1=${res1}");
关键字 | 来源 | 被修饰的标识符是否可变 |
---|---|---|
val | value | 不可变 |
val | variable | 可变 |
在没有显式声明变量或者常量的数据类型时,scala 会根据字面量自动推断数据类型。例如下面两行都能实现声明一个整型变量:
var num:Int = 12
var num = 12
Scala 中绝大多数操作符和 Java 一样,举例如下:
算术运算符:±*/%
关系运算符:> < != ==
逻辑运算符:! || &&
赋值运算符:= += %=
和 Java不同的是:Scala没有自运算符,例如: i++
和 ++i
在 Scala 中是不被允许的。可以使用 += 1
来实现变量自增。
另外,在 Scala 中的操作符都被定义成了方法,例如 + 就是被定义到 Int类中的函数,也就是所有的 Int 类型都拥有 + 方法,也就是说:3 + 5
等价于 (3).+(5)
每个基础类型都有对应的富包装类,如下表所示:
基础类型 | 富包装类 |
---|---|
scala.Byte | scala.runtime.RichByte |
scala.Byte | scala.runtime.RichByte |
scala.Short | scala.runtime.RichShort |
scala.Int | scala.runtime.RichInt |
scala.Long | scala.runtime.RichLong |
scala.Float | scala.runtime.RichFloat |
scala.Double | scala.runtime.RichDouble |
scala.Char | scala.runtime.RichChar |
scala.Boolean | scala.runtime.RichBoolean |
富包装类中提供了很多使用的函数,例如:
3 max 5 → (3).max(5)
3 min 5 → (3).min(5)
技巧提示:在交互式环境中,可以通过 tab 键能将某个对象的所有可能使用的方法列举出来。
if(10 % 2 == 0) {
println("偶数");
} else {
println("奇数");
}
与 Java 中的 if 结构不一样的是,Scala 结构能为变量或者常量赋值,类似 Java 中的三元运算符,例如:
var str = if(10 % 2 == 0) "偶数" else "奇数"
先做,再判断是否需要继续做
do {
println(s"num = ${num}");
num -= 1;
} while(num > 0);
先判断是否需要开始做
while(num < 10) {
println(s"num = ${num}");
num += 1;
};
Java 版本的 for 循环:
for(int i = 0;i < 20;i++) {
System.out.println(i);
}
scala 版本的 for 循环:
for(i <- 0 until 20) {
println(i);
}
Scala中的 for 循环和Java中截然不同,它有区间,步长,守卫等概念,如下图所示:
scala> for(i <- 0 to 2; j <- 4 until 6) println(s"i=${i} j=${j}")
i=0 j=4
i=0 j=5
i=1 j=4
i=1 j=5
i=2 j=4
i=2 j=5
多个生成器非常类似一个双重 for 循环,我们可以利用这个输出一个 九九乘法表,示例如下:
for(x <- 1 to 9; y <- 1 to x) {
val r = x * y
val rs = (if (r < 10) "0" else "") + r
print(s"$y × $x = $rs\t")
if(x == y) {
println("")
}
}
举例:试从 [0, 100] 之间,找到所有的 7 的倍数,并将 7 的倍数放到一个变量中。
var ns = for (i <- 0 to 100 if i % 7 == 0 && i != 0) yield {
i;
}
for(i <- ns) {
println(s"i=$i")
}
从 for 推导式会通过 yield 字段将循环体返回的数据逐个加入到 ns 容器中,得到的ns容器是包含一组Int 类型数据的一个容器,可以通过 for 循环将其迭代输出。
Scala 不像 Java那用为跳出循环提供 break 和continue等关键字,为了达到类似的效果,可以通过 util.control.Breaks 包下的breakable 和break。
利用 scala 的 breakable 和 break 实现 Java 的 break的效果:
import util.control.Breaks._
breakable {
for(i <- 0 to 10) {
if(i > 3) {
break();
}
println(i);
}
}
利用 scala 的 breakable 和 break 实现 Java 的 continue的效果:
import util.control.Breaks._
for(i <- 0 to 10) {
breakable {
if(i == 3) {
break();
}
println(i);
}
}
代码中的 import util.control.Breaks._ 和 Java 中 import util.control.Breaks.* 的效果是一样的。用于导入这个包下所有成员。
同时,通过这两个例子,也能看出:Scala 中的 break 跳出实际上是 breakable 语句块。只是当 breakable 的作用域在不同位置表现出的效果是不一样的而已。
Scala 通过控制台读取数据到程序中需要先导入 scala.io.StdIn 包中相关的函数,例如需要读取控制台输入的整型,我们可以这样写:
import scala.io.StdIn._;
var a = readInt();
var b = readInt();
var res = a + b;
println("两个数字之和为" + res);
Scala 输出可以直接使用 print 和println函数来实现输出,它们被定义在scala.Predef 包中,而这个包是Scala 默认的包,所以才使得它们能直接被使用。
Scala 程序和 Java是完全兼容的,所以,可以直接在 Scala 中直接引用 Java的对象,例如:
import java.io.PrintWriter;
val outFile = new PrintWriter("io.txt");
outFile.println("Hello");
outFile.println("World!");
outFile.flush();
outFile.close();
Scala 提供了能直接读取文件的静态类,如下所示:
import scala.io.Source;
val inFile = Source.fromFile("io.txt");
val lines = inFile.getLines();
for(txt <- lines) {
println(s"txt= $txt")
}
受检异常:在编写代码的时候需要清楚知道可能抛出的异常并处理
不受检异常:在运行时才抛出
一般地,Scala 会把所有的异常都处理成不受检异常!当然,也可以手动捕获异常,如下所示:
import scala.io.Source
try {
val inputFile = Source.fromFile("io.txt", "GBK")
val lines = inputFile.getLines();
for(line <- lines) {
println(line)
}
} catch {
case ex: java.io.FileNotFoundException => {
println("找不到文件");
}
case ex: java.io.IOException => {
println("IOException");
}
}
数组是:具有相同数据类型的集合
Scala 数组声明的语法格式:
var arr:Array[String] = new Array[String](3)
或
var arr = new Array[String](3)
变量arr 是一个字符串类型的数组,数组长度为 3 ,可存储 3 个元素。我们可以为每个元素设置值,并通过索引来访问每个元素,需要注意的是:Scala 对数组下标的操作是使用小括号而不是中括号,如下所示:
arr(0) = 100;
println(arr(0));
上述方式是采用面向对象的方式声明了数组,Scala 还支持函数式编程的方式来声明数组,如下所示:
var arr:Array[String] = Array("Hello", "Scala");
或
var arr = Array("Hello", "Scala");
for ( x <- arr ) {
println(x)
}
通过 size 或者 length 属性能获得数组的长度,例如:
println("长度为 " + arr.size);
定义示例:
val arr = Array.ofDim[Int](2,3)
或
val arr: Array[Array[Int]] = Array(Array(0, 0, 0), Array(0, 0, 0))
赋值示例:
arr(0)(0) = 1
元组是对多个不同类型的简单封装的对象。如下所示:
val tuple = (1, 3.14, “Hello”);
取值操作:
println(tuple._1);
println(tuple._2);
println(tuple._3);
需要注意的是:tuple 中的数据是常量,不可更改。
Scala 提供了一套非常方便的容器库,比如序列,集合和映射等,它们的特质都被定义到了scala.collection 包里统一管理。从宏观角度来看,它们的层次结构如下所示:
序列是按照一定顺序存储一系列数据的容器。
var ls = List(1, 2, 3)
println(ls(0));
ls = ls.updated(1, 33);
println(ls(1));
println(ls.size);
println(ls.head);
println(ls.tail);
var ls2 = 4::ls;
println(ls2);
var ls3 = 3::4::55::Nil;
println(ls3);
默认情况下,声明的列表是不可变集,执行 updated 方法后,不会修改原List,而是生成新的List返回。列表结合符号 :: 是从右向左链接,并且生成新的List,同样,也不会修改原List。
val v = Vector(1,2);
var v2 = 3+:v;
println(v);
println(v2);
集合是一系列不重复元素的容器。
var s = Set("Hello", "World");
s += "Spark";
println(s.contains("Spark"));
映射是一系列键值对元素容器。
var m = Map("name" -> "Timor", "age" -> 18);
m.updated("name", "jack");
println(m("name"));
m += ("weight" -> 33.5);
println(m("weight"));
定义一个函数需要使用 def 关键字,def 是 define 的缩写。
Scala 标准定义示例:
Java 中和上述定义类似的写法:
int add(int a, int b) {
system.out.println(“正在执行 add 函数”);
return a + b;
}
和 Java 定义函数的四个不同:
Scala 的函数的声明一个像定义变量一样来实现,如下所示:
这样依赖,Scala 的函数可以像变量一样作为参数传递到函数中被使用,如下所示:
var arr = Array(2, 3, 6);
def map(ns:Array[Int], a:(Int)=>String) = {
var i = 0;
var ns2 = new Array[String](ns.size);
while(i < ns.size) {
ns2(i) = a(ns(i));
i += 1;
}
ns2;
}
var res = map(arr, v => "192.168.1." + v);
当 Lambda 表达式的参数只有一个的时候,可以省略小括号,将上述代码放置到交互式环境中执行完成之后,res 的值是:
res: Array[String] = Array(192.168.1.2, 192.168.1.3, 192.168.1.6)
类似 map这种能够在函数中调用函数的函数,被称之为高阶函数。Scala 的提供了大量的高阶函数来给开发者使用,如下所示:
scala> var arr = Array(2, 4, 7)
arr: Array[Int] = Array(2, 4, 7)
scala> arr.map(v => "192.168.1." + v)
res0: Array[String] = Array(192.168.1.2, 192.168.1.4, 192.168.1.7)
v => {"192.168.1." + v }
(v) => "192.168.1." + v
"192.168.1." + _
class Counter {
val value = 0;
var variable = 0;
def say() = {
println("Hi");
}
}
在交互式环境下使用该类:
scala> var a = new Counter
a: Counter = Counter@75b76c78
scala> println(a.value)
0
scala> a.variable = 200
a.variable: Int = 200
scala> a.say
Hi
很多时候,我们会选择使用一些关键字来修改成员,比如 public,protected,private等。来决定成员的可见范围。在 Scala 中,如果没有加任何修饰关键字的话,默认情况下是公有的。
一般地,我们会选择将字段设置为私有,然后声明 getter和setter 方法,使其能在外部被调用,例如:
class Counter {
// age 字段
private var _age = 0;
def age = _age;
def age_=(v:Int) = {
_age = v;
}
// name 字段
private var _name = "";
def name = _name;
def name_=(v:String) = {
_name = v;
}
}
在上述代码中,name和age就相当于 getName 和getAge 方法,name_= 和age_= 就相当于setName和setAge方法,具体使用如下:
scala> val a = new Counter
a: Counter = Counter@6d4b21a6
scala> a.age = 10
a.age: Int = 10
scala> a.name_=("timor")
scala> a.name
res19: String = timor
方法的定义需要注意以下几点:
在上述Counter 类中增加如下函数
def max(num:Int) = {
if (num > this._age) num else this._age;
}
在调用的时候可以按如下方式:
scala> a max 2
res24: Int = 10
scala> a.max(2)
res25: Int = 10
代码中的 a max 2
就是将函数max采用了中缀操作符的调用方法
在 Scala 中,类的主体就是类的主构造器。在类名后允许跟一对小括号用于在构造的时候传参,看起来就像一个函数一样,如下所示:
class Counter(var height:Int, val gender:Boolean, weight:Double) {
}
但是和函数不同的是,主构造器的参数名称前被允许使用 var 或 val 关键字。如果使用var关键字,Scala会自动为该名称生成对应的getter/setter方法。而使用val关键字,则只生成对应的 getter方法,不提供 setter方法。如果没有使用 var或者val关键字,则就是起到往主构造函数中传递参数的作用。
在Scala 类中,可以有多个辅助构造器,但是每个辅助构造器必须直接或间接调用主构造器才能被允许使用。如下所示:
class Counter(var height:Int, val gender:Boolean, weight:Double) {
println("main constructor");
def this() {
this(22, true, 3.14);
println("first assist constructor");
}
def this(weight:Double) {
this();
println("second assist constructor");
}
}
Scala 单例对象提供了和Java静态成员同样的功能,所有被定义到单例对象中的字段和方法都是静态的。其优点就是能够在不用实例化的时候就能直接使用其成员字段或者成员方法。如下所示:
object Counter {
private var value = 100;
def increase() = {
value += 1;
value;
}
}
使用示例:
scala> Counter.increase
res36: Int = 101
在同一个文件中,如果定义了和单例对象同名的类,它们两个就互为伴生关系,同名类就是这个单例对象的伴生类,而这个单例对象就是同名类的伴生对象。
在同一个文件中,如果没有和单例对象同名的类,则该单例对象就是孤立对象。
在所有的应用程序中,都必须有一个唯一的入口函数,而这个入口函数必须是静态的,所以,Scala 应用的main函数都是被定义到单例对象中。例如:
object Counter {
def main(args:Array[String]) {
println("Hello World!");
}
}
此时,我们可以把这段代码保存到 Counter.scala 文件,然后使用 scalac 命令编译该文件,最后使用 scala 命令来执行编译后的目标程序。