Scala语言学习
基础
- 安装
Scala
-
REPL
(Read-Evaluate-Print-Loop shell
) - 处理数据
- 字面量:直接出现在源代码中的数据
- 值
value
:val <identifier>[: <type>] = <data>
- 不可变的、有类型的存储单元,支持类型推导
- 变量
variable
:var <identifier>[: <type>] = <data>
- 可变的、有类型的存储单元,支持类型推导
- 可重新赋值,但是不可改变数据类型(支持类型转换的例外)
- 命名:支持使用字母、数字和一些特殊的操作符字符
例如可以使用等,不能使用
[]
和.
-
合法命名规则
- 不以数字开头
- 以数字开头的话,编译器最初会把解析到的第一个数字当成一个字面量数字,然后后面解析到其他非数字字符就会出错
- 一个或多个除反引用号外的任意字符,这些字符必须包围在一对反引号之中
val `a.b` = 4
- 不以数字开头
- 类型
-
核心数值类型
-
Byte
、Short
、Int
、Long
、Float
、Double
-
toType方法
手动完成类型间转换 - 与
JVM
包装数值类型可以互操作
-
-
字符串
String
建立在
Java
的String
基础上与
Java
不同,==
会检查字符串真正的想等性,而不是对象引用想等性三重引号创建多行
String
,类似Python
""" your string """
-
字符串内插
- 在字符串的第一个双引号前面增加一个
s
前缀,使用美元符$
指示插入数据的引用(某些容易区分的情况下,大括号{}
可以省略,但是加上更容易区分)
val approx = 355/113f println(s"Pi, using 355/133, is about ${approx}.")
- 在字符串的第一个双引号前面增加一个
-
字符串内插的替代格式可以使用
printf
语法(java.util.Formatter
)- 在字符串前面添加
f
前缀
scala> val item = "apple" scala> f"Enjoying this $item ${355/133.0}%.5f times today.")
- 在字符串前面添加
-
正则表达式(基于
Java
类java.util.regex.Pattern
)- 用正则表达式捕获值
val <Regex value>(<identifier>) = <input string>)
val pattern = """.* apple ([\d.]+) times .*""".r
val pattern(amountText) = input
val amount = amountText.toDouble
- 用正则表达式捕获值
-
Scala
类型概述
-
Nothing
:所有类型的子类 -
Null
:所有指示null
值的AnyRef
类型的子类 -
Unit
:指示没有值(类似void
) -
Any
:Scala
中所有类型的根 -
AnyVal
:所有值类型的根 -
AnyRef
:所有引用(非值)类型的根
-
Scala
不支持其他类型到Boolean类型
的自动转换。非null字符串不会计算为true
-
元组
(<value 1>,<value 2>[,<value 3>...])
- 不同于列表和数组,没有办法迭代处理一个元组中的元素,元组的作用只是作为多个值的容器
- 根据元素的索引访问元组中的元素(下划线加上索引,索引从1开始)
- 创建一个大小为2的元组,也可以使用关系操作符(
->
),表示元组中的键值对
-
表达式和条件式
-
表达式块(用
{}
结合多个表达式创建,最后一个表达式作为表达式块的返回值)- 可嵌套、跨行,多个表达式写在一行需要
;
分隔
- 可嵌套、跨行,多个表达式写在一行需要
语句:不返回值的表达式
-
if...else
表达式块if (<Boolean expression>) <expression>
if (<Boolean expression>) <expression> else <expression>
-
匹配表达式(类似
switch
)-
Scala
匹配表达式支持匹配如值、类型、正则表达式、数值范围和数据结构内容
<expression> match{ case <pattern match> => <expression> [case ...] }
- 可以使用模式替换式(
pattern alternative
),匹配多种内容 case <pattern 1> | <pattern 2> .. => <one or more expressions>
- 增加通配模式,放止输入内容无法匹配的情况运行时异常
- 值绑定模式
case <identifier> => <one or more expressions>
- 将输入绑定到一个特定的字面量上,如果不匹配就会进入这个绑定值的情况
- 使用通配符
_
:相当于匿名占位符,将在运行时替换为一个表达式的值- 与值绑定的不同在于不能再
case
表达式块中访问通配符的值
- 与值绑定的不同在于不能再
- 值绑定模式
- 用模式哨位匹配
- 增加一个
if
语句,为匹配表达式增加条件逻辑 case <pattern> if <Boolean expression> => <one or more expression>
- 增加一个
- 用模式变量匹配类型
- 匹配输入表达式的类型
case <identifier>: <type> => <one or more expressions>
-
-
循环
-
Range
(范围)数据结构<starting integer> [to|until] <ending integer> [by increment]
-
基本for循环迭代处理
for (<indentifier> <- <iterator>) [yield] [<expression>]
-
yield
:如果指定,则调用的所有表达式的返回值将作为一个集合返回,如果没有指定,则调用表达式,但是不能访问它的返回值 for (x <- 1 to 7){println(s"Day $x:")}
-
迭代器哨位(过滤器)
for (<indentifier> <- <iterator> if <Boolean expression>)...
threes = for(i <- 1 to 20 if i%3 == 0) yield i
-
嵌套迭代器
for{ x <- 1 to 2 y <- 1 to 3 } { print(s"($x,$y) ")}
-
值绑定
for (<indentifier> <- <iterator>; <identifier> = <expression>)...
for(i <- 0 to 8; pow = 1 <<i) yield pow
-
While
和Do/While
循环while(<Boolean expression>) statement
-
函数
- 定义无输入的函数
def <identifier> = <expression>
def <identifier>() = <expression>
- 如果定义函数时没加小括号,则调用时也不可以加小括号,而加了小括号的无输入函数调用时可以不加小括号
- 定义函数时指定返回类型
def <identifier>: <type> = <expression>
- 定义函数
def <identifier>(<identifier>: <type>[,...]): <type> = <expression>
- 过程:没有返回值的函数
- 使用表达式块调用函数
- 当使用一个参数调用函数时,可以使用表达式块返回的结果发送参数
- 递归函数
- 递归函数可能会遇到“栈溢出”错误,
Scala
编译器可以使用尾递归优化一些递归函数 - 利用尾递归优化的函数,递归调用不会创建新的空间,而是使用当前函数的栈空间
- 只有最后一个语句是递归调用的函数才能由
Scala
编译器完成尾递归优化 - 可以利用函数注解来标志一个函数将完成尾递归优化
@annotation.tailrec
- 递归函数可能会遇到“栈溢出”错误,
- 嵌套函数
- 用命名参数调用函数
- 可以不按原先定义的顺序指定参数
- 有默认值的参数
-
Vararg
参数- 这是一个函数参数,可以匹配调用者的0个或多个实参
-
vararg
参数后面不能跟非vararg
参数,因为无法加以区分 -
vararg
参数实现为一个集合,可以通过迭代器访问 - 要标志一个参数匹配一个或者多个输入实参,在函数定义中需要该参数类型后面增加一个
*
号 - 例如
def sum(items: Int*): Int = {...}
- 参数组
- 可以把参数表分解为参数组,每个参数组用小括号分割
- 例如
def max(x:Int)(y:Int) = if (x > y) x else y
- 类型参数
- 类型参数指示了值参数类型或返回值类型
- 函数参数或返回值的类型不再固定,而是可以由函数调用者设置
def <function-name>[type-name](<parameter-name>:<type-name>): <type-name>...
- 例如
def identity[A](a: A): A = a
- 方法和操作符
- 中缀点记法:
<class instance>.<method>[(<parameters>)]
-
Scala
中使用的所有算术运算符都是方法 -
Scala
的操作符记法允许使用空格分隔对象、操作符方法和方法的参数(只有一个) - 例如
2 + 3
和2.+(3)
是一样的 - 操作符记法:
<object> <method> <parameter>
- 如果要调用多个参数,则必须将多个参数包含在
()
中
- 如果要调用多个参数,则必须将多个参数包含在
- 中缀点记法:
首类函数
- “首类”表示函数不仅能得到声明和调用,还可以作为一个数据结构用在这个语言的任何地方
- 高阶函数:接受其他函数作为参数,或者使用函数作为返回值
- 声明式编程:要求使用高阶函数或其他机制声明要做的工作,而不手动实现
- 命令式编程:总要明确指定操作的逻辑流
- 函数类型和值
函数的类型是其输入类型与返回值类型的一个简单组合,用一个箭头从输入类型指向输出类型
-
([<type>, ...]) => <type>
def double(x: Int): Int = x*2 val myDouble: (Int) => Int = double val myDoubleCopy = myDouble val myDouble2 = double _
-
myDouble
就是一个值,只不过这个值可以调用 - 一个函数值可以赋值给一个新值
-
myDouble
必须有显示的类型,以区分它是一个函数值,而不是一个函数调用(如果函数只有单个参数可以省略小括号) -
val <identifier> = <function name> _
:使用通配符为函数赋值
-
- 函数字面量
- 创建一个没有名字的函数并把这个函数赋值一个新的函数值
val doubler = (x: Int) => x * 2
- 函数字面量是所赋数据的一个字面量表达式
- 其他名字:
匿名函数
、Lambda表达式
、Lambdas
、function0,function1,function2,...
(Scala
编译器对字面量的叫法。根据输入参数的个数而定,单参数就叫<function1>
) - 语法:
([<identifier>: <type>, ...]) => <expression>
- 占位符语法
函数字面量的一种缩写形式,将命名参数替换为通配符(
_
)-
使用条件
- 函数的现实类型在字面量之外指定(明确好输入输出)
- 输入参数最多只使用一次(因为只能按照位置匹配一次,多个占位符有着相同的符号,匹配之后就无法确定需要的参数)
-
示例
def combination(x: Int, y: Int, f: (Int,Int) => Int) = f(x,y) combination(23, 12, _*_)
def tripleOp[A,B](a: A, b: A, c: A, f: (A, A, A) => B) => f(a, b, c) tripleOp[Int, Int](23, 92, 14, _ * _ + _) tripleOp[Int, Double](23, 92, 14, 1.0 * _ / _ / _)
- 部分应用函数和柯里化
功能:为函数保留一些重复使用的参数
-
方法一:部分应用(
partially apply
)函数,使用通配符替代其他参数(函数按位置匹配)def factorOf(x: Int, y: Int) = y % x == 0 val mutipleOf3 = factorOf(3, _: Int) val y = mutipleOf3(78)
-
方法二:函数柯里化(
curring
,讨好的意思),使用参数组-
有多个参数表的函数可以认为是多个函数的一个链。单个参数表则认为是一个单独的函数调用
def factorOf(x: Int)(y: Int) = y % x == 0 val isEven = factorOf(2) _ val z = isEven(32) // z = true
def factorOf(x: Int)(y: Int)
的函数类型为Int => Int => Boolean
-
- 传名参数
一种函数参数类型:可以取一个值,也可以取最终返回一个值的函数
-
语法:
<identifier>: => <type>
scala> def doubles(x: => Int) = { | println("Now doubling" + x) | x * 2 | } doubles: (x: => Int)Int scala> doubles(5) Now doubling 5 res1: Int = 10 scala> def f(i: Int) = { println(s"Hello from f($i)"); i } f: (i: Int)Int scala> doubles( f(8) ) Hello from f(8) Now doubling 8 Hello from f(8) //doubles调用两次x,所以f函数也调用两次 res2: Int = 16
- 偏函数
- 有些函数并不能支持满足输入类型的所有可能值。这种函数称为偏函数,因为它们只能部分应用于输入数据
- 用函数字面量块调用高阶函数
- 实际上就是输入的参数类型是函数,那个输入参数可以用表达式块或者函数字面量来代替
- 好处
- 将单独的代码块包为在工具函数中
- 管理数据库事务,即高阶函数打开会话,调用函数参数,然后commit或者rollback结束事务
- 重新尝试处理可能的错误,将函数参数调用指定的次数,直到不再产生错误
- 根据局部、全局或者外部值有条件地调用函数参数
常用集合
- 列表、集和映射
-
List
:不可变的单链表- 访问列表中的单个元素,可以作为一个函数调用这个列表,并提供一个索引号(从0开始)
- List是一个不可变的递归数据结构(链表),列表中的每一项都有自己的表头和越来越短的表尾
- 所有列表都有一个
Nil
实例作为终结点,可以通过比较当前元素与Nil
来检查是否到达列表表尾(Nil
实际上是List[Nothing]
的一个单例实例)
-
Set
:不可变的无序集合 -
Map
:不可变的键值库,也称为散列映射、字典或关联数组 -
List
、Set
、Map
的根类型都是Iterable
-
foreach()
:取一个函数,对列表中的每一项,分别调用这个函数 -
map()
:取一个函数,将一个列表元素转换为另一个值或类型 -
reduce()
:取一个函数,将两个列表元素结合为一个元素
-
-
-
Cons
操作符可以通过
cons
(construct
的简写)操作符来构建列表-
使用
Nil
为基础,并使用右结合的cons操作符::
绑定元素,就可以构建列表val numbersOld = List(1,2,3) val numbers = 1 :: 2 :: 3 :: Nil
-
::
是List
的一个方法,它取一个值,这会成为新列表的表头val first = Nil.::(1) // 1 -> Nil
- 列表算术运算
- 因为
List
是一个不可变的集合,所有“修改”是指“返回一个新列表,其中包含所请求的修改” - 谓词函数(
predicate function
):得到一个输入值后会相应地返回true
或false
-
::
:为列表追加单个元素 -
:::
:为列表追加另一个列表 -
++
:为列表追加另一个集合 -
==
:判断集合类型和元素内容是否相同 -
distinct
:返回不包含重复的列表 -
drop
:去除列表前n个元素 -
filter
:输入一个谓词函数,过滤掉指定的元素 -
flatten
:将一个包含列表的列表转换为元素列表 -
partition
:输入一个谓词函数,将元素分组,由两个列表组成一个元组 -
reverse
:逆转列表 -
slice
:切片,输入切片的开头和结尾,输出不包括结尾 -
sortBy
:根据指定函数排序 -
sorted
:根据自然顺序排序 -
splitAt
:根据指定索引将列表切分成两个 -
take
:从列表抽取前n个元素 -
zip
:两个列表合并为一个元组列表
- 因为
- 映射列表
-
collect
:使用偏函数转换各个元素,保留可应用的元素 -
flatMap
:给定一个函数转换各个元素,扁平化结果(消除列表中的列表,这种嵌套结构) -
map
:使用给定函数转换各个元素 -
Scala
可以把Java数组
转换为它自己的类型Array
-
- 归约列表
- 将列表收缩为单个值
-
Scala
的集合支持:- 数学归约:
max
、min
、product
、sum
- 逻辑归约:
contains
、endsWith
、exists
、forall
、startsWith
- 通用的高阶操作,折叠,可以用来创建任何其他类型的列表归约算法
-
fold
、foldLeft
、foldRight
、reduce
、reduceLeft
、reduceRight
、scan
、scanLeft
、scanRight
-
- 数学归约:
- 转换集合
-
mkString
:根据指定分隔符将一个集合转换为String
-
toBuffer
:将一个不可变的集合转变为可变的集合 -
toList
:将一个集合转变为List
-
toMap
:将一个2元元组的集合转换为一个Map
-
toSet
:将一个集合转换为一个Set
-
toString
:将一个集合呈现为一个String
,包括集合的类型
-
-
Java
和Scala
集合兼容性- 默认情况下两者集合类型是不兼容的
- 添加
JavaConverters
:import collection.JavaConverters._
- 集合转换:
asJava
、asScala
- 使用集合的模式匹配
- 匹配表达式、模式哨卫、值绑定
- 模式匹配是
Scala语言
的一个核心特性
更多集合
-
可变的集合类型 : 不可变的集合类型
-
collection.mutable.Buffer
:collection.imutable.List
-
collection.mutable.Set
:collection.imutable.Set
-
collection.mutable.Map
:collection.imutable.Map
- 使用
toList
、toSet
、toMap
将可变集合类型转换回不可变的集合类型
-
-
从不可变集合创建可变集合
- 不可变集合
List
、Map
和Set
都可以使用toBuffer方法
转换为可变的collection.mutable.Buffer
类型
- 不可变集合
-
使用集合构建器
-
Builder
:生成指定的集合类型,只支持追加操作 Set.newBuilder[Char]
- 需要调用
result方法
得到最终结果
-
-
数组
- Array是一个大小固定的可变索引集合,实际只是Java数组类型的一个包装器,另外还提供了隐含类高级特性,使它可以项序列一样使用
val numbers = Array(1, 2, 3, 4)
-
Seq
和序列-
Seq
是所有序列的根类型
Seq
:所有序列的根类型,List()
的快捷方式IndexedSeq
:索引序列的根类型,Vector()
的快捷方式Vector
:这一类列表有一个后备Array
实例,可以按索引访问Range
:整数范围。动态生成数据LinearSeq
:线性(链表)序列的根类型List
:元素的单链表Queue
:先进先出列表Stack
:后进先出列表Stream
:懒列表。访问元素时才增加相应元素String
:字符集合,扩展了Iterable
-
-
Stream
懒集合,由一个或多个启示元素和一个递归函数生成。流可能是无界的,理论上是无限的集合,只是在访问元素时才回生成这个元素
使用
Stream.cons
用表头表尾构建一个新的流,替代语法,可以使用#::
操作符示例:
def inc(head: Int): Stream[Int] = head #:: inc(head+1)
-
创建一个有界的流
def to(head: Char, end: Char): Stream[Char] = (head > end) match { case true => Stream.empty case false => head #:: to((head+1).toChar, end) }
-
一元集合
- 支持类似
Iterable
中的变换操作,但是包含的元素不能多于1个
- 支持类似
-
Option
集合- 扩展了
Iterable
的一个一元集合,Option类型
表示一个值的存在或不存在 -
Option
可以安全地替代null
值,告诉用户这个值可能不存在,从而减少出发NullPointerException异常
的可能性 - 另一些开发人员把它看作是构建操作链的一种更为安全的方法,确保操作链中只包含有效的值
-
Option
依赖两个子类型提供的具体实现:Some
和None
-
Some
:一个类型参数化的单元素集合 -
None
:一个空集合。None
类型没有类型参数,因为它永远不包含任何内容 - 用
isDefined
和isEmpty
分别检查一个给定的Option
是Some
还是None
-
-
Option
集合提供了一种安全的机制,而且还提供了一些操作来存储和变换可能存在也可能不存在的值,还提供了一些安全操作可以抽取可能存在的值 - 警告:避免使用
Option.get()
,此方法不安全,对None
调用get()
将导致运行时错误 - 安全的
Option
抽取操作fold
-
getOrElse
:为Some
,则返回值;为None
,则返回传名参数的结果 -
orElse
:非空,返回这个Option
,否则从给定的传名参数返回一个Option
- 匹配表达式
match{ case Some(x) => x; case None => -1 }
- 扩展了
-
Try
集合- 将错误处理转变为集合管理。提供一种机制来捕获给定函数参数中发生的错误,并返回这个错误。
-
Scala
抛出一个异常会中断程序流,并把控制交回给最近的处理器来处理这个特定的异常。未处理的异常会终止应用。 -
Scala
支持try{}..catch{}
块。 - 推荐使用
util.Try()
,因为它提供了一种更安全、更有表述性,而且纯粹一元的方法来处理错误 -
util.Try
类型没有具体实现,但是有两个已实现的子类型Success
和Failure
- 使用Try的错误处理方法
flatMap
:对于Success
,调用一个同样返回util.Try
的函数,从而将当前返回值映射到一个新的内嵌返回值foreach
:一旦Success
,执行给定的函数,Failure
则什么都不做getOrElse
:返回Success
中的内嵌值,对于Failure
则返回传名参数的值orElse
:与flatMap
相反。对于Failure
,调用一个同样返回util.Try
的函数toOption
:将util.Try
转换为Option
,缺点是丢失内嵌的Exception
map
:对于Success
,调用一个函数,将内嵌值映射到一个新值匹配表达式
match { case util.Success(x) =>x; case util.Failure(error) => -1 }
-
什么都不做:只需要允许异常在调用栈中向上传播,直到被捕获或者导致当前应用退出
val input = " 123 " val result = util.Try(input.toInt) orElse util.Try(input.trim.toInt) result foreach { r => println(s"Parsed '$input' to $r!") } val x = result match { case util.Success(x) => Some(x) case util.Failure(ex) => { println(s"Couldn't parse input '$input'") None } }
-
Future
集合-
concurrent.Future
,发起一个后台任务 -
future
表示一个可能的值,并提供了安全操作来串链其他操作或者抽取值。与Option
和Try
不同,future的值不是立即可用,因为创建future
时后台任务可能仍在工作。 - 支持在并发线程中运行后台任务。
- 调用
future
并提供一个函数会在一个单独的线程中执行该函数,而当前线程仍继续操作 - 在创建
future
之前,必须指定当前会话或应用的“上下文”来并发运行函数,默认使用global上下文
import concurrent.ExecutionContext.Implicits.global
- 可以设置回调函数或另外的
future
,当future
任务执行完成时,执行这个回调或者future
- 异步处理
future
-
fallbackTo
:将第二个future
串链到第一个future
,如果第一个future
不成功,则调用第二个future
-
flatMap
:如果第一个成功,则使用第一个的返回值调用第二个future
-
map
:将给定的函数串链到future
,如果future
成功,则其返回值将用来调用这个函数 -
onComplete
:future
任务完成后,将用一个util.Try
调用指定函数,其中包含一个值(成功)或者一个异常(失败) -
onFailure
:如果future
任务抛出一个异常,将使用这个异常来调用指定函数 -
onSuccess
:如果future
任务成功完成,将使用返回值来调用给定函数 -
Future.sequence
:并发地运行给定序列中的future
,返回一个新的future
-
- 同步处理
future
-
concurrent.Await.result()
,取后台线程和一个最大等待时间 - 等待时间:
import concurrent.duration._
- 等待10s:
val maxTime = Duration(10, SECONDS)
-
-
类
-
基础
- 在
Scala
中,类参数在类名后指定,就像函数定义中函数参数跟在函数名后面一样。类参数可以用来初始化字段(类中的值和变量),或者用于传入函数,但是一旦类已经创建,这些参数就不再可用 - 可以把某个字段声明为类参数,通过在类参数前增加
val
或者var
,类参数就成为类中的一个字段 -
Scala
中,一个类可以使用extends
关键字扩展最多一个其他类,另外可以用override
关键字覆盖所继承方法的行为 - 类中的字段和方法用
this
关键字访问,父类中的字段和方法可以用super
关键字访问 -
Scala
多态指类能够采用其他兼容类的形式。“兼容”指一个子类的实例可以用于替代其父类的实例,但是反过来不行
- 在
-
定义类
class <identifier> [type-parameters] [([val|var] <identifier>: <type>[, ...])] //输入参数可以有默认值 [extends <identifier>[type-parameters](<input parameters>)] //继承父类可以有输入参数 [{ fields, methods, and classes }]
- 实例就是一个内存分配,提供了类字段的存储空间。这个动作(即预留空间来分配一个类的内容)称为实例化
- 访问类的字段和方法:标准中缀点记法、中缀操作符记法
-
抽象类
- 将由其他类扩展的一个类,而自己不能实例化
- 由
abstract
关键字指定 - 定义其子类必须有的核心字段和方法,但一般不提供具体实现(可以有默认实现)
- 基于多态,如果一个值的类型为抽象类,它可以具体指向某个非抽象子类的实例,调用方法时实际上最后会在子类上调用
重载方法
-
apply
方法- 名为“
apply
”的方法有时是指它要作为一个默认方法或一个注入方法,可以直接调用而不需要方法名 - 实际是一种快捷方式,可以使用小括号触发功能而不需要方法名,就如
python
的__call__
方法,以及pytorch
的forward
- 名为“
-
懒值
- 定义一个值时可以在
val
关键字前面加上关键字lazy
来创建一个懒值 - 类中使用的字段(值和变量)都是在类第一次实例化时创建的,而懒值是第一次调用时创建
- 要确保时间或者性能敏感操作在类的生命周期中只执行一次,懒值则是一种很好的方法
- 常用于存储基于文件的属性、打开的数据库链接,以及其他只有在确实必要时才初始化的不可变数据等信息。
- 定义一个值时可以在
-
包装
- 为
Scala
文件定义包:package <identifier>
- 源文件要存储在与包匹配的目录中
- 访问包装类:用点分隔的包路径或者导入包
- 与
Java
不同,代码中的任何位置都可以使用import
,与python
类似 - 支持使用
_
操作符导入一个包的全部内容 - 导入组:
import <package>.{<class 1>[,<class 2>...]}
- 使用导入别名:
import <package>.{<original name> => <alias>}
- 包装语法:包作为一个块,用大括号包围它的类
package <identifier> { <class definitions> }
- 为
-
私密性控制
- 默认地,
Scala
不会增加私密性控制 -
protected
:同一个类或者子类中的代码才能访问 -
private
:仅定义这个字段或方法的类可以访问
- 默认地,
-
私密性访问修饰符
- 包级保护
- 另外,可以根据另一个类的亲密性覆盖这些策略
- 实例级保护
- 包级保护
-
最终类和密封类
-
final
类不能在子类中被覆盖,final
关键字定义的值,变量或方法,可以确保所有子类都将使用这个实现 -
sealed
密封类- 密封类会限制一个类的子类必须位于福勒所在的同一个文件中
-
对象、Case类和Trait
- 对象
object
- 一种类类型,只能有不超过1个实例,也就是单例(
singleton
) -
Java
和其他语言的某些字段和方法为“静态”或“全局”,对象可以提供类似的功能,不过将它们与可实例化的类解耦合 - 对象不是用
new
关键字创建,只需要按名字直接访问,在首次访问时自动实例化(第一次访问前,不会实例化) - 定义对象用
object
关键字,没有任何输入参数,对象不能扩展,但是别的类可以继承它 - 定义:
object <identifier> [extends <identifier>] [{ fields, methods, and classes }]
- 最适合
object
的函数是纯函数和处理外部I/O
的函数- 纯函数:会返回完全由其输入计算得到的结果,而没有任何副作用
-
I/O
函数:处理外部数据的函数,如处理文件、数据库和外部服务
-
apply
方法和伴生对象工厂模式是一种很流行的方法,可以从伴生对象生成一个类的新实例。
-
伴生对象是与类同名的一个对象,与类在同一个文件中定义
class Multiplier(val x: Int) { def product(y: Int) = x * y } object Multiplier { def apply(x: Int) = new Multiplier(x) }
- 使用对象的命令行应用
- 使用对象中的一个
main
方法作为应用的入口点 def main(args: Array[String]) { ... }
- 使用对象中的一个
-
Case
类case class <identifier> ([var] <identifier>: <type>[,...]) [extends <identifier>(<input-parameters>)] [{ fields and methods }]
- 不可实例化的类,包含多个自动生成的方法,包括一个自动生成的伴生对象
- 适合数据传输对象,主要用于存储数据
-
val
关键字可以用,但是case
类默认将输入参数转换为值字段,所以没必要添加,如果需要一个变量字段,则使用var
关键字 -
case
类方法-
apply
:对象object
中,一个工厂方法,用于实例化case
类 -
copy
:类中,返回实例的一个副本 -
equals
:类中 -
hashCode
:类中 -
toString
:类中 -
unapply
:对象中,将实例抽取到一个字段元组,从而可以使用case
类实例完成模式匹配
-
-
Trait
-
trait
不能实例化,支持多重继承的类 -
trait
不能有类参数,但可以有类型参数 - 定义:
trait <identifier> [extends <identifier>] [{ fields, methods, and classes }]
- 用with关键字为类增加一个或多个
trait
,类没有扩展trait
,而是被trait
扩展,trait
为现有的类增加新的功能或者配置,这个特性也称为依赖注入-
Spring
通过定制Java注解
和初始化模块实现了类似的功能,但是Scala
的trait
不要求特定的注解或特殊的包来实现依赖注入
-
-
- 导入实例成员
- 与导入类和对象的
import
使用方法一样,可以导入单个成员,或者通过通配符_
导入所有成员 - 不会覆盖私密性控制,只能导入可以正常访问的字段和方法
- 与导入类和对象的
- 一种类类型,只能有不超过1个实例,也就是单例(
高级类型
- 暂略