admin管理员组文章数量:1665132
转自:【https://blog.csdn/c391183914/article/details/78647533?locationNum=2&fps=1】
- 一 Scala安装与配置
- 1 安装
- 2 配置IDEA
- 二 Scala基础
- 1 Hello Scala
- 11 IDEA运行HelloScala程序
- 12 控制台运行HelloScala程序
- 2 声明值和变量
- 3 常用类型
- 31 常用类型介绍
- 32 常用类型结构图
- 4 算数操作符重载
- 5 调用函数与方法
- 6 option类型
- 1 Hello Scala
- 三 控制结构和函数
- 1 if else表达式
- 2 while表达式
- 3 for表达式
- 4 函数
- 5 过程
- 6 懒值
- 7 异常
- 四 数据结构
- 1 数据结构特点
- 2 数组 Array
- 3 元组 Tuple
- 4 列表 List
- 5 队列 Queue
- 6 映射 Map
- 7 集 Set
- 8 集合元素与函数的映射
- 9 化简折叠扫描
- 10 拉链
- 11 迭代器
- 12 流 Stream
- 13 视图 View
- 14 线程安全的集合
- 15 并行集合
- 16 操作符
- 五 模式匹配
- 1 switch
- 2 守卫
- 3 模式中的变量
- 4 类型模式
- 5 匹配数组列表元组
- 6 提取器
- 7 变量声明中的模式
- 8 for表达式中的模式
- 9 样例类
- 10 Copy方法和带名参数
- 11 Case语句的中置缀表达式
- 12 匹配嵌套结构
- 13 密封类
- 14 模拟枚举
- 15 偏函数
- 六 高阶函数
- 1 作为参数的高阶函数
- 2 匿名函数
- 3 高阶函数
- 4 参数类型推断
- 5 闭包
- 6 柯里化
- 7 控制抽象
- 七 类
- 1 简单类和无参方法
- 2 Getter Setter方法
- 3 对象私有字段
- 4 Bean属性
- 5 构造器
- 6 嵌套类
- 八 对象
- 1 单例对象
- 2 伴生对象
- 3 Apply方法
- 4 应用程序对象
- 5 枚举
- 九 包和引用
- 1 包作用域
- 2 包对象
- 3 包可见性
- 4 引入
- 5 重命名和隐藏方法
- 十 继承
- 1 继承类
- 2 重写方法
- 3 类型检查和转换
- 4 受保护的字段和方法
- 5 超类的构造
- 6 重名字段
- 7 匿名子类
- 8 抽象类
- 9 构造顺序和提前定义
- 10 Scala继承层级
- 十一 特质
- 1 不允许多重集成
- 2 当做接口使用的特质
- 3 带有具体实现的特质
- 4 带有特质的对象
- 5 叠加在一起的特质
- 6 在特质中重写抽象方法
- 7 当做富接口使用的特质
- 8 特质中的具体字段
- 9 特质中的抽象字段
- 10 特质构造顺序
- 11 初始化特质中的字段
- 12 扩展类的特质
- 13 自身类型
- 十二 注解
- 1 什么可以被注解
- 2 注解参数
- 3 注解实现
- 4 针对Java的注解
- 5 由于优化的注解
- 十三 类型参数先了解即可
- 1 泛类型
- 2 泛型函数
- 3 类型变量限定
- 5 视图界定
- 6 上下文界定
- 7 Manifest上下文界定
- 8 多重界定
- 9 类型约束
- 10 型变
- 十四 隐式转换和隐式参数
- 1 隐式转换
- 2 利用隐式转换丰富类库功能
- 3 隐式值
- 4 隐式视图
- 5 隐式类
- 6 隐式的转换时机
- 7 隐式解析机制
- 8 隐式转换的前提
- 十五 文件和正则表达式
- 1 读取行
- 2 读取字符
- 3 读取词法单元和数字
- 4 读取网络资源文件写入控制台操作
- 5 序列化
- 6 进程控制
- 7 正则表达式
- 十六 高级类型
- 1 类型与类的区别
- 2 classOf与getClass的区别
- 3 单例类型
- 4 类型投影
- 5 类型别名
- 6 结构类型
- 7 复合类型
- 8 中置类型
- 9 自身类型
- 10 运行时反射
- 101 获取运行时类型信息
- 102 运行时类型实例化
- 103 运行时类成员的访问
一、 Scala安装与配置
1.1 安装
Scala需要Java运行时库,安装Scala需要首先安装JVM虚拟机,推荐安装JDK1.8。
在http://www.scala-lang/ 下载Scala2.11.8程序安装包
根据不同的操作系统选择不同的安装包,下载完成后,将安装包解压到安装目录。将scala安装目录下的bin目录加入到PATH环境变量:
SCALA_HOME: SCALA_HOME= D:\scala-2.11.8
PATH: 在PATH变量中添加:%SCALA_HOME%\bin
完成以上流程后,在命令行输入:scala,进入如下界面:
注意:该操作Windows和Linux配置流程是一样的。可以参考Java的JDK的配置过程。
到此为止,Scala的安装已经成功。
1.2 配置IDEA
1) 打开IDEA工具,如图:点击Configure
2) 点击Plugins
3) 点击Install plugin from disk
4) 选择scala的plugins
5) 此时会显示一个Scala的条目,在右侧点击Restart IntelliJ IDEA
6) 创建Maven项目
创建的maven项目默认是不支持scala的,需要为项目添加scala的framework,如图:
在这里选择Scala后,在右边的Use library中配置你的安装目录即可,最后点击OK。
7) 在项目的目录结构中,创建scala文件夹,并标记为source
8) 以上配置都完成后,就可以在scala上点击右键创建scala class了
二、 Scala基础
2.1 Hello Scala
2.1.1 IDEA运行HelloScala程序
1) 在scala上右键,创建scala object
2) 编写代码如下:
object HelloScala {
def main(args: Array[String]): Unit = {
println("Hello Scala")
}
}
右键Run HelloScala,如图:
2.1.2 控制台运行HelloScala程序
1) 打包
scala程序的打包过程和java工程的打包是一样的,因为我们都是使用maven构建的项目,所以你只需要一Java的Maven工程一致的打包方式打包项目即可。即:使用idea,右侧的maven工具,双击package即可,该步骤完成后会在项目的目录结构中生成一个target文件夹,其中有打包好的jar文件如图:
打包好的jar文件:
2) 运行
将该jar包复制到任意位置,然后打开cmd,执行命令: scala -cp C:\Users\61661\Desktop\scala-1.0-SNAPSHOT.jar HelloScala
即可看到效果如图:
2.2 声明值和变量
Scala声明变量有两种方式,一个用val,一个用var。
val / var 变量名 : 变量类型 = 变量值。
val定义的值是不可变的,它不是一个常量,是不可变量,或称之为只读变量。
val示例:
scala> val a1 = 10
scala> a1 = 20(此处会报错,因为val不允许初始化后再次修改a1变量的引用)
var示例:
scala> var a2 = 10
scala> a2 = 20
尖叫提示:
1、scala默认为匿名变量分配val
2、val定义的变量虽然不能改变其引用的内存地址,但是可以改变其引用的对象的内部的其他属性值。
3、为了减少可变性引起的bug,应该尽可能地使用不可变变量。变量类型可以省略,解析器会根据值进行推断。val和var声明变量时都必须初始化。
2.3 常用类型
2.3.1 常用类型介绍
Scala有8种数据类型:Byte、Char、Short、Int、Long、Float、Double以及Boolean。
类型 | 介绍 |
---|---|
Boolean | true 或者 false |
Byte | 8位, 有符号 |
Short | 16位, 有符号 |
Int | 32位, 有符号 |
Long | 64位, 有符号 |
Char | 16位, 无符号 |
Float | 32位, 单精度浮点数 |
Double | 64位, 双精度浮点数 |
String | 其实就是由Char数组组成 |
与Java中的数据类型不同,Scala并不区分基本类型和引用类型,所以这些类型都是对象,可以调用相对应的方法。String直接使用的是java.lang.String. 不过,由于String实际是一系列Char的不可变的集合,Scala中大部分针对集合的操作,都可以用于String,具体来说,String的这些方法存在于类scala.collection.immutable.StringOps中。
由于String在需要时能隐式转换为StringOps,因此不需要任何额外的转换,String就可以使用这些方法。
每一种数据类型都有对应的Rich* 类型,如RichInt、RichChar等,为基本类型提供了更多的有用操作。
2.3.2 常用类型结构图
Scala中,所有的值都是类对象,而所有的类,包括值类型,都最终继承自一个统一的根类型Any。统一类型,是Scala的又一大特点。更特别的是,Scala中还定义了几个底层类(Bottom Class),比如Null和Nothing。
1) Null是所有引用类型的子类型,而Nothing是所有类型的子类型。Null类只有一个实例对象,null,类似于Java中的null引用。null可以赋值给任意引用类型,但是不能赋值给值类型。
2) Nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于Nothing是其他任意类型的子类,他还能跟要求返回值的方法兼容。
3) Unit类型用来标识过程,也就是没有明确返回值的函数。 由此可见,Unit类似于Java里的void。Unit只有一个实例,(),这个实例也没有实质的意义。
2.4 算数操作符重载
+-*/%可以完成和Java中相同的工作,但是有一点区别,他们都是方法。你几乎可以用任何符号来为方法命名。
举例:
scala> 1 + 2
等同于:
scala> 1.+(2)
尖叫提示:Scala中没有++、–操作符,需要通过+=、-=来实现同样的效果。
2.5 调用函数与方法
在scala中,一般情况下我们不会刻意的去区分函数与方法的区别,但是他们确实是不同的东西。后面我们再详细探讨。首先我们要学会使用scala来调用函数与方法。
1) 调用函数,求方根
scala> import scala.math._
scala> sqrt(100)
2) 调用方法,静态方法(scala中没有静态方法这个概念,需要通过伴生类对象来实现)
scala> BigInt.probablePrime(16, scala.util.Random)
3) 调用方法,非静态方法,使用对象调用
scala> "HelloWorld".distinct
4) apply与update方法
apply方法是调用时可以省略方法名的方法。用于构造和获取元素:
"Hello"(4) 等同于 "Hello".apply(4)
Array(1,2,3) 等同于 Array.apply(1,2,3)
如:
println("Hello"(4))
println("Hello".apply(4))
在StringOps中你会发现一个 def apply(n: Int): Char方法定义。update方法也是调用时可以省略方法名的方法,用于元素的更新:
arr(4) = 5 等同于 arr.update(4,5)
如:
val arr1 = new Array[Int](5)
arr1(1) = 2
arr1.update(1, 2)
println(arr1.mkString(","))
2.6 option类型
Scala为单个值提供了对象的包装器,表示为那种可能存在也可能不存在的值。他只有两个有效的子类对象,一个是Some,表示某个值,另外一个是None,表示为空,通过Option的使用,避免了使用null、空字符串等方式来表示缺少某个值的做法。
如:
val map1 = Map("Alice" -> 20, "Bob" -> 30)
println(map1.get("Alice"))
println(map1.get("Jone"))
三、 控制结构和函数
3.1 if else表达式
scala中没有三目运算符,因为根本不需要。scala中if else表达式是有返回值的,如果if或者else返回的类型不一样,就返回Any类型(所有类型的公共超类型)。
例如:if else返回类型一样
val a3 = 10
val a4 =
if(a3 > 20){
"a3大于20"
}else{
"a3小于20"
}
println(a4)
例如:if else返回类型不一样
val a5 =
if(a3 > 20){
"a3大于20"
}
println(a5)
如果缺少一个判断,什么都没有返回,但是Scala认为任何表达式都会有值,对于空值,使用Unit类,写做(),叫做无用占位符,相当于java中的void。
尖叫提示:行尾的位置不需要分号,只要能够从上下文判断出语句的终止即可。但是如果在单行中写多个语句,则需要分号分割。在Scala中,{}快包含一系列表达式,其结果也是一个表达式。块中最后一个表达式的值就是块的值。
3.2 while表达式
Scala提供和Java一样的while和do循环,与If语句不同,While语句本身没有值,即整个While语句的结果是Unit类型的()。
1) while循环
var n = 1;
val while1 = while(n <= 10){
n += 1
}
println(while1)
println(n)
2) while循环的中断
import scala.util.control.Breaks
val loop = new Breaks
loop.breakable{
while(n <= 20){
n += 1;
if(n == 18){
loop.break()
}
}
}
println(n)
尖叫提示:scala并没有提供break和continue语句来退出循环,如果需要break,可以通过几种方法来做1、使用Boolean型的控制变量 2、使用嵌套函数,从函数中return 3、使用Breaks对象的break方法。
3.3 for表达式
Scala 也为for 循环这一常见的控制结构提供了非常多的特性,这些for 循环的特性被称为for 推导式(for comprehension)或for 表达式(for expression)。
1) for示例1:to左右两边为前闭后闭的访问
for(i <- 1 to 3; j <- 1 to 3){
print(i * j + " ")
}
println()
2) for示例2:until左右两边为前闭后开的访问
for(i <- 1 until 3; j <- 1 until 3) {
print(i * j + " ")
}
println()
3) for示例3:引入保护式(也称条件判断式)该语句只打印1 3。保护式满足为true则进入循环内部,满足为false则跳过,类似于continue
for(i <- 1 to 3 if i != 2) {
print(i + " ")
}
println()
4) for示例4:引入变量
for(i <- 1 to 3; j = 4 - i) {
print(j + " ")
}
println()
5) for示例5:将遍历过程中处理的结果返回到一个,使用yield关键字
val for5 = for(i <- 1 to 10) yield i
println(for5)
6) for示例6:使用花括号{}代替小括号()
for{
i <- 1 to 3
j = 4 - i}
print(i * j + " ")
println()
尖叫提示:{}和()对于for表达式来说都可以。for 推导式有一个不成文的约定:当for 推导式仅包含单一表达式时使用原括号,当其包含多个表达式时使用大括号。值得注意的是,使用原括号时,早前版本的Scala 要求表达式之间必须使用分号。
3.4 函数
scala定义函数的标准格式为: def 函数名(参数名1: 参数类型1, 参数名2: 参数类型2) : 返回类型 = {函数体}
1) 函数示例1:返回Unit类型的函数
def shout1(content: String) : Unit = {
println(content)
}
2) 函数示例2:返回Unit类型的函数,但是没有显式指定返回类型。(当然也可以返回非Unit类型的值)
def shout2(content: String) = {
println(content)
}
3) 函数示例3:返回值类型有多种可能,此时也可以省略Unit
def shout3(content: String) = {
if(content.length >= 3)
content + "喵喵喵~"
else
3
}
4) 函数示例4:带有默认值参数的函数,调用该函数时,可以只给无默认值的参数传递值,也可以都传递,新值会覆盖默认值;传递参数时如果不按照定义顺序,则可以通过参数名来指定。
def shout4(content: String, leg: Int = 4) = {
println(content + "," + leg)
}
5) 函数示例5:变长参数(不确定个数参数,类似Java的…)
def sum(args: Int*) = {
var result = 0
for(arg <- args)
result += arg
result
}
6) 递归函数:递归函数在使用时必须有明确的返回值类型
def factorial(n: Int): Int = {
if(n <= 0)
1
else
n * factorial(n - 1)
}
尖叫提示:
1、Scala可以通过=右边的表达式 推断出函数的返回类型。如果函数体需要多个表达式,可以用代码块{}。
2、可以把return 当做 函数版本的break语句。
3、递归函数一定要指定返回类型。
4、变长参数通过*来指定,所有参数会转化为一个seq序列。
3.5 过程
我们将函数的返回类型为Unit的函数称之为过程。
1) 定义过程示例1:
def shout1(content: String) : Unit = {
println(content)
}
2) 定义过程示例2:
def shout1(content: String) = {
println(content)
}
3) 定义过程示例3:
def shout1(content: String) {
println(content)
}
尖叫提示:这只是一个逻辑上的细分,如果因为该概念导致了理解上的混淆,可以暂时直接跳过过程这样的描述。毕竟过程,在某种意义上也是函数。
3.6 懒值
当val被声明为lazy时,他的初始化将被推迟,直到我们首次对此取值,适用于初始化开销较大的场景。
1) lazy示例:通过lazy关键字的使用与否,来观察执行过程
object Lazy {
def init(): String = {
println("init方法执行")
"嘿嘿嘿,我来了~"
}
def main(args: Array[String]): Unit = {
lazy val msg = init()
println("lazy方法没有执行")
println(msg)
}
}
3.7 异常
当碰到异常情况时,方法抛出一个异常,终止方法本身的执行,异常传递到其调用者,调用者可以处理该异常,也可以升级到它的调用者。运行系统会一直这样升级异常,直到有调用者能处理它。 如果一直没有处理,则终止整个程序。
Scala的异常的工作机制和Java一样,但是Scala没有“checked”异常,你不需要声明说函数或者方法可能会抛出某种异常。受检异常在编译器被检查,java必须声明方法所会抛出的异常类型。
抛出异常:用throw关键字,抛出一个异常对象。所有异常都是Throwable的子类型。throw表达式是有类型的,就是Nothing,因为Nothing是所有类型的子类型,所以throw表达式可以用在需要类型的地方。
捕捉异常:在Scala里,借用了模式匹配的思想来做异常的匹配,因此,在catch的代码里,是一系列case字句。
异常捕捉的机制与其他语言中一样,如果有异常发生,catch字句是按次序捕捉的。因此,在catch字句中,越具体的异常越要靠前,越普遍的异常越靠后。 如果抛出的异常不在catch字句中,该异常则无法处理,会被升级到调用者处。
finally字句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作。
1) 异常示例:
object ExceptionSyllabus {
def divider(x: Int, y: Int): Float= {
if(y == 0) throw new Exception("0作为了除数")
else x / y
}
def main(args: Array[String]): Unit = {
try {
println(divider(10, 3))
} catch {
case ex: Exception => println("捕获了异常:" + ex)
} finally {}
}
}
四、 数据结构
4.1 数据结构特点
Scala同时支持可变集合和不可变集合,不可变集合从不可变,可以安全的并发访问。
两个主要的包:
不可变集合:scala.collection.immutable
可变集合: scala.collection.mutable
Scala优先采用不可变集合,对于几乎所有的集合类,Scala都同时提供了可变和不可变的版本。
不可变集合继承层次:
可变集合继承层次:
4.2 数组 Array
1) 定长数组
//定义
val arr1 = new Array[Int](10)
//赋值
arr1(1) = 7
或:
//定义
val arr1 = Array(1, 2)
2) 变长数组
//定义
val arr2 = ArrayBuffer[Int]()
//追加值
arr2.append(7)
//重新赋值
arr2(0) = 7
3) 定长数组与变长数组的转换
arr1.toBuffer
arr2.toArray
4) 多维数组
//定义
val arr3 = Array.ofDim[Double](3,4)
//赋值
arr3(1)(1) = 11.11
5) 与Java数组的互转
Scal数组转Java数组:
val arr4 = ArrayBuffer("1", "2", "3")
//Scala to Java
import scala.collection.JavaConversions.bufferAsJavaList
val javaArr = new ProcessBuilder(arr4)
println(javaArrmand())
Java数组转Scala数组:
import scala.collection.JavaConversions.asScalaBuffer
import scala.collection.mutable.Buffer
val scalaArr: Buffer[String] = javaArrmand()
println(scalaArr)
6) 数组的遍历
for(x <- arr1) {
println(x)
}
4.3 元组 Tuple
元组也是可以理解为一个容器,可以存放各种相同或不同类型的数据。
1) 元组的创建
val tuple1 = (1, 2, 3, "heiheihei")
println(tuple1)
2) 元组数据的访问,注意元组元素的访问有下划线,并且访问下标从1开始,而不是0
val value1 = tuple1._4
println(value1)
3) 元组的遍历
方式1:
for (elem <- tuple1.productIterator) {
print(elem)
}
println()
方式2:
tuple1.productIterator.foreach(i => println(i))
tuple1.productIterator.foreach(print(_))
4.4 列表 List
如果List列表为空,则使用Nil来表示。
1) 创建List
val list1 = List(1, 2)
println(list1)
2) 访问List元素
val value1 = list1(1)
println(value1)
3) List元素的追加
val list2 = list1 :+ 99
println(list2)
val list3 = 100 +: list1
println(list3)
4) List的创建与追加,符号“::”,注意观察去掉Nil和不去掉Nil的区别
val list4 = 1 :: 2 :: 3 :: list1 :: Nil
println(list4)
4.5 队列 Queue
队列数据存取符合先进先出策略
1) 队列的创建
import scala.collection.mutable
val q1 = new mutable.Queue[Int]
println(q1)
2) 队列元素的追加
q1 += 1;
println(q1)
3) 向队列中追加List
q1 ++= List(2, 3, 4)
println(q1)
4) 按照进入队列的顺序删除元素
q1.dequeue()
println(q1)
5) 塞入数据
q1.enqueue(9, 8, 7)
println(q1)
6) 返回队列的第一个元素
println(q1.head)
7) 返回队列最后一个元素
println(q1.last)
8) 返回除了第一个以外的元素
println(q1.tail)
4.6 映射 Map
这个地方的学习,就类比Java的map集合学习即可。
1) 构造不可变映射
val map1 = Map("Alice" -> 10, "Bob" -> 20, "Kotlin" -> 30)
2) 构造可变映射
val map2 = scala.collection.mutable.Map("Alice" -> 10, "Bob" -> 20, "Kotlin" -> 30)
3) 空的映射
val map3 = new scala.collection.mutable.HashMap[String, Int]
4) 对偶元组
val map4 = Map(("Alice", 10), ("Bob", 20), ("Kotlin", 30))
5) 取值
如果映射中没有值,则会抛出异常,使用contains方法检查是否存在key。如果通过 映射.get(键) 这样的调用返回一个Option对象,要么是Some,要么是None。
val value1 = map1("Alice")//建议使用get方法得到map中的元素
println(value1)
6) 更新值
map2("Alice") = 99
println(map2("Alice"))
或:
map2 += ("Bob" -> 99)
map2 -= ("Alice", "Kotlin")
println(map2)
或:
val map5 = map2 + ("AAA" -> 10, "BBB" -> 20)
println(map5)
7) 遍历
for ((k, v) <- map1) println(k + " is mapped to " + v)
for (v <- map1.keys) println(v)
for (v <- map1.values) println(v)
for(v <- map1) prinln(v)
4.7 集 Set
集是不重复元素的结合。集不保留顺序,默认是以哈希集实现。
如果想要按照已排序的顺序来访问集中的元素,可以使用SortedSet(已排序数据集),已排序的数据集是用红黑树实现的。
默认情况下,Scala 使用的是不可变集合,如果你想使用可变集合,需要引用 scala.collection.mutable.Set 包。
1) Set不可变集合的创建
val set = Set(1, 2, 3)
println(set)
2) Set可变集合的创建,如果import了可变集合,那么后续使用默认也是可变集合
import scala.collection.mutable.Set
val mutableSet = Set(1, 2, 3)
3) 可变集合的元素添加
mutableSet.add(4)
mutableSet += 6
// 注意该方法返回一个新的Set集合,而非在原有的基础上进行添加
mutableSet.+(5)
4) 可变集合的元素删除
mutableSet -= 1
mutableSet.remove(2)
println(mutableSet)
5) 遍历
for(x <- mutableSet) {
println(x)
}
6) Set更多常用操作
序号 | 方法 | 描述 |
---|---|---|
1 | def +(elem: A): Set[A] | 为集合添加新元素,并创建一个新的集合,除非元素已存在 |
2 | def -(elem: A): Set[A] | 移除集合中的元素,并创建一个新的集合 |
3 | def contains(elem: A): Boolean | 如果元素在集合中存在,返回 true,否则返回 false。 |
4 | def &(that: Set[A]): Set[A] | 返回两个集合的交集 |
5 | def &~(that: Set[A]): Set[A] | 返回两个集合的差集 |
6 | def ++(elems: A): Set[A] | 合并两个集合 |
7 | def drop(n: Int): Set[A]] | 返回丢弃前n个元素新集合 |
8 | def dropRight(n: Int): Set[A] | 返回丢弃最后n个元素新集合 |
9 | def dropWhile(p: (A) => Boolean): Set[A] | 从左向右丢弃元素,直到条件p不成立 |
10 | def max: A | 查找最大元素 |
11 | def min: A | 查找最小元素 |
12 | def take(n: Int): Set[A] | 返回前 n 个元素 |
4.8 集合元素与函数的映射
1) map:将集合中的每一个元素映射到某一个函数
val names = List("Alice", "Bob", "Nick")
println(names.map(_.toUpperCase))
2) flatmap:flat即压扁,压平,扁平化,效果就是将集合中的每个元素的子元素映射到某个函数并返回新的集合
val names = List("Alice", "Bob", "Nick")
println(names.flatMap(_.toUpperCase()))
4.9 化简、折叠、扫描
1) 折叠,化简:将二元函数引用于集合中的函数
val list = List(1, 2, 3, 4, 5)
val i1 = list.reduceLeft(_ - _)
val i2 = list.reduceRight(_ - _)
println(i1)
println(i2)
.reduceLefft(_ - _)这个函数的执行逻辑如图所示:
.reduceRight(_ - _)反之同理
2) 折叠,化简:fold
fold函数将上一步返回的值作为函数的第一个参数继续传递参与运算,直到list中的所有元素被遍历。可以把reduceLeft看做简化版的foldLeft。相关函数:fold,foldLeft,foldRight,可以参考reduce的相关方法理解。
val list2 = List(1, 9, 2, 8)
val i4 = list2.fold(5)((sum, y) => sum + y)
println(i4)
foldRight
val list3 = List(1, 9, 2, 8)
val i5 = list3.foldRight(100)(_ - _)
println(i5)
尖叫提示:foldLeft和foldRight有一种缩写方法对应分别是/:和:\
例如:
foldLeft
val list4 = List(1, 9, 2, 8)
val i6 = (0 /: list4)(_ - _)
println(i6)
3) 统计一句话中,各个文字出现的次数
val sentence = "一首现代诗《笑里藏刀》:哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈刀哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈"
//m + (“一” -> 1, “首” -> 1, “哈” -> 1)
val i7 = (Map[Char, Int]() /: sentence)((m, c) => m + (c -> (m.getOrElse(c, 0) + 1)))
println(i7)
4) 折叠,化简,扫描
这个理解需要结合上面的知识点,扫描,即对某个集合的所有元素做fold操作,但是会把产生的所有中间结果放置于一个集合中保存。
val i8 = (1 to 10).scanLeft(0)(_ + _)
println(i8)
4.10 拉链
val list1 = List("15837312345", "13737312345", "13811332299")
val list2 = List(17, 87)
println(i1)
4.11 迭代器
你可以通过iterator方法从集合获得一个迭代器,通过while循环和for表达式对集合进行遍历。
val iterator = List(1, 2, 3, 4, 5).iterator
while (iterator.hasNext) {
println(iterator.next())
}
或:
for(enum <- iterator) {
println(enum)
}
4.12 流 Stream
stream是一个集合。这个集合,可以用于存放,无穷多个元素,但是这无穷个元素并不会一次性生产出来,而是需要用到多大的区间,就会动态的生产,末尾元素遵循lazy规则。
1) 使用#::得到一个stream
def numsForm(n: BigInt) : Stream[BigInt] = n #:: numsForm(n + 1)
2) 传递一个值,并打印stream集合
val tenOrMore = numsForm(10)
println(tenOrMore)
3) tail的每一次使用,都会动态的向stream集合按照规则生成新的元素
println(tenOrMore.tail)
println(tenOrMore)
4) 使用map映射stream的元素并进行一些计算
println(numsForm(5).map(x => x * x))
4.13 视图 View
Stream的懒执行行为,你可以对其他集合应用view方法来得到类似的效果,该方法产出一个其方法总是被懒执行的集合。但是view不会缓存数据,每次都要重新计算。
例如:我们找到10万以内,所有数字倒序排列还是它本身的数字。
val viewSquares = (1 to 100000)
.view
.map(x => {
// println(x)
x.toLong * x.toLong
}).filter(x => {
x.toString == x.toString.reverse
})
println(viewSquares(3))
for(x <- viewSquares){
print(x + ",")
}
4.14 线程安全的集合
所有线程安全的集合都是以Synchronized开头的集合,例如:
SynchronizedBuffer
SynchronizedMap
SynchronizedPriorityQueue
SynchronizedQueue
SynchronizedSet
SynchronizedStack
4.15 并行集合
Scala为了充分使用多核CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算。
主要用到的算法有:
Divide and conquer : 分治算法,Scala通过splitters,combiners等抽象层来实现,主要原理是将计算工作分解很多任务,分发给一些处理器去完成,并将它们处理结果合并返回
Work stealin:算法,主要用于任务调度负载均衡(load-balancing),通俗点完成自己的所有任务之后,发现其他人还有活没干完,主动(或被安排)帮他人一起干,这样达到尽早干完的目的。
1) 打印1~5
(1 to 5).foreach(println(_))
println()
(1 to 5).par.foreach(println(_))
2) 查看并行集合中元素访问的线程
val result1 = (0 to 10000).map{case _ => Thread.currentThread.getName}.distinct
val result2 = (0 to 10000).par.map{case _ => Thread.currentThread.getName}.distinct
println(result1)
println(result2)
4.16 操作符
这部分内容没有必要刻意去理解和记忆,语法使用的多了,自然就会产生感觉,该部分内容暂时大致了解一下即可。
1) 如果想在变量名、类名等定义中使用语法关键字(保留字),可以配合反引号反引号:
val `val` = 42
2) 这种形式叫中置操作符,A操作符B等同于A.操作符(B)
3) 后置操作符,A操作符等同于A.操作符,如果操作符定义的时候不带()则调用时不能加括号
4) 前置操作符,+、-、!、~等操作符A等同于A.unary_操作符
5) 赋值操作符,A操作符=B等同于A=A操作符B
五、 模式匹配
5.1 switch
与default等效的是捕获所有的case_ 模式。如果没有模式匹配,抛出MatchError,每个case中,不用break语句。可以在match中使用任何类型,而不仅仅是数字。
var result = 0;
val op : Char = '-'
op match {
case '+' => result = 1
case '-' => result = -1
case _ => result = 0
}
println(result)
5.2 守卫
像if表达式一样,match也提供守卫功能,守卫可以是任何Boolean条件
for (ch <- "+-3!") {
var sign = 0
var digit = 0
ch match {
case '+' => sign = 1
case '-' => sign = -1
case _ if ch.toString.equals("3") => digit = 3
case _ => sign = 0
}
println(ch + " " + sign + " " + digit)
}
5.3 模式中的变量
如果在case关键字后跟变量名,那么match前表达式的值会赋给那个变量。
val str = "+-3!"
for (i <- str.indices) {
var sign = 0
var digit = 0
str(i) match {
case '+' => sign = 1
case '-' => sign = -1
case ch if Character.isDigit(ch) => digit = Character.digit(ch, 10)
case _ =>
}
println(str(i) + " " + sign + " " + digit)
}
5.4 类型模式
可以匹配对象的任意类型,但是不能直接匹配泛型类型,这样描述比较抽象,看下面的例子:这样做的意义在于,避免了使用isInstanceOf和asInstanceOf方法。
val a = 8
val obj = if(a == 1) 1
else if(a == 2) "2"
else if(a == 3) BigInt(3)
else if(a == 4) Map("aa" -> 1)
else if(a == 5) Map(1 -> "aa")
else if(a == 6) Array(1, 2, 3)
else if(a == 7) Array("aa", 1)
else if(a == 8) Array("aa")
val r1 = obj match {
case x: Int => x
case s: String => s.toInt
case BigInt => -1 //不能这么匹配
case _: BigInt => Int.MaxValue
case m: Map[String, Int] => "Map[String, Int]类型的Map集合"
case m: Map[_, _] => "Map集合"
case a: Array[Int] => "It's an Array[Int]"
case a: Array[String] => "It's an Array[String]"
case a: Array[_] => "It's an array of something other than Int"
case _ => 0
}
println(r1 + ", " + r1.getClass.getName)
尖叫提示:Map类型的泛型在匹配的时候,会自动删除泛型类型,只会匹配到Map类型,而不会精确到Map里面的泛型类型。
5.5 匹配数组、列表、元组
Array(0) 匹配只有一个元素且为0的数组。
Array(x,y) 匹配数组有两个元素,并将两个元素赋值为x和y。
Array(0,_*) 匹配数组以0开始。
1) 匹配数组
for (arr <- Array(Array(0), Array(1, 0), Array(0, 1, 0), Array(1, 1, 0), Array(1, 1, 0, 1))) {
val result = arr match {
case Array(0) => "0"
case Array(x, y) => x + " " + y
case Array(x, y, z) => x + " " + y + " " + z
case Array(0, _*) => "0..."
case _ => "something else"
}
println(result)
}
2) 匹配列表
与匹配数组相似,同样可以应用于列表
for (lst <- Array(List(0), List(1, 0), List(0, 0, 0), List(1, 0, 0))) {
val result = lst match {
case 0 :: Nil => "0"
case x :: y :: Nil => x + " " + y
case 0 :: tail => "0 ..."
case _ => "something else"
}
println(result)
}
3) 匹配元组
同样可以应用于元组
for (pair <- Array((0, 1), (1, 0), (1, 1))) {
val result = pair match {
case (0, _) => "0 ..."
case (y, 0) => y + " 0"
case _ => "neither is 0"
}
println(result)
}
5.6 提取器
模式匹配,什么才算是匹配呢?即,case中unapply方法返回some集合则为匹配成功,返回none集合则为匹配失败。下面我们来看几个例子做详细探讨。
1) unapply
—— 调用unapply,传入number
—— 接收返回值并判断返回值是None,还是Some
—— 如果是Some,则将其解开,并将其中的值赋值给n(就是case Square(n)中的n)
创建object Square:
object Square {
def unapply(z: Double): Option[Double] = Some(math.sqrt(z))
}
模式匹配使用:
val number: Double = 36.0
number match {
case Square(n) => println(s"square root of $number is $n")
case _ => println("nothing matched")
}
2) unapplySeq
—— 调用unapplySeq,传入namesString
—— 接收返回值并判断返回值是None,还是Some
—— 如果是Some,则将其解开
—— 判断解开之后得到的sequence中的元素的个数是否是三个
—— 如果是三个,则把三个元素分别取出,赋值给first,second和third
创建object Names:
object Names {
def unapplySeq(str: String): Option[Seq[String]] = {
if (str.contains(",")) Some(str.split(","))
else None
}
}
模式匹配使用:
val namesString = "Alice,Bob,Thomas"
namesString match {
case Names(first, second, third) => {
println("the string contains three people's names")
println(s"$first $second $third")
}
case _ => println("nothing matched")
}
5.7 变量声明中的模式
match中每一个case都可以单独提取出来,意思是一样的,如下:
val (x, y) = (1, 2)
val (q, r) = BigInt(10) /% 3
val arr = Array(1, 7, 2, 9)
val Array(first, second, _*) = arr
println(first, second)
5.8 for表达式中的模式
import scala.collection.JavaConverters._
for ((k, v) <- System.getProperties.asScala)
println(k + " -> " + v)
for ((k, "") <- System.getProperties.asScala)
println(k)
for ((k, v) <- System.getProperties.asScala if v == "")
println(k)
尖叫提示:for中匹配会自动忽略失败的匹配
5.9 样例类
样例类首先是类,除此之外它是为模式匹配而优化的类,样例类用case关键字进行声明:
1) 样例类的创建
package unit6
abstract class Amount
case class Dollar(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
case object Nothing extends Amount
2) 当我们有一个类型为Amount的对象时,我们可以用模式匹配来匹配他的类型,并将属性值绑定到变量:
for (amt <- Array(Dollar(1000.0), Currency(1000.0, "EUR"), Nothing)) {
val result = amt match {
case Dollar(v) => "$" + v
case Currency(_, u) => u
case Nothing => ""
}
println(amt + ": " + result)
}
当你声明样例类时,如下几件事情会自动发生:
—— 构造其中的每一个参数都成为val——除非它被显式地声明为var(不建议这样做)
—— 在半生对象中提供apply方法让你不用new关键字就能构造出相应的对象,比如Dollar(29.95)或Currency(29.95, “EUR”)
—— 提供unapply方法让模式匹配可以工作
—— 将生成toString、equals、hashCode和copy方法——除非显式地给出这些方法的定义。
除上述外,样例类和其他类型完全一样。你可以添加方法和字段,扩展它们。
5.10 Copy方法和带名参数
copy创建一个与现有对象值相同的新对象,并可以通过带名参数来修改某些属性。
val amt = Currency(29.95, "EUR")
val price = amt.copy(value = 19.95)
println(amt)
println(price)
println(amt.copy(unit = "CHF"))
5.11 Case语句的中置(缀)表达式
什么是中置表达式?1 + 2,这就是一个中置表达式。如果unapply方法产出一个元组,你可以在case语句中使用中置表示法。比如可以匹配一个List序列。
List(1, 7, 2, 9) match {
case first :: second :: rest => println(first + second + rest.length)
case _ => 0
}
5.12 匹配嵌套结构
比如某一系列商品想捆绑打折出售
1) 创建样例类
abstract class Item
case class Article(description: String, price: Double) extends Item
case class Bundle(description: String, discount: Double, item: Item*) extends Item
2) 匹配嵌套结构
val sale = Bundle("愚人节大甩卖系列", 10,
Article("《九阴真经》", 40),
Bundle("从出门一条狗到装备全发光的修炼之路系列", 20,
Article("《如何快速捡起地上的装备》", 80),
Article("《名字起得太长躲在树后容易被地方发现》",30)))
3) 将descr绑定到第一个Article的描述
val result1 = sale match {
case Bundle(_, _, Article(descr, _), _*) => descr
}
println(result1)
4) 通过@表示法将嵌套的值绑定到变量。_*绑定剩余Item到rest
val result2 = sale match {
case Bundle(_, _, art @ Article(_, _), rest @ _*) => (art, rest)
}
println(result2)
5) 不使用_*绑定剩余Item到rest
val result3 = sale match {
case Bundle(_, _, art @ Article(_, _), rest) => (art, rest)
}
println(result3)
6) 计算某个Item价格的函数,并调用
def price(it: Item): Double = {
it match {
case Article(_, p) => p
case Bundle(_, disc, its@_*) => its.map(price _).sum - disc
}
}
println(SwitchBaseSyllabus.price(sale))
5.13 密封类
如果想让case类的所有子类都必须在申明该类的相同的文件中定义,可以将样例类的通用超类声明为sealed,叫做密封类,密封就是外部用户不能在其他文件中定义子类。
5.14 模拟枚举
样例类可以模拟出枚举类型
1) 创建密封样例类(不密封也可以,在这里只是为了用一下sealed关键字)
package unit6
sealed abstract class TrafficLightColor
case object Red extends TrafficLightColor
case object Yellow extends TrafficLightColor
case object Green extends TrafficLightColor
2) 模拟枚举
for (color <- Array(Red, Yellow, Green))
println(
color match {
case Red => "stop"
case Yellow => "slowly"
case Green => "go"
})
5.15 偏函数
偏函数,它只对会作用于指定类型的参数或指定范围值的参数实施计算
val f: PartialFunction[Char, Int] = {
case '+' => 1
case '-' => -1
}
println(f('-'))
println(f.isDefinedAt('0'))
println(f('+'))
// println(f('0'))
再深入探讨一点点:
我们定义一个将List集合里面数据+1的偏函数:
val f1 = new PartialFunction[Any, Int] {
def apply(any: Any) = any.asInstanceOf[Int] + 1
def isDefinedAt(any: Any) = if (any.isInstanceOf[Int]) true else false
}
val rf1 = List(1, 3, 5, "seven") collect f1
println(rf1)
如上的功能,等同于:
def f2: PartialFunction[Any, Int] = {
case i: Int => i + 1
}
val rf2 = List(1, 3, 5, "seven") collect f2
println(rf2)
六、 高阶函数
6.1 作为参数的高阶函数
函数作为一个变量传入到了另一个函数中,那么该作为参数的函数的类型是:function1,即:(参数类型) => 返回类型
def plus(x: Int) = 3 + x
val result1 = Array(1, 2, 3, 4).map(plus(_))
println(result1.mkString(","))
尖叫提示:带有一个参数的函数的类型是function1,带有两个是function2,以此类推
6.2 匿名函数
即没有名字的函数,可以通过函数表达式来设置匿名函数。
val triple = (x: Double) => 3 * x
println(triple(3))
6.3 高阶函数
能够接受函数作为参数的函数,叫做高阶函数。
1) 高阶函数的使用
def highOrderFunction1(f: Double => Double) = f(10)
def minus7(x: Double) = x - 7
val result2 = highOrderFunction1(minus7)
println(result2)
2) 高阶函数同样可以返回函数类型
def minusxy(x: Int) = (y: Int) => x - y
val result3 = minusxy(3)(5)
println(result3)
6..4 参数(类型)推断
// 传入函数表达式
highOrderFunction1((x: Double) => 3 * x)
// 参数推断省去类型信息
highOrderFunction1((x) => 3 * x)
// 单个参数可以省去括号
highOrderFunction1(x => 3 * x)
// 如果变量旨在=>右边只出现一次,可以用_来代替
highOrderFunction1(3 * _)
6.5 闭包
闭包就是一个函数把外部的那些不属于自己的对象也包含(闭合)进来。
def minusxy(x: Int) = (y: Int) => x - y
这就是一个闭包:
1) 匿名函数(y: Int) => x -y嵌套在minusxy函数中。
2) 匿名函数(y: Int) => x -y使用了该匿名函数之外的变量x
3) 函数minusxy返回了引用了局部变量的匿名函数
再举一例:
def minusxy(x: Int) = (y: Int) => x - y
val f1 = minusxy(10)
val f2 = minusxy(10)
println(f1(3) + f2(3))
此处f1,f2这两个函数就叫闭包。
6.6 柯里化
函数编程中,接受多个参数的函数都可以转化为接受单个参数的函数,这个转化过程就叫柯里化,柯里化就是证明了函数只需要一个参数而已。其实我们刚才的学习过程中,已经涉及到了柯里化操作,所以这也印证了,柯里化就是以函数为主体这种思想发展的必然产生的结果。
1) 柯里化示例
def mul(x: Int, y: Int) = x * y
println(mul(10, 10))
def mulCurry(x: Int) = (y: Int) => x * y
println(mulCurry(10)(9))
def mulCurry2(x: Int)(y:Int) = x * y
println(mulCurry2(10)(8))
2) 柯里化的应用
比较两个字符串在忽略大小写的情况下是否相等,注意,这里是两个任务:
1、全部转大写(或小写)
2、比较是否相等
针对这两个操作,我们用一个函数去处理的思想,其实无意间也变成了两个函数处理的思想。示例如下:
val a = Array("Hello", "World")
val b = Array("hello", "world")
println(a.corresponds(b)(_.equalsIgnoreCase(_)))
其中corresponds函数的源码如下:
def corresponds[B](that: GenSeq[B])(p: (A,B) => Boolean): Boolean = {
val i = this.iterator
val j = that.iterator
while (i.hasNext && j.hasNext)
if (!p(i.next(), j.next()))
return false
!i.hasNext && !j.hasNext
}
尖叫提示:不要设立柯里化存在的意义这样的命题,柯里化,是面向函数思想的必然产生结果。
6.7 控制抽象
控制抽象是一类函数:
1、参数是函数。
2、函数参数没有输入值也没有返回值。
1) 使用示例
def runInThread(f1: () => Unit): Unit = {
new Thread {
override def run(): Unit = {
f1()
}
}.start()
}
runInThread {
() => println("干活咯!")
Thread.sleep(5000)
println("干完咯!")
}
是不是很爽?是不是有点类似线程池的感觉,同一个线程,可以动态的向里面塞不同的任务去执行。
可以再简化一下,省略(),看下如下形式:
def runInThread(f1: => Unit): Unit = {
new Thread {
override def run(): Unit = {
f1
}
}.start()
}
runInThread {
println("干活咯!")
Thread.sleep(5000)
println("干完咯!")
}
2) 进阶用法:实现类似while的until函数
def until(condition: => Boolean)(block: => Unit) {
if (!condition) {
block
until(condition)(block)
}
}
var x = 10
until(x == 0) {
x -= 1
println(x)
}
七、 类
7.1 简单类和无参方法
类的定义可以通过class关键字实现,如下:
package unit7
class Dog {
private var leg = 4
def shout(content: String) {
println(content)
}
def currentLeg = leg
}
使用这个类:
val dog = new Dog
dog shout "汪汪汪"
println(dog currentLeg)
尖叫提示:在Scala中,类并不声明为Public,一个Scala源文件可以包含多个类。所有这些类都具有公有可见性。调用无参方法时,可以加(),也可以不加;如果方法定义中不带括号,那么调用时就不能带括号。
7.2 Getter Setter方法
对于scala类中的每一个属性,编译后,会有一个私有的字段和相应的getter、setter方法生成:
//getter
println(dog leg)
//setter
dog.leg_=(10)
println(dog currentLeg)
当然了,你也可以不使用自动生成的方式,自己定义getter和setter方法
class Dog2 {
private var _leg = 4
def leg = _leg
def leg_=(newLeg: Int) {
_leg = newLeg
}
}
使用之:
val dog2 = new Dog2
dog2.leg_=(10)
println(dog2.leg)
尖叫提示:自己手动创建变量的getter和setter方法需要遵循以下原则:
1) 字段属性名以“_”作为前缀,如:_leg
2) getter方法定义为:def leg = _leg
3) setter方法定义时,方法名为属性名去掉前缀,并加上后缀,后缀是:“leg_=”,如例子所示
7.3 对象私有字段
变量:workDetails在封闭包professional中的任何类中可访问。
封闭包:friends的任何类都可以被society包中任何类访问。
变量:secrets只能在实例方法的隐式对象(this)中访问。
package unit7
package society {
package professional {
class Executive {
private[professional] var workDetails = null
private[society] var friends = null
private[this] var secrets = null
def help(another: Executive) {
println(another.workDetails)
// println(another.secrets) 报错:访问不到
}
}
}
}
7.4 Bean属性
JavaBeans规范定义了Java的属性是像getXXX()和setXXX()的方法。许多Java工具都依赖这个命名习惯。为了Java的互操作性。将Scala字段加@BeanProperty时,这样的方法会自动生成。
1) 创建一个Bean,使用@BeanProperty注解标识某个属性变量
package unit7
import scala.beans.BeanProperty
class Person {
@BeanProperty var name: String = _
}
2) 通过getName、setName访问属性
val person = new Person
person.setName("Nick")
person.getName
println(person.name)
尖叫提示:
Person将会生成四个方法:
—— name:String
—— name_=(newValue:String): Unit
—— getName():String
—– setName(newValue:String):Unit
7.5 构造器
scala中构造分为主构造器和辅助构造器
1) 主构造的参数直接放置于类名之后
定义类:
class ClassConstructor (var name: String, private var price: Double){
def myPrintln = println(name + "," + price)
}
执行:
val classConstructor = new ClassConstructor("《傲慢与偏见》", 20.5)
classConstructor.myPrintln
2) 主构造器会执行类定义中的所有语句
定义类:
class ClassConstructor2(val name: String = "", val price: Double = 0) {
println(name + "," + price)
}
执行:
val classConstructor2 = new ClassConstructor2("aa", 20)
val classConstructor2_2 = new ClassConstructor2()
3) 通过private设置的主构造器的私有属性
参考1)
4) 如果不带val和var的参数至少被一个方法使用,该参数将自动升级为字段,这时,name和price就变成了类的不可变字段,而且这两个字段是对象私有的,这类似于 private[this] val 字段的效果。
否则,该参数将不被保存为字段,即实例化该对象时传入的参数值,不会被保留在实例化后的对象之中。
主构造器参数 | 生成的字段/方法 |
---|---|
name: String | 对象私有字段。如果没有方法使用name, 则没有该字段 |
private val/var name: String | 私有字段,私有的getter和setter方法 |
var name: String | 私有字段,公有的getter和setter方法 |
@BeanProperty val/var name: String | 私有字段,公有的Scala版和Java版的getter和setter方法 |
如果想让主构造器变成私有的,可以在()之前加上private,这样用户只能通过辅助构造器来构造对象了
class Person private () {...}
5) 辅助构造器名称为this,通过不同参数进行区分,每一个辅助构造器都必须以主构造器或者已经定义的辅助构造器的调用开始
class Person {
private var name = ""
private var age = 0
def this(name: String) {
this()
this.name = name
}
def this(name: String, age: Int) {
this(name)
this.age = age
}
def description = name + " is " + age + " years old"
}
7.6 嵌套类
即,在class中,再定义一个class,以此类推。
Java中的内部类从属于外部类。Scala中内部类从属于实例。
1) 创建一个嵌套类,模拟局域网的聊天场景
import scala.collection.mutable.ArrayBuffer
//嵌套类
class Network {
class Member(val name: String) {
val contacts = new ArrayBuffer[Member]
}
private val members = new ArrayBuffer[Member]
def join(name: String) = {
val m = new Member(name)
members += m
m
}
}
2) 使用该嵌套类
//创建两个局域网
val chatter1 = new Network
val chatter2 = new Network
//Fred 和 Wilma加入局域网1
val fred = chatter1.join("Fred")
val wilma = chatter1.join("Wilma")
//Barney加入局域网2
val barney = chatter2.join("Barney")
//Fred将同属于局域网1中的Wilma添加为联系人
fred.contacts += wilma
//fred.contacts += barney //这样做是不行的,Fred和Barney不属于同一个局域网,即,Fred和Barney不是同一个class Member实例化出来的对象
在Scala中,每个实例都有它自己的Member类,就和他们有自己的members字段一样。也就是说,chatter1.Member和chatter2.Member是不同的两个类。也就是所谓的:路径依赖类型,此处需要详细解释之。
如果想让members接受所有实例的Member,一般有两种办法:
1) 将Member作为Network的伴生对象存在
创建类:
import scala.collection.mutable.ArrayBuffer
// 伴生对象
class Network2 {
private val members = new ArrayBuffer[Network2.Member]
def join(name: String) = {
val m = new Network2.Member(name)
members += m
m
}
def description = "该局域网中的联系人:" +
(for (m <- members) yield m.description).mkString(", ")
}
object Network2 {
class Member(val name: String) {
val contacts = new ArrayBuffer[Member]
def description = name + "的联系人:" +
(for (c <- contacts) yield c.name).mkString(" ")
}
}
使用:
val chatter3 = new Network2
val chatter4 = new Network2
//Fred 和 Wilma加入局域网1
val fred2 = chatter3.join("Fred")
val wilma2 = chatter3.join("Wilma")
//Barney加入局域网2
val barney2 = chatter4.join("Barney")
//Fred将同属于局域网3中的Wilma添加为联系人
fred2.contacts += wilma2
//Fred将不同属于局域网3中,属于局域网4中的的Wilma添加为联系人
fred2.contacts += barney2
println(chatter3.description)
println(chatter4.description)
println(fred2.description)
println(wilma2.description)
println(barney2.description)
2) 使用类型投影,注意留意关键符号:“#”
创建类:
import scala.collection.mutable.ArrayBuffer
//投影
class Network3 {
class Member(val name: String) {
val contacts = new ArrayBuffer[Network3#Member]
}
private val members = new ArrayBuffer[Member]
def join(name: String) = {
val m = new Member(name)
members += m
m
}
}
使用:
val chatter5 = new Network3
val chatter6 = new Network3
//Fred 和 Wilma加入局域网1
val fred3 = chatter5.join("Fred")
val wilma3 = chatter5.join("Wilma")
//Barney加入局域网2
val barney3 = chatter6.join("Barney")
fred3.contacts += wilma3
尖叫提示:与Java一样,在嵌套类中,如果想得到外部类的实例化对象的引用,可以使用“外部类.this”的方式得到。
八、 对象
8.1 单例对象
Scala中没有静态方法和静态字段,可以用object这个语法结构来达到同样的目的。
object Dog {
println("已初始化...")
private var leg = 0
def plus() = {
leg += 1
leg
}
}
对象的构造器在该对象第一次使用时调用。如果对象没有使用过,他的构造器也不会被执行。
对象基本具有类的所有特性,就是一点,你不能设置构造器的参数。
8.2 伴生对象
Java中的类可以既有实例方法又有静态方法,Scala中可以通过伴生对象进行实现。如下:
class Cat {
val hair = Cat.growHair
private var name = ""
def changeName(name: String) = {
this.name = name
}
def describe = println("hair:" + hair + "name:" + name)
}
object Cat {
private var hair = 0
private def growHair = {
hair += 1
hair
}
}
测试:
val cat1 = new Cat
val cat2 = new Cat
cat1.changeName("黑猫")
cat2.changeName("白猫")
cat1.describe
cat2.describe
尖叫提示:类和它的伴生对象可以相互访问私有特性,他们必须存在同一个源文件中。必须同名
8.3 Apply方法
1) apply方法一般都声明在伴生类对象中,可以用来实例化伴生类的对象:
class Man private(val sex: String, name: String) {
def describe = {
println("Sex:" + sex + "name:" + name)
}
}
object Man {
def apply(name: String) = {
new Man("男", name)
}
}
测试:
val man1 = Man("Nick")
val man2 = Man("Thomas")
man1.describe
man2.describe
2) 也可以用来实现单例模式,我们只需要对上述列子稍加改进:
class Man private(val sex: String, name: String) {
def describe = {
println("Sex:" + sex + "name:" + name)
}
}
object Man {
var instance: Man = null
def apply(name: String) = {
if(instance == null) {
instance = new Man("男", name)
}
instance
}
}
测试:
val man1 = Man("Nick")
val man2 = Man("Thomas")
man1.describe
man2.describe
8.4 应用程序对象
每一个Scala应用程序都需要从一个对象的main方法开始执行,这个方法的类型为Array[String]=>Unit:
object Hello {
def main(args: Array[String]) {
println("Hello, World!")
}
}
或者扩展一个App特质:
object Hello extends App {
if (args.length > 0)
println("Hello, " + args(0))
else
println("Hello, World!")
}
8.5 枚举
Scala中没有枚举类型,定义一个扩展Enumeration类的对象,并以value调用初始化枚举中的所有可能值:
object TrafficLightColor extends Enumeration {
val Red = Value(0, "Stop")
val Yellow = Value(1, "Slow")
val Green = Value(2, "Go")
}
测试:
println(TrafficLightColor.Red)
println(TrafficLightColor.Red.id)
println(TrafficLightColor.Yellow)
println(TrafficLightColor.Yellow.id)
println(TrafficLightColor.Green)
println(TrafficLightColor.Green.id)
九、 包和引用
9.1 包/作用域
在Java和Scala中管理项目可以使用包结构,C和C#使用命名空间。
对于package,有如下几种形式:
1) 形式体现:
package com.nick.impatient.people
class Person{
val name = "Nick"
def play(message: String): Unit ={
}
}
等同于:
package com.nick.impatient
package people
class Person{
val name = "Nick"
def play(message: String): Unit ={
}
}
等同于:
package com.nick.impatient{// com和com.nick的成员在这里不可见
package people{
class Person{
val name = "Nick"
def play(message: String): Unit ={
}
}
}
}
尖叫提示:位于文件顶部不带花括号的包声明在整个文件范围内有效。
通过以上形式,发出总结:
1、包也可以像内部类那样嵌套,作用域原则:可以直接向上访问。即,子package中直接访问父package中的内容。(即:作用域)
2、包对象可以持有函数和变量
3、引入语句可以引入包、类和对象
4、源文件的目录和包之间并没有强制的关联关系
5、可以在同一个.scala文件中,声明多个并列的package
6、包名可以相对也可以绝对,比如,访问ArrayBuffer的绝对路径是:root.scala.collection.mutable.ArrayBuffer
9.2 包对象
包可以包含类、对象和特质,但不能包含函数或变量的定义。很不幸,这是Java虚拟机的局限。把工具函数或常量添加到包而不是某个Utils对象,这是更加合理的做法。包对象的出现正是为了解决这个局限。每个包都可以有一个包对象。你需要在父包中定义它,且名称与子包一样。
package com.nick.impatient
package object people {
val defaultName = "Nick"
}
package people {
class Person {
var name = defaultName // 从包对象拿到的常置
}
}
9.3 包可见性
在Java中,没有被声明为public、private或protected的类成员在包含该类的包中可见。在Scala中,你可以通过修饰符达到同样的效果。以下方法在它自己的包中可见:
package com.nick.impatient.people
class Person {
private[people] def description="人的名字:" + name
}
当然,也可以将可见度延展到上层包:
private[impatient] def description="人的名字:" + name
9.4 引入
在Scala中,import语句可以出现在任何地方,并不仅限于文件顶部。import语句的效果一直延伸到包含该语句的块末尾:这是个很有用的特性,尤其是对于通配引入而言。从多个源引入大量名称总是让人担心。事实上,有些Java程序员特别不喜欢通配引入,以至于从不使用这个特性,而是让IDE帮他们生成一长串引入语句。通过将引入放置在需要这些引入的地方,你可以大幅减少可能的名称冲突。
9.5 重命名和隐藏方法
1) 重命名:
如果你想要引人包中的几个成员,可以像这样使用选取器( selector),而且选取的同时,可以重命名:
import java.util.{ HashMap=>JavaHashMap, List}
import scala.collection.mutable._
这样一来,JavaHashMap就是java.utiI.HashMap,而HashMap则对应
scala.collection.mutable.HashMap。
2) 隐藏:
选取器HashMap =>_将隐藏某个成员而不是重命名它。这仅在你需要引入其他成员时有用:
import java.util.{HashMap=>_, _ }
import scala.collection.mutable._
现在,HashMap很明确的便指向了scala.collection.mutable.HashMap,因为java.util.HashMap被隐藏起来了。
尖叫提示:
每个Scala程序都隐式地以如下代码开始:
import java.lang._
import scala._
import Predef._
和Java程序一样,java.lang总是被引入。接下来,scala包也被引入,不过方式有些特殊。不像所有其他引入,这个引入被允许可以覆盖之前的引入。举例来说,scala.StringBuilder覆盖java.lang.StringBuilder而不是与之冲突。最后,Predef对象被引入。它包含了相当多有用的函数,这些同样可以被放置在scala包对象中,不过Predef在Scala还没有加入包对象之前就存在了。
由于scala包默认被引入,对于那些以scala开头的包,你完全不需要写全这个前缀。例如:
collection.mutable.HashMap
上述代码和以下写法一样好:
scala.collection.mutable. HashMap
十、 继承
10.1 继承类
和Java一样使用extends关键字,在定义中给出子类需要而超类没有的字段和方法,或者重写超类的方法。
class Person {
var name = ""
}
class Employee extends Person{
var salary = 0.0
def description = "员工姓名:" + name + " 薪水:" + salary
}
尖叫提示:如果类声明为final,他讲不能被继承。如果单个方法声明为final,将不能被重写。
10.2 重写方法
重写一个非抽象方法需要用override修饰符,调用超类的方法使用super关键字
class Person {
var name = ""
override def toString = getClass.getName + "[name=" + name + "]"
}
class Employee extends Person {
var salary = 0.0
override def toString = super.toString + "[salary=" + salary + "]"
}
10.3 类型检查和转换
要测试某个对象是否属于某个给定的类,可以用isInstanceOf方法。用asInstanceOf方法将引用转换为子类的引用。classOf获取对象的类名。
1) classOf[String]就如同Java的 String.class
2) obj.isInstanceOf[T]就如同Java的obj instanceof T
3) obj.asInstanceOf[T]就如同Java的(T)obj
println("Hello".isInstanceOf[String])
println("Hello".asInstanceOf[String])
println(classOf[String])
10.4 受保护的字段和方法
protected在scala中比Java要更严格一点,即, 只有继承关系才可以访问,同一个包下,也是不可以的。
10.5 超类的构造
类有一个主构器和任意数量的辅助构造器,而每个辅助构造器都必须以对先前定义的辅助构造器或主构造器的调用开始。子类的辅助构造器最终都会调用主构造器,只有主构造器可以调用超类的构造器。辅助构造器永远都不可能直接调用超类的构造器。在Scala的构造器中,你不能调用super(params)。
class Person(val name: String, val age: Int) {
override def toString = getClass.getName + "[name=" + name +
",age=" + age + "]"
}
class Employee(name: String, age: Int, val salary : Double) extends Person(name, age) {
override def toString = super.toString + "[salary=" + salary + "]"
}
10.6 重名字段
子类改写父类或者抽象父类的字段,通过以下方式:
class Person1(val name:String,var age:Int){
println("主构造器已经被调用")
val school="五道口职业技术学院"
def sleep="8 hours"
override def toString="我的学校是:" + school + "我的名字和年龄是:" + name + "," + age
}
class Person2(name:String, age:Int) extends Person1(name, age){
override val school: String = "清华大学"
}
尖叫提示:
1、def只能重写另一个def
2、val只能重写另一个val或不带参数的def
3、var只能重写另一个抽象的var
什么是抽象var?
abstract class Person3 {
var name:String
}
10.7 匿名子类
和Java一样,你可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类:
class Person4(val name: String) {
override def toString = getClass.getName + "[name=" + name + "]"
}
使用:
val alien = new Person4("Fred") {
def greeting = "Greetings, Earthling! My name is Fred."
}
println(alien.greeting)
10.8 抽象类
可以通过abstract关键字标记不能被实例化的类。方法不用标记abstract,只要省掉方法体即可。类可以拥有抽象字段,抽象字段就是没有初始值的字段。
abstract class Person(val pname: String) {
val id: Int
var name: String
def idString: Int
}
class Employee(pname: String) extends Person(pname) {
val id = 5;
var name = ">>>"
def idString = pname.hashCode
}
尖叫提示:子类实现抽象方法不需要override
10.9 构造顺序和提前定义
当子类重写了父类的方法或者字段后,父类又依赖这些字段或者方法初始化,这个时候就会出现问题,比如:
class Creature {
val range: Int = 10
val env: Array[Int] = new Array[Int](range)
}
class Ant extends Creature {
override val range = 2
}
此时的构造顺序为:
1) Ant的构造器在做它自己的构造之前,调用Creature的构造器
2) Creature的构造器将它的range字段设为10
3) Creature的构造器为了初始化env数组,调用range()取值器
4) 该方法被重写以输出(还未初始化的)Ant类的range字段值
5) range方法返回0,(这是对象被分配空间时所有整形字段的初始值)
6) env被设为长度为0的数组。
7) Ant构造器继续执行,将其range字段设为2.
那么env的大小是多少?是0,惊不惊喜,意不意外?
问题解决,3种方案:
1) 可以将val声明为final,这样子类不可改写。
2) 可以将超类中将val声明为lazy,这样安全但并不高效。
3) 还可以使用提前定义语法,可以在超类的构造器执行之前初始化子类的val字段:
class Ant2 extends {
override val range = 3
} with Creature
10.10 Scala继承层级
在scala中,所有其他类都是AnyRef的子类,类似Java的Object。
AnyVal和AnyRef都扩展自Any类。Any类是跟节点
Any中定义了isInstanceOf、asInstanceOf方法,以及哈希方法等。
Null类型的唯一实例就是null对象。可以将null赋值给任何引用,但不能赋值给值类型的变量。
Nothing类型没有实例。它对于泛型结构是有用处的,举例:空列表Nil的类型是List[Nothing],它是List[T]的子类型,T可以是任何类。
十一、 特质
11.1 不允许多重集成
所有的面向对象的语言都不允许直接的多重继承,因为会出现“deadly diamond of death”问题。Scala提供了特质(trait),特质可以同时拥有抽象方法和具体方法,一个类可以实现多个特质。
11.2 当做接口使用的特质
特质中没有实现的方法就是抽象方法。类通过extends继承特质,通过with可以继承多个特质。
trait Logger {
def log(msg: String)
}
class ConsoleLogger extends Logger with Cloneable with Serializable {
def log(msg: String) {
println(msg)
}
}
Logger with Cloneable with Serializable是一个整体,extends这个整体
所有的java接口都可以当做Scala特质使用。
11.3 带有具体实现的特质
特质中的方法并不一定是抽象的:
trait ConsoleLogger {
def log(msg: String) {
println(msg)
}
}
class Account {
protected var balance = 0.0
}
class SavingsAccount extends Account with ConsoleLogger {
def withdraw(amount: Double) {
if (amount > balance) log("余额不足")
else balance -= amount
}
}
11.4 带有特质的对象
在构建对象时混入某个具体的特质,覆盖掉抽象方法,提供具体实现:
trait Logger {
def log(msg: String)
}
trait ConsoleLogger extends Logger {
def log(msg: String) {
println(msg)
}
}
class Account {
protected var balance = 0.0
}
abstract class SavingsAccount extends Account with Logger {
def withdraw(amount: Double) {
if (amount > balance) log("余额不足")
else balance -= amount
}
}
object Main extends App {
val account = new SavingsAccount with ConsoleLogger
account.withdraw(100)
}
11.5 叠加在一起的特质
super并不是指继承关系,而是指的加载顺序。
继承多个相同父特质的类,会从右到左依次调用特质的方法。Super指的是继承特质左边的特质,从源码是无法判断super.method会执行哪里的方法,如果想要调用具体特质的方法,可以指定:super[ConsoleLogger].log(…).其中的泛型必须是该特质的直接超类类型
trait Logger {
def log(msg: String);
}
trait ConsoleLogger extends Logger {
def log(msg: String) {
println(msg)
}
}
trait TimestampLogger extends ConsoleLogger {
override def log(msg: String) {
super.log(new java.util.Date() + " " + msg)
}
}
trait ShortLogger extends ConsoleLogger {
override def log(msg: String) {
super.log(if (msg.length <= 15) msg else s"${msg.substring(0, 12)}...")
}
}
class Account {
protected var balance = 0.0
}
abstract class SavingsAccount extends Account with Logger {
def withdraw(amount: Double) {
if (amount > balance) log("余额不足")
else balance -= amount
}
}
object Main extends App {
val acct1 = new SavingsAccount with TimestampLogger with ShortLogger
val acct2 = new SavingsAccount with ShortLogger with TimestampLogger
acct1.withdraw(100)
acct2.withdraw(100)
}
11.6 在特质中重写抽象方法
trait Logger2 {
def log(msg: String)
}
//因为有super,Scala认为log还是一个抽象方法
trait TimestampLogger2 extends Logger2 {
abstract override def log(msg: String) {
super.log(new java.util.Date() + " " + msg)
}
}
trait ShortLogger2 extends Logger2 {
abstract override def log(msg: String) {
super.log(if (msg.length <= 15) msg else s"${msg.substring(0, 12)}...")
}
}
trait ConsoleLogger2 extends Logger2 {
override def log(msg: String) {
println(msg)
}
}
class Account2 {
protected var balance = 0.0
}
abstract class SavingsAccount2 extends Account2 with Logger2 {
def withdraw(amount: Double) {
if (amount > balance) log("余额不足")
else balance -= amount
}
}
object Main2 extends App {
val acct1 = new SavingsAccount2 with ConsoleLogger2 with TimestampLogger2 with ShortLogger2
acct1.withdraw(100)
}
11.7 当做富接口使用的特质
即该特质中既有抽象方法,又有非抽象方法
//富特质
trait Logger3 {
def log(msg: String)
def info(msg: String) {
log("INFO: " + msg)
}
def warn(msg: String) {
log("WARN: " + msg)
}
def severe(msg: String) {
log("SEVERE: " + msg)
}
}
trait ConsoleLogger3 extends Logger3 {
def log(msg: String) {
println(msg)
}
}
class Account3 {
protected var balance = 0.0
}
abstract class SavingsAccount3 extends Account3 with Logger3 {
def withdraw(amount: Double) {
if (amount > balance) severe("余额不足")
else balance -= amount
}
}
object Main3 extends App {
val acct = new SavingsAccount with ConsoleLogger
acct.withdraw(100)
}
11.8 特质中的具体字段
特质中可以定义具体字段,如果初始化了就是具体字段,如果不初始化就是抽象字段。
混入该特质的类就具有了该字段,字段不是继承,而是简单的加入类。是自己的字段。
trait Logger4 {
def log(msg: String)
}
trait ConsoleLogger4 extends Logger4 {
def log(msg: String) {
println(msg)
}
}
trait ShortLogger4 extends Logger4 {
val maxLength = 15
abstract override def log(msg: String) {
super.log(if (msg.length <= maxLength) msg else s"${msg.substring(0, maxLength - 3)}...")
}
}
class Account4 {
protected var balance = 0.0
}
class SavingsAccount4 extends Account4 with ConsoleLogger4 with ShortLogger4 {
var interest = 0.0
def withdraw(amount: Double) {
if (amount > balance) log("余额不足")
else balance -= amount
}
}
object Main4 extends App {
val acct = new SavingsAccount4
acct.withdraw(100)
println(acct.maxLength)
}
11.9 特质中的抽象字段
特质中未被初始化的字段在具体的子类中必须被重写。
//特质中的具体字段
trait Logger5 {
def log(msg: String)
}
trait ConsoleLogger5 extends Logger5 {
def log(msg: String) {
println(msg)
}
}
trait ShortLogger5 extends Logger5 {
val maxLength: Int
abstract override def log(msg: String) {
super.log(if (msg.length <= maxLength) msg else s"${msg.substring(0, maxLength - 3)}...")
}
}
class Account5 {
protected var balance = 0.0
}
abstract class SavingsAccount5 extends Account5 with Logger5 {
var interest = 0.0
def withdraw(amount: Double) {
if (amount > balance) log("余额不足")
else balance -= amount
}
}
object Main5 extends App {
val acct = new SavingsAccount5 with ConsoleLogger5 with ShortLogger5 {
val maxLength = 20
}
acct.withdraw(100)
println(acct.maxLength)
}
11.10 特质构造顺序
特质也是有构造器的,构造器中的内容由“字段的初始化”和一些其他语句构成
trait Logger6 {
println("我在Logger6特质构造器中,嘿嘿嘿。。。")
def log(msg: String)
}
trait ConsoleLogger6 extends Logger6 {
println("我在ConsoleLogger6特质构造器中,嘿嘿嘿。。。")
def log(msg: String) {
println(msg)
}
}
trait ShortLogger6 extends Logger6 {
val maxLength: Int
println("我在ShortLogger6特质构造器中,嘿嘿嘿。。。")
abstract override def log(msg: String) {
super.log(if (msg.length <= maxLength) msg else s"${msg.substring(0, maxLength - 3)}...")
}
}
class Account6 {
println("我在Account6构造器中,嘿嘿嘿。。。")
protected var balance = 0.0
}
abstract class SavingsAccount6 extends Account6 with ConsoleLogger6 with ShortLogger6{
println("我再SavingsAccount6构造器中")
var interest = 0.0
override val maxLength: Int = 20
def withdraw(amount: Double) {
if (amount > balance) log("余额不足")
else balance -= amount
}
}
object Main6 extends App {
val acct = new SavingsAccount6 with ConsoleLogger6 with ShortLogger6
acct.withdraw(100)
println(acct.maxLength)
}
步骤总结:
1、调用当前类的超类构造器
2、第一个特质的父特质构造器
3、第一个特质构造器
4、第二个特质构造器的父特质构造器由于已经执行完成,所以不再执行
5、第二个特质构造器
6、当前类构造器
11.11 初始化特质中的字段
特质不能有构造器参数,每个特质都有一个无参数的构造器。缺少构造器参数是特质与类之间唯一的技术差别。除此之外,特质可以具备类的所有特性,比如具体的和抽象的字段,以及超类。现在有如下情景:我们想通过特质来实现日志数据的输出,输出到某一个文件中
import java.io.PrintStream
trait Logger7{
def log(msg:String)
}
trait FileLogger7 extends Logger7{
val fileName:String
val out = new PrintStream(fileName)
override def log(msg: String): Unit = {
out.print(msg)
out.flush()
}
}
class SavingsAccount7{
}
object Main7 extends App {
val acct = new SavingsAccount7 with FileLogger7 {
override val fileName = "2017-11-24.log"//空指针异常
}
}
如果想修复如上错误,可以:
1) 使用“提前定义”
import java.io.PrintStream
trait Logger7 {
def log(msg: String)
}
trait FileLogger7 extends Logger7 {
val fileName: String
val out = new PrintStream(fileName)
override def log(msg: String): Unit = {
out.print(msg)
out.flush()
}
}
class SavingsAccount7 {
}
object Main7 extends App {
//提前定义
val acct = new {
override val fileName = "2017-11-24.log"
} with SavingsAccount7 with FileLogger7
acct.log("heiheihei")
}
或这样提前定义:
package unit12
import java.io.PrintStream
trait Logger7 {
def log(msg: String)
}
trait FileLogger7 extends Logger7 {
val fileName: String
val out = new PrintStream(fileName)
override def log(msg: String): Unit = {
out.print(msg)
out.flush()
}
}
//提前定义在这里
class SavingsAccount7 extends {
override val fileName = "2017-11-24.log"
} with FileLogger7
object Main7 extends App {
val acct = new SavingsAccount7 with FileLogger7
acct.log("嘿嘿嘿")
}
2) 使用lazy
package unit12
import java.io.PrintStream
trait Logger7 {
def log(msg: String)
}
trait FileLogger7 extends Logger7 {
val fileName: String
lazy val out = new PrintStream(fileName)
override def log(msg: String): Unit = {
out.print(msg)
out.flush()
}
}
class SavingsAccount7 {
}
object Main7 extends App {
val acct = new SavingsAccount7 with FileLogger7 {
override val fileName = "2017-11-24.log"
}
acct.log("哈哈哈")
}
11.12 扩展类的特质
总结:
1、特质可以继承自类,以用来拓展该类的一些功能
2、所有混入该特质的类,会自动成为那个特质所继承的超类的子类
3、如果混入该特质的类,已经继承了另一个类,不就矛盾了?注意,只要继承的那个类是特质超类的子类即可。
例如:
1) 特质可以继承自类,以用来拓展该类的一些功能
trait LoggedException extends Exception{
def log(): Unit ={
println(getMessage())
}
}
2) 所有混入该特质的类,会自动成为那个特质所继承的超类的子类
class UnhappyException extends LoggedException{
override def getMessage = "哦,我的上帝,我要踢爆他的屁股!"
}
3) 如果混入该特质的类,已经继承了另一个类,不就矛盾了?注意,只要继承的那个类是特质超类的子类即可。
正确:
class UnhappyException2 extends IndexOutOfBoundsException with LoggedException{
override def getMessage = "哦,我的上帝,我要踢爆他的屁股!"
}
错误:
class UnhappyException3 extends JFrame with LoggedException{
override def getMessage = "哦,我的上帝,我要踢爆他的屁股!"
}
11.13 自身类型
主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型。
比如:
//自身类型特质
trait Logger9{
this: Exception =>
def log(): Unit ={
println(getMessage)
}
}
这样一来,在该特质中,可以随意调用“自身类型”中的各种方法。
十二、 注解
注解就是标签。
标签是用来标记某些代码需要特殊处理的。
处理的手段可以在代码运行时操作,也可以在编译期操作。
12.1 什么可以被注解
1) 可以为类,方法,字段局部变量,参数,表达式,类型参数以及各种类型定义添加注解
@Entity class Student
@Test def play() {}
@BeanProperty var username = _
def doSomething(@NotNull message: String) {}
@BeanProperty @Id var username = _
2) 构造器注解,需要在主构造器之前,类名之后,且需要加括号,如果注解有参数,则写在注解括号里
class Student @Inject() (var username: String, var password: String)
3) 为表达式添加注解,在表达式后添加冒号
(map1.get(key): @unchecked) match {...}
4) 泛型添加注解
class Student[@specialized T]
5) 实际类型添加注解
String @cps[Unit]
12.2 注解参数
Java注解可以有带名参数:
@Test(timeout = 100, expected = classOf[IOException])
// 如果参数名为value,则该名称可以直接略去。
@Named("creds") var credentials: Credentials = _ // value参数的值为 “creds”
// 注解不带参数,圆括号可以省去
@Entity class Credentials
Java 注解的参数类型只能是:
数值型的字面量
字符串
类字面量
Java枚举
其他注解
上述类型的数组(但不能是数组的数组)
Scala注解可以是任何类型,但只有少数几个Scala注解利用了这个增加的灵活性。
12.3 注解实现
你可以实现自己的注解,但是更多的是使用Scala和Java提供的注解。
注解必须扩展Annotation特质:
class unchecked extends annotation.Annotation
12.4 针对Java的注解
1) Java修饰符:对于那些不是很常用的Java特性,Scala使用注解,而不是修饰符关键字。
@volatile var done = false // JVM中将成为volatile的字段
@transient var recentLookups = new HashMap[String, String] // 在JVM中将成为transient字段,该字段不会被序列化。
@strictfp def calculate(x: Double) = ...
@native def win32RegKeys(root: Int, path: String): Array[String]
2) 标记接口:Scala用注解@cloneable和@remote 而不是 Cloneable和Java.rmi.Remote“标记接口”来标记可被克隆的对象和远程的对象。
@cloneable class Employee
3) 受检异常:和Scala不同,Java编译器会跟踪受检异常。如果你从Java代码中调用Scala的方法,其签名应包含那些可能被抛出的受检异常。用@throws注解来生成正确的签名。
class Book {
@throws (classOf[IOException]) def read(filename: String) { ... }
...
}
Java版本的方法签名:
void read(String fileName) throws IOException
// 如果没有@throws注解,Java代码将不能捕获该异常
try {//Java代码
book.read("war-and-peace.txt");
} catch (IOException ex) {
...
}
即:Java编译期需要在编译时就知道read方法可以抛IOException异常,否则Java会拒绝捕获该异常。
12.5 由于优化的注解
尾递归的优化
啥玩是尾递归?
尾递归:
def story(): Unit = {从前有座山,山上有座庙,庙里有个老和尚,一天老和尚对小和尚讲故事:story()}
尖叫提示:进入下一个函数不再需要上一个函数的环境了,得出结果以后直接返回。
非尾递归:
def story(): Unit = {从前有座山,山上有座庙,庙里有个老和尚,一天老和尚对小和尚讲故事:story(),小和尚听了,找了块豆腐撞死了}
尖叫提示:下一个函数结束以后此函数还有后续,所以必须保存本身的环境以供处理返回值。
递归调用有时候能被转化成循环,这样能节约栈空间:
object Util {
def sum(xs: Seq[Int]): BigInt = {
if (xs.isEmpty) 0 else xs.head + sum(xs.tail)
}
...
}
上面的sum方法无法被优化,因为计算过程中最后一步是加法,而不是递归调用。调整后的代码:
def sum2(xs: Seq[Int], partial: BigInt): BigInt = {
if (xs.isEmpty) partial else sum2(xs.tail, xs.head + partial)
}
Scala编译器会自动对sum2应用“尾递归”优化。如果你调用sum(1 to 1000000) 将会发生一个栈溢出错误。不过sum2(1 to 1000000, 0) 将会得到正确的结果。
尽管Scala编译器会尝试使用尾递归优化,但有时候某些不太明显的原因会造成它无法这样做。如果你想编译器无法进行优化时报错,则应该给你的方法加上@tailrec注解。
*尖叫提示:对于消除递归,一个更加通用的机制叫做“蹦床”。蹦床的实现会将执行一个循环,不停的调用函数。每个函数都返回下一个将被调用的函数。尾递归在这里是一个特例,每个函数都返回它自己。*Scala有一个名为TailCalls的工具对象,帮助我们轻松实现蹦床:
import scala.util.control.TailCalls._
def evenLength(xs: Seq[Int]): TailRec[Boolean] = {
if(xs.isEmpty) done(true) else tailcall(oddLength(xs.tail))
}
def oddLength(xs: Seq[Int]): TailRec[Boolean] = {
if(xs.isEmpty) done(false) else tailcall(evenLength(xs.tail))
}
// 获得TailRec对象获取最终结果,可以用result方法
evenLength(1 to 1000000).result
十三、 类型参数(先了解即可)
13.1 泛类型
类和特质都可以带类型参数,用方括号来定义类型参数,可以用类型参数来定义变量、方法参数和返回值。带有一个或多个类型参数的类是泛型的。如下p1,如果实例化时没有指定泛型类型,则scala会自动根据构造参数的类型自动推断泛型的具体类型。
class Pair[T, S](val first: T, val second: S) {
override def toString = "(" + first + "," + second + ")"
}
//从构造参数推断类型
val p1 = new Pair(42, "String")
//设置类型
val p2 = new Pair[Any, Any](42, "String")
13.2 泛型函数
函数或方法也可以有类型(泛型)参数。
// 从参数类型来推断类型
println(getMiddle(Array("Bob", "had", "a", "little", "brother")).getClass.getTypeName)
//指定类型,并保存为具体的函数。
val f = getMiddle[String] _
println(f(Array("Bob", "had", "a", "little", "brother")))
13.3 类型变量限定
在Java泛型里不表示某个泛型是另外一个泛型的子类型可以使用extends关键字,而在scala中使用符号“<:”,这种形式称之为泛型的上界。
class Pair1[T <: Comparable[T]](val first: T, val second: T) {
def smaller = if (firstpareTo(second) < 0) first else second
}
object Main1 extends App{
override def main(args: Array[String]): Unit = {
val p = new Pair1("Fred", "Brooks")
println(p.smaller)
}
}
在Java泛型里表示某个泛型是另外一个泛型的父类型,使用super关键字,而在scala中,使用符号“>:”,这种形式称之为泛型的下界。
class Pair2[T](val first: T, val second: T) {
def replaceFirst[R >: T](newFirst: R) = new Pair2[R](newFirst, second)
override def toString = "(" + first + "," + second + ")"
}
object Main2 extends App{
override def main(args: Array[String]): Unit = {
val p = new Pair2("Nick", "Alice")
println(p)
println(p.replaceFirst("Joke"))
println(p)
}
}
在Java中,T同时是A和B的子类型,称之为多界,形式如:
13.5 视图界定
在Scala中,如果你想标记某一个泛型可以隐式的转换为另一个泛型,可以使用:[T <% Comparable[T]],由于Scala的Int类型没有实现Comparable接口,所以我们需要将Int类型隐式的转换为RichInt类型,比如:
class Pair3[T <% Comparable[T]](val first: T, val second: T) {
def smaller = if (firstpareTo(second) < 0) first else second
override def toString = "(" + first + "," + second + ")"
}
object Main3 extends App {
val p = new Pair3(4, 2)
println(p.smaller)
}
13.6 上下文界定
视图界定 T <% V要求必须存在一个从T到V的隐式转换。上下文界定的形式为T:M,其中M是另一个泛型类,它要求必须存在一个类型为M[T]的隐式值。
下面类定义要求必须存在一个类型为Ordering[T]的隐式值,当你使用了一个使用了隐式值得方法时,传入该隐式参数。
class Pair4[T: Ordering](val first: T, val second: T) {
def smaller(implicit ord: Ordering[T]) = {
println(ord)
if (ordpare(first, second) < 0) first else second
}
override def toString = "(" + first + "," + second + ")"
}
object Main4 extends App{
override def main(args: Array[String]): Unit = {
val p4 = new Pair4(1, 2)
println(p4.smaller)
}
}
13.7 Manifest上下文界定
Manifest是scala2.8引入的一个特质,用于编译器在运行时也能获取泛型类型的信息。在JVM上,泛型参数类型T在运行时是被“擦拭”掉的,编译器把T当作Object来对待,所以T的具体信息是无法得到的;为了使得在运行时得到T的信息,scala需要额外通过Manifest来存储T的信息,并作为参数用在方法的运行时上下文。
def test[T] (x:T, m:Manifest[T]) { ... }
有了Manifest[T]这个记录T类型信息的参数m,在运行时就可以根据m来更准确的判断T了。但如果每个方法都这么写,让方法的调用者要额外传入m参数,非常不友好,且对方法的设计是一道伤疤。好在scala中有隐式转换、隐式参数的功能,在这个地方可以用隐式参数来减轻调用者的麻烦。
def foo[T](x: List[T])(implicit m: Manifest[T]) = {
println(m)
if (m <:< manifest[String])
println("Hey, this list is full of strings")
else
println("Non-stringy list")
}
foo(List("one", "two"))
foo(List(1, 2))
foo(List("one", 2))
隐式参数m是由编译器根据上下文自动传入的,比如上面是编译器根据 “one”,”two” 推断出 T 的类型是 String,从而隐式的传入了一个Manifest[String]类型的对象参数,使得运行时可以根据这个参数做更多的事情。
不过上面的foo 方法定义使用隐式参数的方式,仍显得啰嗦,于是scala里又引入了“上下文绑定”,
def foo[T](x: List[T]) (implicit m: Manifest[T])
可以简化为:
def foo[T:Manifest] (x: List[T])
在引入Manifest的时候,还引入了一个更弱一点的ClassManifest,所谓的弱是指类型信息不如Manifest那么完整,主要针对高阶类型的情况
scala在2.10里却用TypeTag替代了Manifest,用ClassTag替代了ClassManifest,原因是在路径依赖类型中,Manifest存在问题:
scala> class Foo{class Bar}
defined class Foo
scala> def m(f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar]) = ev
warning: there were 2 deprecation warnings; re-run with -deprecation for details
m: (f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar])Manifest[f.Bar]
scala> val f1 = new Foo;val b1 = new f1.Bar
f1: Foo = Foo@681e731c
b1: f1.Bar = Foo$Bar@271768ab
scala> val f2 = new Foo;val b2 = new f2.Bar
f2: Foo = Foo@3e50039c
b2: f2.Bar = Foo$Bar@771d16b9
scala> val ev1 = m(f1)(b1)
warning: there were 2 deprecation warnings; re-run with -deprecation for details
ev1: Manifest[f1.Bar] = Foo@681e731c.type#Foo$Bar
scala> val ev2 = m(f2)(b2)
warning: there were 2 deprecation warnings; re-run with -deprecation for details
ev2: Manifest[f2.Bar] = Foo@3e50039c.type#Foo$Bar
scala> ev1 == ev2 // they should be different, thus the result is wrong
res28: Boolean = true
了解之后,我们总结一下,TypeTag到底有啥用呢?看下面的例子:
请留意:
=:=,意思为:type equality
<:< ,意思为:subtype relation
类型判断不要用 == 或 !=
class Animal{}
class Dog extends Animal{}
object MainFoo extends App{
override def main(args: Array[String]): Unit = {
val list1 = List(1, 2, 3)
val list2 = List("1", "2", "3")
val list3 = List("1", "2", 3)
def test1(x: List[Any]) = {
x match {
case list: List[Int] => "Int list"
case list: List[String] => "String list"
case list: List[Any] => "Any list"
}
}
println(test1(list1))
println(test1(list2))
println(test1(list3))
import scala.reflect.runtime.universe._
def test2[A : TypeTag](x: List[A]) = typeOf[A] match {
case t if t =:= typeOf[String] => "String List"
case t if t <:< typeOf[Animal] => "Dog List"
case t if t =:= typeOf[Int] => "Int List"
}
println(test2(List("string")))
println(test2(List(new Dog)))
println(test2(List(1, 2)))
}
}
13.8 多重界定
不能同时有多个上界或下界,变通的方式是使用复合类型
T <: A with B
T >: A with B
可以同时有上界和下界,如
T >: A <: B
这种情况下界必须写在前边,上界写在后边,位置不能反。同时A要符合B的子类型,A与B不能是两个无关的类型。
可以同时有多个view bounds
T <% A <% B
这种情况要求必须同时存在 T=>A的隐式转换,和T=>B的隐式转换。
class A{}
class B{}
implicit def string2A(s:String) = new A
implicit def string2B(s:String) = new B
def foo2[ T <% A <% B](x:T) = println("foo2 OK")
foo2("test")
可以同时有多个上下文界定
T : A : B
这种情况要求必须同时存在C[T]类型的隐式值,和D[T]类型的隐式值。
class C[T];
class D[T];
implicit val c = new C[Int]
implicit val d = new D[Int]
def foo3[ T : C : D ](i:T) = println("foo3 OK")
foo3(2)
13.9 类型约束
类型约束,提供了限定类型的另一种方式,一共有3中关系声明:
T =:= U意思为:T类型是否等于U类型
T <:< U意思为:T类型是否为U或U的子类型
T <%
class Pair5[T] (val first: T, val second: T)(implicit ev: T <:< Comparable[T]){}
使用举例:
import java.io.File
class Pair6[T](val first: T, val second: T) {
def smaller(implicit ev: T <:< Ordered[T]) = {
if(first < second) first else second
}
}
object Main6 extends App{
override def main(args: Array[String]): Unit = {
//构造Pair6[File]时,注意此时是不会报错的
val p6 = new Pair6[File](new File(""), new File(""))
//这就报错了
p6.smaller
}
}
13.10 型变
术语:
英文 | 中文 | 示例 |
---|---|---|
Variance | 型变 | Function[-T, +R] |
Nonvariant | 不变 | Array[A] |
Covariant | 协变 | Supplier[+A] |
Contravariant | 逆变 | Consumer[-A] |
Immutable | 不可变 | String |
Mutable | 可变 | StringBuilder |
其中,Mutable常常意味着Nonvariant,但是Noncovariant与Mutable分别表示两个不同的范畴。
即:可变的,一般意味着“不可型变”,但是“不可协变”和可变的,分别表示两个不同范畴。
型变(Variance)拥有三种基本形态:协变(Covariant), 逆变(Contravariant), 不变(Nonconviant),可以形式化地描述为:
一般地,假设类型C[T]持有类型参数T;给定两个类型A和B,如果满足A <: B,则C[A]与 C[B]之间存在三种关系:
如果C[A] <: C[B],那么C是协变的(Covariant);
如果C[A] :> C[B],那么C是逆变的(Contravariant);
否则,C是不变的(Nonvariant)。
Scala的类型参数使用+标识“协变”,-标识“逆变”,而不带任何标识的表示“不变”(Nonvariable):
如何判断一个类型是否有型变能力:
一般地,“不可变的”(Immutable)类型意味着“型变”(Variant),而“可变的”(Mutable)意味着“不变”(Nonvariant)。
其中,对于不可变的(Immutable)类型C[T]
如果它是一个生产者,其类型参数应该是协变的,即C[+T];
如果它是一个消费者,其类型参数应该是逆变的,即C[-T]。
十四、 隐式转换和隐式参数
14.1 隐式转换
隐式转换函数是以implicit关键字声明的带有单个参数的函数。这种函数将会自动应用,将值从一种类型转换为另一种类型。
implicit def a(d: Double) = d.toInt
//不加上边这句你试试
val i1: Int = 3.5
println(i1)
14.2 利用隐式转换丰富类库功能
如果需要为一个类增加一个方法,可以通过隐式转换来实现。比如想为File增加一个read方法,可以如下定义:
class RichFile(val from: File) {
def read = Source.fromFile(from.getPath).mkString
}
implicit def file2RichFile(from: File) = new RichFile(from)
val contents = new File("C:\\Users\\61661\\Desktop\\scala笔记.txt").read
println(contents)
有什么好处呢?好处就是你可以不修改原版本的代码而为原本的代码增加新功能。
14.3 隐式值
将name变量标记为implicit,所以编译器会在方法省略隐式参数的情况下去搜索作用域内的隐式值作为缺少参数。
implicit val name = "Nick"
def person(implicit name: String) = name
println(person)
但是如果此时你又相同作用域中定义一个隐式变量,再次调用方法时就会报错:
implicit val name = "Nick"
implicit val name2 = "Nick"
def person(implicit name: String) = name
println(person)
14.4 隐式视图
1) 隐式转换为目标类型:把一种类型自动转换到另一种类型
def foo(msg : String) = println(msg)
implicit def intToString(x : Int) = x.toString
foo(10)
2) 隐式转换调用类中本不存在的方法
class Dog {
val name = "金毛"
}
class Skill{
def fly(animal: Dog, skill: String) = println(animal.name + "已领悟" + skill)
}
object Learn{
implicit def learningType(s : Dog) = new Skill
}
object Main2 extends App{
override def main(args: Array[String]): Unit = {
import unit15.Learn._
val dog = new Dog
dog.fly(dog, "飞行技能")
}
}
当然了,以上操作也可以定义在包对象中,即,在object Learn的外面再套一层,package,没问题的!
14.5 隐式类
在scala2.10后提供了隐式类,可以使用implicit声明类,但是需要注意以下几点:
—– 其所带的构造参数有且只能有一个
—– 隐式类必须被定义在“类”或“伴生对象”或“包对象”里
—– 隐式类不能是case class(case class在定义会自动生成伴生对象与2矛盾)
—– 作用域内不能有与之相同名称的标示符
object StringUtils {
implicit class StringImprovement(val s : String){ //隐式类
def increment = s.map(x => (x +1).toChar)
}
}
object Main3 extends App{
import unit15.StringUtils._
println("mobin".increment)
}
14.6 隐式的转换时机
1) 当方法中的参数的类型与目标类型不一致时
2) 当对象调用类中不存在的方法或成员时,编译器会自动将对象进行隐式转换
14.7 隐式解析机制
即编译器是如何查找到缺失信息的,解析具有以下两种规则:
1) 首先会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象)。
2) 如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。类型的作用域是指与该类型相关联的全部伴生模块,一个隐式实体的类型T它的查找范围如下:
a) 如果T被定义为T with A with B with C,那么A,B,C都是T的部分,在T的隐式解析过程中,它们的伴生对象都会被搜索。
b) 如果T是参数化类型,那么类型参数和与类型参数相关联的部分都算作T的部分,比如List[String]的隐式搜索会搜索List的伴生对象和String的伴生对象。
c) 如果T是一个单例类型p.T,即T是属于某个p对象内,那么这个p对象也会被搜索。
d) 如果T是个类型注入S#T,那么S和T都会被搜索。
14.8 隐式转换的前提
1) 不能存在二义性
2) 隐式操作不能嵌套
十五、 文件和正则表达式
15.1 读取行
import scala.io.Source
object FileSyllabus {
def main(args: Array[String]): Unit = {
//文件读取
val file1 = Source.fromFile("C:\\Users\\61661\\Desktop\\scala笔记.txt")
val lines = file1.getLines
for (line <- lines) {
println(line)
}
file1.close
}
}
尖叫提示:记得close
1) 文件内容转数组:
val array= file1.getLines.toArray
2) 文件内容转字符串:
val iterator = file1.mkString
15.2 读取字符
由于Source.fromFile直接返回的就是Iterator[Char],所以可以直接对其进行迭代,按照字符访问里边每一个元素。
Source.fromFile("C:\\Users\\61661\\Desktop\\scala笔记.txt", "UTF-8")
for(ch <- file2){
println(ch)
}
file2.close
15.3 读取词法单元和数字
如果想将以某个字符或某个正则表达式分开的字符成组读取,可以这么做:
val file3 = Source.fromFile("D:\\BigData课堂笔记\\尚硅谷BigData笔记\\尚硅谷大数据技术之Scala\\2.资料\\info.csv")
val tokens = file3.mkString.split(",")
println(tokens.mkString(" "))
file3.close
15.4 读取网络资源、文件写入、控制台操作
1) 读取网络资源
val webFile = Source.fromURL("http://www.baidu")
webFile.foreach(print)
webFile.close()
2) 写入数据到文件
import java.io.{File, PrintWriter}
val writer = new PrintWriter(new File("嘿嘿嘿.txt"))
for (i <- 1 to 100)
writer.println(i)
writer.close()
3) 控制台操作
//控制台交互--老API
print("请输入内容:")
val consoleLine1 = Console.readLine()
println("刚才输入的内容是:" + consoleLine1)
//控制台交互--新API
print("请输入内容(新API):")
val consoleLine2 = StdIn.readLine()
println("刚才输入的内容是:" + consoleLine2)
15.5 序列化
@SerialVersionUID(1L) class Person extends Serializable{
override def toString = name + "," + age
val name = "Nick"
val age = 20
}
object PersonMain extends App{
override def main(args: Array[String]): Unit = {
import java.io.{FileOutputStream, FileInputStream, ObjectOutputStream, ObjectInputStream}
val nick = new Person
val out = new ObjectOutputStream(new FileOutputStream("Nick.obj"))
out.writeObject(nick)
out.close()
val in = new ObjectInputStream(new FileInputStream("Nick.obj"))
val saveNick = in.readObject()
in.close()
println(saveNick)
}
}
15.6 进程控制
我们可以使用scala来操作shell,scala提供了scala.sys.process包提供了用于shell程序交互的工具。
1) 执行shell
import sys.process._
"ls -al /"!
"ls -al /"!!
尖叫提示:!和!!的区别在于:process包中有一个将字符串隐式转换成ProcessBuild对象的功能,感叹号就是执行这个对象,单感叹号的意思就是程序执行成功返回0,执行失败返回非0,如果双感叹号,则结果以字符串的形式返回。
2) 管道符
import sys.process._
"ls -al /" #| "grep etc" !
3) 将shell的执行结果重定向到文件
import sys.process._
"ls -al /" #| "grep etc" !;
"ls -al /" #>> new File("output.txt") !;
尖叫提示:注意,每一个感叹号后边,有分号结束
scala进程还可以提供:
p #&& q操作,即p任务执行成功后,则执行q任务。
p #|| q操作,即p任务执行不成功,则执行q任务。
既然这么强大,那么crontab + scala + shell,就完全不需要使用oozie了。
15.7 正则表达式
我们可以通过正则表达式匹配一个句子中所有符合匹配的内容,并输出:
import scala.util.matching.Regex
val pattern1 = new Regex("(S|s)cala")
val pattern2 = "(S|s)cala".r
val str = "Scala is scalable and cool"
println((pattern2 findAllIn str).mkString(","))
十六、 高级类型
16.1 类型与类的区别
在Java里,一直到jdk1.5之前,我们说一个对象的类型(type),都与它的class是一一映射的,通过获取它们的class对象,比如 String.class, int.class, obj.getClass() 等,就可以判断它们的类型(type)是不是一致的。
而到了jdk1.5之后,因为引入了泛型的概念,类型系统变得复杂了,并且因为jvm选择了在运行时采用类型擦拭的做法(兼容性考虑),类型已经不能单纯的用class来区分了,比如 List 和 List 的class 都是 Class,然而两者类型(type)却是不同的。泛型类型的信息要通过反射的技巧来获取,同时java里增加了Type接口来表达更泛的类型,这样对于 List这样由类型构造器和类型参数组成的类型,可以通过 Type 来描述;它和 List 类型的对应的Type对象是完全不同的。
在Scala里,类型系统又比java复杂很多,泛型从一开始就存在,还支持高阶的概念(后续会讲述)。所以它没有直接用Java里的Type接口,而是自己提供了一个scala.reflect.runtime.universe.Type(2.10后)
在scala里获取类型信息是比较便捷的:
class A{}
object TypeSyllabus {
def main(args: Array[String]): Unit = {
import scala.reflect.runtime.universe._
println(typeOf[A])
}
}
同样scala里获取类(Class)信息也很便捷,类似:
class A{}
object TypeSyllabus {
def main(args: Array[String]): Unit = {
import scala.reflect.runtime.universe._
println(typeOf[A])
println(classOf[A])
}
}
尖叫提示:注意,typeOf 和 classOf 方法接收的都是类型符号(symbol),并不是对象实例。
16.2 classOf与getClass的区别
获取Class时的两个方法:classOf 和 getClass
scala> class A
scala> val a = new A
scala> a.getClass
res2: Class[_ <: A] = class A
scala> classOf[A]
res3: Class[A] = class A
上面显示了两者的不同,getClass方法得到的是 Class[A]的某个子类,而 classOf[A] 得到是正确的 Class[A],但是去比较的话,这两个类型是equals为true的。
这种细微的差别,体现在类型赋值时,因为java里的 Class[T]是不支持协变的,所以无法把一个 Class[_ < : A] 赋值给一个 Class[A]。
16.3 单例类型
16.4 类型投影
在scala里,内部类型(排除定义在object内部的),想要表达所有的外部类A实例路径下的B类型,即对 a1.B 和 a2.B及所有的 an.B类型找一个共同的父类型,这就是类型投影,用 A#B的形式表示。
A#B
/ \
/ \
a1.B a2.B
我们回头来对比一下scala里的类型投影与java里的内部类型的概念,java里的内部类型在写法上是 Outter.Inner 它其实等同于scala里的投影类型 Outter#Inner,java里没有路径依赖类型的概念,比较简化。
16.5 类型别名
可以通过type关键字来创建一个简单的别名,类型别名必须被嵌套在类或者对象中,不能出现在scala文件的顶层:
class Document {
import scala.collection.mutable._
type Index = HashMap[String, (Int, Int)]
}
def play(x: Index): Unit ={}
16.6 结构类型
结构类型是指一组关于抽象方法、字段和类型的规格说明,你可以对任何具备append方法的类的实例调用appendLines方法,这种方式比定义特质更加灵活,是通过反射进行调用的:
class Structure {
def play() = println("play方法调用了")
}
object HelloStructure {
def main(args: Array[String]): Unit = {
type X = {def play(): Unit} //type关键字是把 = 后面的内容命名为别名。
def init(res: X) = res.play //本地方法
init(new {
def play() = println("Played")
})
init(new {
def play() = println("Play再一次")
})
object A {
def play() {
println("A object play")
}
}
init(A)
val structure = new Structure
init(structure)
}
}
总结:
结构类型,简单来说,就是只要是传入的类型,符合之前定义的结构的,都可以调用。
16.7 复合类型
class A extends B with C with D with E
应做类似如下形式解读:
class A extends (B with C with D with E)
T1 with T2 with T3 …
这种形式的类型称为复合类型(compound type)或者也叫交集类型(intersection type)。
也可以通过type的方式声明符合类型:
type X = X1 with X2
16.8 中置类型
中置类型是一个带有两个类型参数的类型,以中置语法表示,比如可以将Map[String, Int]表示为:
val scores: String Map Int = Map("Fred" -> 42)
16.9 自身类型
self => 这句相当于给this起了一个别名为self:
class A {
self => //this别名
val x=2
def foo = self.x + this.x
}
self不是关键字,可以用除了this外的任何名字命名(除关键字)。就上面的代码,在A内部,可以用this指代当前对象,也可以用self指代,两者是等价的。
它的一个场景是用在有内部类的情况下:
class Outer { outer =>
val v1 = "here"
class Inner {
println(outer.v1) // 用outer表示外部类,相当于Outer.this
}
}
对于this别名 self =>这种写法形式,是自身类型(self type)的一种特殊方式。self在不声明类型的情况下,只是this的别名,所以不允许用this做this的别名。
16.10 运行时反射
scala编译器会将scala代码编译成JVM字节码,编译过程中会擦除scala特有的一些类型信息,在scala-2.10以前,只能在scala中利用java的反射机制,但是通过java反射机制得到的是只是擦除后的类型信息,并不包括scala的一些特定类型信息。从scala-2.10起,scala实现了自己的反射机制,我们可以通过scala的反射机制得到scala的类型信息。
给定类型或者对象实例,通过scala运行时反射,可以做到:1)获取运行时类型信息;2)通过类型信息实例化新对象;3)访问或调用对象的方法和属性等。
16.10.1 获取运行时类型信息
scala运行时类型信息是保存在TypeTag对象中,编译器在编译过程中将类型信息保存到TypeTag中,并将其携带到运行期。我们可以通过typeTag方法获取TypeTag类型信息。
import scala.reflect.runtime.universe._
val typeTagList = typeTag[List[Int]]//得到了包装Type对象的TypeTag对象
println(typeTagList)
或者使用:
typeOf[List[Int]]//直接得到了Type对象
尖叫提示:Type对象是没有被类型擦除的
我们可以通过typeTag得到里面的type,再通过type得到里面封装的各种内容:
import scala.reflect.runtime.universe._
val typeTagList = typeTag[List[Int]]
println(typeTagList)
println(typeTagList.tpe)
println(typeTagList.tpe.decls.take(10))
16.10.2 运行时类型实例化
我们已经知道通过Type对象可以获取未擦除的详尽的类型信息,下面我们通过Type对象中的信息找到构造方法并实例化类型的一个对象:
class Person(name:String, age: Int) {
def myPrint() = {
println(name + "," + age)
}
}
object PersonMain extends App{
override def main(args: Array[String]): Unit = {
//得到JavaUniverse用于反射
val ru = scala.reflect.runtime.universe
//得到一个JavaMirror,一会用于反射Person.class
val mirror = ru.runtimeMirror(getClass.getClassLoader)
//得到Person类的Type对象后,得到type的特征值并转为ClassSymbol对象
val classPerson = ru.typeOf[Person].typeSymbol.asClass
//得到classMirror对象
val classMirror = mirror.reflectClass(classPerson)
//得到构造器Method
val constructor = ru.typeOf[Person].decl(ru.termNames.CONSTRUCTOR).asMethod
//得到MethodMirror
val methodMirror = classMirror.reflectConstructor(constructor)
//实例化该对象
val p = methodMirror("Mike", 1)
println(p)
}
}
16.10.3 运行时类成员的访问
class Person(name:String, age: Int) {
def myPrint() = {
println(name + "," + age)
}
}
object PersonMain extends App{
override def main(args: Array[String]): Unit = {
//获取Environment和universe
val ru = scala.reflect.runtime.universe
//获取对应的Mirrors,这里是运行时的
val mirror = ru.runtimeMirror(getClass.getClassLoader)
//得到Person类的Type对象后,得到type的特征值并转为ClassSymbol对象
val classPerson = ru.typeOf[Person].typeSymbol.asClass
//用Mirrors去reflect对应的类,返回一个Mirrors的实例,而该Mirrors装载着对应类的信息
val classMirror = mirror.reflectClass(classPerson)
//得到构造器Method
val constructor = ru.typeOf[Person].decl(ru.termNames.CONSTRUCTOR).asMethod
//得到MethodMirror
val methodMirror = classMirror.reflectConstructor(constructor)
//实例化该对象
val p = methodMirror("Mike", 1)
println(p)
//反射方法并调用
val instanceMirror = mirror.reflect(p)
//得到Method的Mirror
val myPrintMethod = ru.typeOf[Person].decl(ru.TermName("myPrint")).asMethod
//通过Method的Mirror索取方法
val myPrint = instanceMirror.reflectMethod(myPrintMethod)
//运行myPrint方法
myPrint()
//得到属性Field的Mirror
val nameField = ru.typeOf[Person].decl(ru.TermName("name")).asTerm
val name = instanceMirror.reflectField(nameField)
println(name.get)
}
}
版权声明:本文标题:Scala超全详解 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/xitong/1730031256a1220002.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论