Scala介绍
设计目的
Scala是一门多范式的类java编程语言,集成了面向对象编程和函数式编程的各种特性。
可以与Java和C#实现无缝互操作(这两种对象都非纯面向对象)
兼容java: Scala类可以调用JAVA方法,创建java对象,继承java类和实现java接口。Scala 运行在Java虚拟机上,并兼容现有的Java程序。
Scala是纯面向对象
Scala是纯面向对象:每个值都是一个对象,也就是说scala没有类似java中的原生类型,一切皆对象,也就是说,在scala是可以对数字等基础类型调用方法的。
函数式语言
每个函数都是一个值(或者说对象),支持函数套嵌和高阶函数
支持模式匹配,用来操作代数式类型?
静态类型
Scala具备类型系统,通过编译时检查,保证代码的安全性和一致性。类型系统具体支持以下特性:
- 泛型类
- 协变和逆变
- 标注
- 类型参数的上下限约束
- 把类别和抽象类型作为对象成员
- 复合类型
- 引用自己时显式指定类型
- 视图
- 多态方法
扩展性
Scala的设计秉承一项事实,即在实践中,某个领域特定的应用程序开发往往需要特定于该领域的语言扩展。Scala提供了许多独特的语言机制,可以以库的形式轻易无缝添加新的语言结构:
- 任何方法可用作前缀或后缀操作符
- 可以根据预期类型自动构造闭包。
并发性
Scala使用Actor作为其并发模型,Actor是类似线程的实体,通过邮箱发收消息。
Actor可以复用线程,因此可以在程序中可以使用数百万个Actor,而线程只能创建数千个。
在2.10之后的版本中,使用Akka作为其默认Actor实现。
Scala Web 框架
以下列出了两个目前比较流行的 Scala 的 Web应用框架:
- Lift 框架
- Play 框架
数据类型
数据类型 描述
- Byte 8位有符号补码整数。数值区间为 -128 到 127
- Short 16位有符号补码整数。数值区间为 -32768 到 32767
- Int 32位有符号补码整数。数值区间为 -2147483648 到 2147483647
- Long 64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807
- Float 32 位, IEEE 754 标准的单精度浮点数
- Double 64 位 IEEE 754 标准的双精度浮点数
- Char 16位无符号Unicode字符, 区间值为 U+0000 到 U+FFFF
- String 字符序列
- Boolean true或false
- Unit 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。
- Null null 或空引用
- Nothing Nothing类型在Scala的类层级的最低端;它是任何其他类型的子类型。
- Any Any是所有其他类的超类
- AnyRef AnyRef类是Scala里所有引用类(reference class)的基类
作用域保护
private[x] 这里的x指代某个所属的包、类或单例对象。如果写成private[x],读作"这个成员除了对[…]中的类或[…]中的包中的类及它们的伴生对像可见外,对其它所有类都是private。
这种技巧在横跨了若干包的大型项目中非常有用,它允许你定义一些在你项目的若干子包中可见但对于项目外部的客户却始终不可见的东西。
Scala 中的 private 限定符,比 Java 更严格,在嵌套类情况下,外层类甚至不能访问被嵌套类的私有成员。
在 scala 中,对保护(Protected)成员的访问比 java 更严格一些。因为它只允许保护成员在定义了该成员的的类的子类中被访问。
循环
Scala 不支持 break 或 continue 语句,但从 2.8 版本后提供了一种中断循环的方式
import scala.util.control.Breaks
/**
* @author
* @create 2019/8/13
* @description
*/
object BreakDemo {
val loop = new Breaks
def main(args: Array[String]): Unit = {
var i = 0
loop.breakable(while(true){
println(i)
if(i > 5){
loop.break()
}
i += 1
})
}
}
for loop:
- var 不能在循环判断括号里。可以不定义var
- 可以用(;)来设置多个区间,循环会用笛卡尔积展开
var a = 0;
var b = 0;
// for 循环
for( a <- 1 to 2; b <- 1 to 2){
println( "Value of a: " + a );
println( "Value of b: " + b );
}
/* 输出
Value of a: 1
Value of b: 1
Value of a: 1
Value of b: 2
Value of a: 2
Value of b: 1
Value of a: 2
Value of b: 2 */
- for 循环过滤: “if express; express”可以使用分号(;)来为表达式添加一个或多个的过滤条件。
- 使用yield: “var newArray = for{ a <- numList if express; express}yield a” 会把当前的元素记下来,保存在集合中,循环结束后将返回该集合
// val numList = List(1,2,3,4,5,6,7,8,9,10);
// for( a <- numList if a != 3; if a < 8 ){
// println( "Value of a: " + a );
// }
val numList = List(1,2,3,4,5,6,7,8,9,10);
// for 后面用()和{} 都可以
val retVal = for( a <- numList
if a != 3; if a < 8
) yield a
for( a <- retVal){
println( "Value of a: " + a );
}
/* 上面两例子都输出
Value of a: 1
Value of a: 2
Value of a: 4
Value of a: 5
Value of a: 6
Value of a: 7
*/
Scala 方法与函数
Scala 有方法与函数,二者在语义上的区别很小。Scala 方法是类的一部分,而函数是一个对象,可以赋值给一个变量。换句话来说在类中定义的函数即是方法。
Scala 中的方法跟 Java 的类似,方法是组成类的一部分。
Scala 中的函数则是一个完整的对象
Scala 中使用 val/var 语句可以定义函数,def 语句定义方法。
方法
如果你不写等于号和方法主体,那么方法会被隐式声明为抽象(abstract),包含它的类型于是也是一个抽象类型,必须加abstract
方法可以直接调用,或者用对象调用
函数
- 函数传名调用:在函数内部,每次取参数时都会调用一次。
object CallByName {
def main(args: Array[String]) {
delayed(time());
}
def time() = {
println("获取时间,单位为纳秒")
System.nanoTime
}
// 注意 此处的=> 就是传名调用,没有这个=>就是传值调用
def delayed( t: => Long ) = {
println("在 delayed 方法内")
println("参数: " + t)
println("参数: " + t)
t
t
}
}
/* 输出:
在 delayed 方法内
获取时间,单位为纳秒
参数: 1675559648083415
获取时间,单位为纳秒
参数: 1675559648272007
获取时间,单位为纳秒
获取时间,单位为纳秒
*/
- 函数传值调用:在函数外部,先调用一次参数函数,将值传给函数
- 指定函数参数名:一般情况下函数调用参数,就按照函数定义时的参数顺序一个个传递。但是我们也可以通过指定函数参数名,并且不需要按照顺序向函数传递参数,实例如下:
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 );
}
}
- 可变参数:允许最后一个参数是可重复的,用*表示
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;
}
}
}
- 递归函数:自己调用自己
object Test {
def main(args: Array[String]) {
for (i <- 1 to 10)
println(i + " 的阶乘为: = " + factorial(i) )
}
def factorial(n: BigInt): BigInt = {
if (n <= 1)
1
else
n * factorial(n - 1)
}
}
- 默认参数值
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
}
}
/*
输出 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 函数内定义函数,定义在函数内的函数称之为局部函数。
以下实例我们实现阶乘运算,并使用内嵌函数:
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)
}
}
- 匿名函数
匿名函数
var inc = (x:Int) => x+1
- 偏应用函数:相当于一种函数封装,你不需要提供函数需要的所有参数,只需要提供部分,或不提供所需参数。
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)
}
}
- 函数柯里化Currying
柯里化(Currying)指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数。
def add(x:Int,y:Int)=x+y
def add(x:Int)(y:Int) = x + y
// 原理如下:
def add(x:Int) = (y:Int) => x+y
- Scala闭包
闭包是一个函数,这个函数依赖一个或多个自由变量,相当于将自由变量捕获而构成一个封闭的函数。
def outer(step: Int) : ()=>Int = {
var freeVariable = 1
def inner() = {
freeVariable += step
println(freeVariable)
freeVariable
}
inner
}
def main(args: Array[String]) {
val f = outer(3)
f()
f()
f()
}
字符串
在 Scala 中,字符串的类型实际上是 Java String,它本身没有 String 类。
在 Scala 中,String 是一个不可变的对象,所以该对象不可被修改。这就意味着你如果修改字符串就会产生一个新的字符串对象。
数组
var a: Array[String] = new Array[String](3)
var z = new Array[String](3)
z(0) = "Runoob"
z(4/2) = "Google"
var x = Array("Runoob", "Baidu", "Google")
// 多维数组
import Array._
var myMatrix = ofDim[Int](3,3) // 或者Array.ofDim
// 创建矩阵
for (i <- 0 to 2) {
for ( j <- 0 to 2) {
myMatrix(i)(j) = j;
}
}
var myList1 = Array(1.9, 2.9, 3.4, 3.5)
var myList2 = Array(8.9, 7.9, 0.4, 1.5)
var myList3 = concat( myList1, myList2)
// 输出所有数组元素
for ( x <- myList3 ) {
println( x )
}
// // 创建区间数组
// var myArr1 = range(10, 20, 2)
// var myArr2 = range(10, 20)
// // 输出所有数组元素
// for ( x <- myArr1 ) {
// print(x + " " )
// }
// println()
// for ( x <- myArr2 ) {
// print( x + " ")
// }
// println()
// var arr = Array.iterate(0, 3)(a => a+1)
// var arr = Array.apply(1)
// def f(x: Int) = x + 1
// var arr = Array.fill(5)(f(1))
// var arr = Array.tabulate(5)(s => s + 10)
// for ( x <- arr ) {
// print( x + " ")
// }
// println()
var arr = Array.tabulate(5, 2)((x, y) => (x + 10, y + 20))
println(arr)
for ( x <- arr ) {
for ( y <- x){
print( y + " ")
}
println()
}
println()
集合
Scala集合有三个基本操作:
- head 返回集合第一个元素
- tail 返回一个集合,包含除了第一元素之外的其他元素
- isEmpty 在集合为空时返回true
val list = List("Runoob", "Google")
val empty = List()
val empty2 = Nil
val dim = List(List(0, 1), List(2, 3), List(1, 2))
虽然可变Set和不可变Set都有添加或删除元素的操作,但是有一个非常大的差别。
对不可变Set进行操作,会产生一个新的set,原来的set并没有改变,这与List一样。 而对可变Set进行操作,改变的是该Set本身,与ListBuffer类似。
元组
与List一样,也是不可变的。但是可以包含不同类型的元素,最大长度为22。
Option
Option用来表示一个值是可选的(有值或无值)
Option[T] 是一个类型为 T 的可选值的容器: 如果值存在, Option[T] 就是一个 Some[T] ,如果不存在, Option[T] 就是对象 None 。
迭代器 Iterator
迭代器不是一个集合,而是一种访问集合的方法
object Test {
def main(args: Array[String]) {
val it = Iterator("Baidu", "Google", "Runoob", "Taobao")
while (it.hasNext){
println(it.next())
}
println("最大元素是:" + it.max )
println("最小元素是:" + it.min )
println("it.size 的值: " + it.size )
println("it.length 的值: " + it.length )
val it2 = Iterator(1, 2, 3)
val merge = it ++ it2
while (it.hasNext){
println(it.next())
}
}
}
类和对象
类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,占用存储空间。类是用于创建对象的蓝图
Scala中的类不声明为public,一个Scala源文件中可以有多个类。
Scala 的类定义可以有参数,称为类参数,类参数在整个类中都可以访问。
单例对象:在 Scala 中,是没有 static 这个东西的,但是它也为我们提供了单例模式的实现方法,那就是使用关键字 object。它和类的区别是,object对象不能带参数。
当单例对象与某个类共享同一个名称时,他被称作是这个类的伴生对象:companion object。你必须在同一个源文件里定义类和它的伴生对象。类被称为是这个单例对象的伴生类:companion class。
继承
Scala继承一个基类跟Java很相似, 但我们需要注意以下几点:
- 重写一个非抽象方法必须使用override修饰符。
- 只有主构造函数才可以往基类的构造函数里写参数。
- 在子类中重写超类的抽象方法时,你不需要使用override关键字。
Trait(特征)
Scala Trait(特征) 相当于 Java 的接口,实际上它比接口还功能强大。
与接口不同的是,它还可以定义属性和方法的实现。
特征构造顺序
特征也可以有构造器,由字段的初始化和其他特征体中的语句构成。这些语句在任何混入该特征的对象在构造时都会被执行。
构造器的执行顺序:
- 调用超类的构造器;
- 特征构造器在超类构造器之后、类构造器之前执行;
- 特征由左到右被构造;
- 每个特征当中,父特征先被构造;
- 如果多个特征共有一个父特征,父特征不会被重复构造
- 所有特征被构造完毕,子类被构造。
构造器的顺序是类的线性化的反向。线性化是描述某个类型的所有超类型的一种技术规格。
模式匹配
类似于java的switch语句
object Test {
def main(args: Array[String]) {
println(matchTest(3))
}
def matchTest(x: Int): String = x match {
case 1 => "one"
case 2 => "two"
case _: Int => "是整数" // 判断是否是Int类型
case _ => "many" // 通配符
}
}
match 表达式通过以代码编写的先后次序尝试每个模式来完成计算,只要发现有一个匹配的case,剩下的case不会继续匹配。
样例类
使用了case关键字的类定义就是就是样例类(case classes),样例类是种特殊的类,经过优化以用于模式匹配。
以下是样例类的简单实例:
object Test {
def main(args: Array[String]) {
val alice = Person("Alice", 25) // 可以去掉new关键字,因为为自动生成伴生对象,伴生对象中提供了apply方法
val bob = new Person("Bob", 32)
val charlie = new Person("Charlie", 32)
for (person <- List(alice, bob, charlie)) {
person match {
case Person("Alice", 25) => println("Hi Alice!")
case Person("Bob", 32) => println("Hi Bob!")
case Person(name, age) =>
println("Age: " + age + " year, name: " + name + "?")
}
}
}
// 样例类
case class Person(name: String, age: Int)
}
在声明样例类时,下面的过程自动发生了:
- 构造器的每个参数都成为val,除非显式被声明为var,但是并不推荐这么做;
- 生成伴生对象,在伴生对象中提供了apply方法,所以可以不使用new关键字就可构建对象;
- 提供unapply方法使模式匹配可以工作;
- 生成toString、equals、hashCode和copy方法,除非显示给出这些方法的定义。
正则表达式
import scala.util.matching.Regex
object Test {
def main(args: Array[String]) {
// 使用 String 类的 r() 方法构造了一个Regex对象。
val pattern = "Scala".r
val str = "Scala is scalable and cool"
println(pattern findFirstIn str)
val p1 = new Regex("(S|s)cala")
println((p1 findAllIn str).mkString(","))
// 不会改变之前的变量str
println((p1 replaceFirstIn(str, "Java")))
println(str)
val s = "我 is a good man"
var p2 = new Regex("\\w")
// \w 匹配数字、字母和下划线
println(p2 findFirstIn s)
var p3 = new Regex("\\G我")
println(p3 findFirstIn s)
}
}
异常处理
异常捕捉的机制与java中一样,唯一不同是:
在Scala里,借用了模式匹配的思想来做异常的匹配,因此,在catch的代码里,是一系列case字句,如下例所示:
import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException
object Test {
def main(args: Array[String]) {
try {
val f = new FileReader("input.txt")
} catch {
case ex: FileNotFoundException => {
println("Missing file exception")
}
case ex: IOException => {
println("IO Exception")
}
} finally {
println("Exiting finally...")
}
}
}
提取器(Extractor)
提取器是从传递给它的对象中提取出构造该对象的参数。
Scala 标准库包含了一些预定义的提取器,我们会大致的了解一下它们。
Scala 提取器是一个带有unapply方法的对象。unapply方法算是apply方法的反向操作:unapply接受一个对象,然后从对象中提取值,提取的值通常是用来构造该对象的值。
package extractor
object Test {
def main(args: Array[String]) {
println ("Apply 方法 : " + apply("Zara", "gmail.com"));
println ("Unapply 方法 : " + unapply("Zara@gmail.com"));
println ("Unapply 方法 : " + unapply("Zara Ali"));
// 在object中定义apply方法,就可以不使用new操作就可以创建对象
println(Test("asd", "zxcv"))
}
// 注入方法 (可选)
def apply(user: String, domain: String) = {
user +"@"+ domain
}
// 提取方法(必选)
def unapply(str: String): Option[(String, String)] = {
val parts = str split "@"
if (parts.length == 2){
Some(parts(0), parts(1))
}else{
None
}
}
}
提取器+模式匹配
在我们实例化一个类的时,可以带上0个或者多个的参数,编译器在实例化的时会调用 apply 方法。我们可以在类和对象中都定义 apply 方法。
就像我们之前提到过的,unapply 用于提取我们指定查找的值,它与 apply 的操作相反。 当我们在提取器对象中使用 match 语句时,unapply 将自动执行,如下所示:
package extractor
object ExtractorWithMatch {
def main(args: Array[String]) {
// 这里自动调用apply方法
val x = ExtractorWithMatch(5)
println(x)
x match
{
// 这里自动调用unapply方法,提取参数num的值
case ExtractorWithMatch(num) => println(x + " 是 " + num + " 的两倍!")
//unapply 被调用
case _ => println("无法计算")
}
}
def apply(x: Int) = x*2
def unapply(z: Int): Option[Int] = if (z % 2 == 0) Some( z / 2) else None
}
文件IO
Scala 进行文件写操作,可以直接用的都是 java中 的 I/O 类 (java.io.File):