数学基础
- 线性代数
- 概率论与随机过程
- 数值计算
- 蒙特卡洛方法与 MCMC 采样
- 机器学习方法概论
统计学习
深度学习
- 深度学习简介
- 深度前馈网络
- 反向传播算法
- 正则化
- 深度学习中的最优化问题
- 卷积神经网络
- CNN:图像分类
- 循环神经网络 RNN
- Transformer
- 一、Transformer [2017]
- 二、Universal Transformer [2018]
- 三、Transformer-XL [2019]
- 四、GPT1 [2018]
- 五、GPT2 [2019]
- 六、GPT3 [2020]
- 七、OPT [2022]
- 八、BERT [2018]
- 九、XLNet [2019]
- 十、RoBERTa [2019]
- 十一、ERNIE 1.0 [2019]
- 十二、ERNIE 2.0 [2019]
- 十三、ERNIE 3.0 [2021]
- 十四、ERNIE-Huawei [2019]
- 十五、MT-DNN [2019]
- 十六、BART [2019]
- 十七、mBART [2020]
- 十八、SpanBERT [2019]
- 十九、ALBERT [2019]
- 二十、UniLM [2019]
- 二十一、MASS [2019]
- 二十二、MacBERT [2019]
- 二十三、Fine-Tuning Language Models from Human Preferences [2019]
- 二十四 Learning to summarize from human feedback [2020]
- 二十五、InstructGPT [2022]
- 二十六、T5 [2020]
- 二十七、mT5 [2020]
- 二十八、ExT5 [2021]
- 二十九、Muppet [2021]
- 三十、Self-Attention with Relative Position Representations [2018]
- 三十一、USE [2018]
- 三十二、Sentence-BERT [2019]
- 三十三、SimCSE [2021]
- 三十四、BERT-Flow [2020]
- 三十五、BERT-Whitening [2021]
- 三十六、Comparing the Geometry of BERT, ELMo, and GPT-2 Embeddings [2019]
- 三十七、CERT [2020]
- 三十八、DeCLUTR [2020]
- 三十九、CLEAR [2020]
- 四十、ConSERT [2021]
- 四十一、Sentence-T5 [2021]
- 四十二、ULMFiT [2018]
- 四十三、Scaling Laws for Neural Language Models [2020]
- 四十四、Chinchilla [2022]
- 四十七、GLM-130B [2022]
- 四十八、GPT-NeoX-20B [2022]
- 四十九、Bloom [2022]
- 五十、PaLM [2022] (粗读)
- 五十一、PaLM2 [2023](粗读)
- 五十二、Self-Instruct [2022]
- 句子向量
- 词向量
- 传统CTR 预估模型
- CTR 预估模型
- 一、DSSM [2013]
- 二、FNN [2016]
- 三、PNN [2016]
- 四、DeepCrossing [2016]
- 五、Wide 和 Deep [2016]
- 六、DCN [2017]
- 七、DeepFM [2017]
- 八、NFM [2017]
- 九、AFM [2017]
- 十、xDeepFM [2018]
- 十一、ESMM [2018]
- 十二、DIN [2017]
- 十三、DIEN [2019]
- 十四、DSIN [2019]
- 十五、DICM [2017]
- 十六、DeepMCP [2019]
- 十七、MIMN [2019]
- 十八、DMR [2020]
- 十九、MiNet [2020]
- 二十、DSTN [2019]
- 二十一、BST [2019]
- 二十二、SIM [2020]
- 二十三、ESM2 [2019]
- 二十四、MV-DNN [2015]
- 二十五、CAN [2020]
- 二十六、AutoInt [2018]
- 二十七、Fi-GNN [2019]
- 二十八、FwFM [2018]
- 二十九、FM2 [2021]
- 三十、FiBiNET [2019]
- 三十一、AutoFIS [2020]
- 三十三、AFN [2020]
- 三十四、FGCNN [2019]
- 三十五、AutoCross [2019]
- 三十六、InterHAt [2020]
- 三十七、xDeepInt [2023]
- 三十九、AutoDis [2021]
- 四十、MDE [2020]
- 四十一、NIS [2020]
- 四十二、AutoEmb [2020]
- 四十三、AutoDim [2021]
- 四十四、PEP [2021]
- 四十五、DeepLight [2021]
- 图的表达
- 一、DeepWalk [2014]
- 二、LINE [2015]
- 三、GraRep [2015]
- 四、TADW [2015]
- 五、DNGR [2016]
- 六、Node2Vec [2016]
- 七、WALKLETS [2016]
- 八、SDNE [2016]
- 九、CANE [2017]
- 十、EOE [2017]
- 十一、metapath2vec [2017]
- 十二、GraphGAN [2018]
- 十三、struc2vec [2017]
- 十四、GraphWave [2018]
- 十五、NetMF [2017]
- 十六、NetSMF [2019]
- 十七、PTE [2015]
- 十八、HNE [2015]
- 十九、AANE [2017]
- 二十、LANE [2017]
- 二十一、MVE [2017]
- 二十二、PMNE [2017]
- 二十三、ANRL [2018]
- 二十四、DANE [2018]
- 二十五、HERec [2018]
- 二十六、GATNE [2019]
- 二十七、MNE [2018]
- 二十八、MVN2VEC [2018]
- 二十九、SNE [2018]
- 三十、ProNE [2019]
- Graph Embedding 综述
- 图神经网络
- 一、GNN [2009]
- 二、Spectral Networks 和 Deep Locally Connected Networks [2013]
- 三、Fast Localized Spectral Filtering On Graph [2016]
- 四、GCN [2016]
- 五、神经图指纹 [2015]
- 六、GGS-NN [2016]
- 七、PATCHY-SAN [2016]
- 八、GraphSAGE [2017]
- 九、GAT [2017]
- 十、R-GCN [2017]
- 十一、 AGCN [2018]
- 十二、FastGCN [2018]
- 十三、PinSage [2018]
- 十四、GCMC [2017]
- 十五、JK-Net [2018]
- 十六、PPNP [2018]
- 十七、VRGCN [2017]
- 十八、ClusterGCN [2019]
- 十九、LDS-GNN [2019]
- 二十、DIAL-GNN [2019]
- 二十一、HAN [2019]
- 二十二、HetGNN [2019]
- 二十三、HGT [2020]
- 二十四、GPT-GNN [2020]
- 二十五、Geom-GCN [2020]
- 二十六、Graph Network [2018]
- 二十七、GIN [2019]
- 二十八、MPNN [2017]
- 二十九、UniMP [2020]
- 三十、Correct and Smooth [2020]
- 三十一、LGCN [2018]
- 三十二、DGCNN [2018]
- 三十三、AS-GCN
- 三十四、DGI [2018]
- 三十五、DIFFPOLL [2018]
- 三十六、DCNN [2016]
- 三十七、IN [2016]
- 图神经网络 2
- 图神经网络 3
- 推荐算法(传统方法)
- 一、Tapestry [1992]
- 二、GroupLens [1994]
- 三、ItemBased CF [2001]
- 四、Amazon I-2-I CF [2003]
- 五、Slope One Rating-Based CF [2005]
- 六、Bipartite Network Projection [2007]
- 七、Implicit Feedback CF [2008]
- 八、PMF [2008]
- 九、SVD++ [2008]
- 十、MMMF 扩展 [2008]
- 十一、OCCF [2008]
- 十二、BPR [2009]
- 十三、MF for RS [2009]
- 十四、 Netflix BellKor Solution [2009]
- 推荐算法(神经网络方法 1)
- 一、MIND [2019](用于召回)
- 二、DNN For YouTube [2016]
- 三、Recommending What Video to Watch Next [2019]
- 四、ESAM [2020]
- 五、Facebook Embedding Based Retrieval [2020](用于检索)
- 六、Airbnb Search Ranking [2018]
- 七、MOBIUS [2019](用于召回)
- 八、TDM [2018](用于检索)
- 九、DR [2020](用于检索)
- 十、JTM [2019](用于检索)
- 十一、Pinterest Recommender System [2017]
- 十二、DLRM [2019]
- 十三、Applying Deep Learning To Airbnb Search [2018]
- 十四、Improving Deep Learning For Airbnb Search [2020]
- 十五、HOP-Rec [2018]
- 十六、NCF [2017]
- 十七、NGCF [2019]
- 十八、LightGCN [2020]
- 十九、Sampling-Bias-Corrected Neural Modeling [2019](检索)
- 二十、EGES [2018](Matching 阶段)
- 二十一、SDM [2019](Matching 阶段)
- 二十二、COLD [2020 ] (Pre-Ranking 模型)
- 二十三、ComiRec [2020](https://www.wenjiangs.com/doc/0b4e1736-ac78)
- 二十四、EdgeRec [2020]
- 二十五、DPSR [2020](检索)
- 二十六、PDN [2021](mathcing)
- 二十七、时空周期兴趣学习网络ST-PIL [2021]
- 推荐算法之序列推荐
- 一、FPMC [2010]
- 二、GRU4Rec [2015]
- 三、HRM [2015]
- 四、DREAM [2016]
- 五、Improved GRU4Rec [2016]
- 六、NARM [2017]
- 七、HRNN [2017]
- 八、RRN [2017]
- 九、Caser [2018]
- 十、p-RNN [2016]
- 十一、GRU4Rec Top-k Gains [2018]
- 十二、SASRec [2018]
- 十三、RUM [2018]
- 十四、SHAN [2018]
- 十五、Phased LSTM [2016]
- 十六、Time-LSTM [2017]
- 十七、STAMP [2018]
- 十八、Latent Cross [2018]
- 十九、CSRM [2019]
- 二十、SR-GNN [2019]
- 二十一、GC-SAN [2019]
- 二十二、BERT4Rec [2019]
- 二十三、MCPRN [2019]
- 二十四、RepeatNet [2019]
- 二十五、LINet(2019)
- 二十六、NextItNet [2019]
- 二十七、GCE-GNN [2020]
- 二十八、LESSR [2020]
- 二十九、HyperRec [2020]
- 三十、DHCN [2021]
- 三十一、TiSASRec [2020]
- 推荐算法(综述)
- 多任务学习
- 系统架构
- 实践方法论
- 深度强化学习 1
- 自动代码生成
工具
- CRF
- lightgbm
- xgboost
- scikit-learn
- spark
- numpy
- matplotlib
- pandas
- huggingface_transformer
- 一、Tokenizer
- 二、Datasets
- 三、Model
- 四、Trainer
- 五、Evaluator
- 六、Pipeline
- 七、Accelerate
- 八、Autoclass
- 九、应用
- 十、Gradio
Scala
- 环境搭建
- 基础知识
- 函数
- 类
- 样例类和模式匹配
- 测试和注解
- 集合 collection(一)
- 集合collection(二)
- 集成 Java
- 并发
二、入门
2.1 基础数据类型
2.1.1 基础数据类型
Java
的基础类型和操作符在Scala
中具有相同的含义。Scala
的基础数据类型包括:String
、值类型(包括:Int/Long/Short/Byte/Float/Double/Char
)、Boolean
。Byte/Short/Int/Long/Char
类型统称为整数类型,整数类型加上Float
和Double
称作数值类型。String
位于java.lang
,其它几种基础数据类型位于scala
包。如Int
的完整名称是scala.Int
。由于
scala
源文件中默认自动引入了java.lang
包的所有成员和scala
包的所有成员,因此可以在任何地方使用这些类型的简单名字(而不是完整名称)。取值区间:
Byte
:8
位带符号整数。取值区间: $ [-2^7,2^7-1] $ ,闭区间。Short
:16
位带符号整数。取值区间: $ [-2^{15},2^{15}-1] $ ,闭区间。Int
:32
位带符号整数。取值区间: $ [-2^{31},2^{31}-1] $ ,闭区间。Long
:64
位带符号整数。取值区间: $ [-2^{63},2^{63}-1] $ ,闭区间。Char
:16
位无符号Unicode
字符。取值区间: $ [0,2^{16}-1] $ ,闭区间。String
:Char
的序列。Float
:32
位IEEE 754
单精度浮点数。Double
:64
位IEEE 754
双精度浮点数。Boolean
:true
或者false
。
Scala
基础数据类型和Java
中对应的类型取值区间完全相同,这使得Scala
编译器可以在生成的字节码中,将Scala
的值类型,如:Int/Double
的实例转换成Java
的基本类型。Scala
中的每个基础数据类型都有一个富包装类
,该富包装类提供额外的方法。当在基础数据类型调用更多的方法时,这些基础数据类型通过隐式转换得到对应的富包装类,并对富包装类调用这些方法。基础数据类型对应的富包装类为:
Byte -> scala.runtime.RichByte
、Short -> scala.runtime.RichShort
、Int -> scala.runtime.RichInt
、Long -> scala.runtime.RichLong
、Char -> scala.runtime.RichChar
、Float -> scala.runtime.RichFloat
、Double -> scala.runtime.RichDouble
、Boolean -> scala.runtime.RichBoolean
、String -> scala.collection.immutable.StringOps
。xxxxxxxxxx
0 max 5 // 结果:5 -1.0 abs // 结果:1.0 -2.7 round // 结果:-3L 1.5 isInfinity // 结果: false (1.0/0) isInfinity // 结果: true 4 to 6 // 结果: Range(4,5,6) "hello" capitalize // 结果: "Hello"
2.1.2 字面量
所有基础数据类型都可以用字面量
literal
来表示该类型的常量值。整数字面量:
Byte/Short/Int/Long
的字面量有两种形式:十进制表示和十六进制表示(以0x
或者0X
开头,包含0~9
以及大小的a~f
或者小写的A~F
)。整数字面量不支持八进制的表示,也不支持以
0
开头的表示(如012
)。Scala
的shell
总是以十进制打印整数值,无论它是用什么形式初始化的。如果整数字面量是以
l
或者L
结尾,则它是Long
类型的;否则就是Int
类型的。当一个
Int
类型的字面量赋值给一个Byte
或者Short
类型的变量时,该字面量会被当做Byte
或者Short
类型,只要这个字面量的值在变量类型的取值区间内即可。xxxxxxxxxx
val byte : Byte = 12 // Int 字面量被当作 Byte 类型 val short : Short = 123 // Int 字面量被当作 Short 类型
浮点数字面量:由十进制数字、可选的小数点、可选的
E
或者e
开头的指数组成(科学计数法)。如果浮点数字面量以
f
或者F
结尾,则它是Float
型的;否则就是Double
型的。Double
型浮点数字面量也可以以d
或者D
结尾,但是这不是必须的。xxxxxxxxxx
val float1 : Float = 1.1f val float2 : Float = 123E45F // 带指数 val double1 : Double = 1.1 val double2 : Double = 123E45 // 也可以显式添加 d 或者 D 结尾字符字面量:由一对单引号、任意一个
Unicode
字符组成。这里除了显式的给出原始字符,也可以用字符的Unicode
码来表示:\u
加上Unicode
码对应的四位十六进制数字。xxxxxxxxxx
val a1 = 'A' // 字符字面量 val a2 = '\u0041' // 使用 Unicode 码事实上,这种
Unicode
字符的方式可以出现在Scala
程序的任何位置,包括变量名中:xxxxxxxxxx
val \u0041B = 1 // 等价于 val AB=1但这种方式并不友好,因为不容易阅读。
还有一些字符字面量是特殊转义字符:
\n
: 换行符\u000A
。\b
:退格符\u000B
。\t
:制表符\u0009
。\f
:换页符\u000C
。\r
:回车符\u000D
。\"
:双引号\u0022
。\'
:单引号\u0027
。\\
:反斜杠\u005C
。
字符串字面量:由双引号包起来的字符组成。其中每个字符也可以用
Unicode
码表示,也支持转义字符。Scala
支持一种特殊语法来表示原生字符串raw string
:用三个双引号开始,并以三个双引号结束。其内部可以包含任何字符,包括换行、单双引号、以及其它特殊字符(三个双引号除外)。原生字符串对于包含大量转义字符、或者跨越多行的字符串比较友好。
xxxxxxxxxx
println("""This is a raw string: Contain "" and '' and \ and a new line and another new line\n""")Symbol
字面量:格式为'ident
,其中ident
可以是任何字母、数字组成的标识符。一个
Symbol
是一种特殊的字符串,相比较于String
类型,它更节省内存并且相等比较的速度很快。事实上
String
类内部维护一个字符串池strings pool
。当调用String
的intern()
方法时,如果字符串池中已经存在该字符串,则直接返回池中字符串对象的引用;如果不存在,则将该字符串添加到池中,并返回该字符串对象的引用。执行过intern()
方法的字符串被称作内部化了的,默认情况下代码中的字符串字面量都是内部化了的(在Java
中,字符串常量也是内部化了的)。同值字符串的intern()
方法返回的引用都相同。而在
Scala
中,Symbol
类型的对象是被内部化了的,任意同名symbol
都指向同一个Symbol
,因此节省了内存。由于不同名
symbol
一定指向不同的Symbol
对象,因此symbol
对象之间可以使用操作符==
快速的进行相等性比较,常数时间内即可完成。而字符串的equals
方法需要逐个字符的比较两个字符串,取决于两个字符串的长度。Symbol
类型一般用于快速比较,如Map<Symbol,Data>
查询Symbol
要比Map<String,Data>
查询String
快得多。Symbol
字面量会被编译器展开成一个工厂方法的调用:Symbol("ident")
。xxxxxxxxxx
val s = 'aSymbol // 等价于 Symbol("aSymbol")对于
Symbol
对象,你唯一能做的是获取它的名字:xxxxxxxxxx
println(s.name) // 输出:aSymbol
布尔值字面量:只有
true
和false
。
2.1.3 字符串插值
Scala
支持字符串插值:允许在字符串字面量中嵌入表达式。xxxxxxxxxx
val name="world" println(s"hello,$name!")其中表达式
s"hello,$name!"
称作processed
字符串字面量。由于s
出现在字符串首个双引号之前,因此Scala
将使用s插值器
来处理该字面量。s
插值器将对内嵌的每个表达式求值,对求值结果调用toString
来替代字面量中的那些表达式。在被处理的字符串字面量中,可以随时用美元符号
$
开启一个表达式。Scala
将从美元符号开始、直到首个非标识符的部分作为表达式。如果表达式包含非标识字符(如空格、操作符),则必须将其放入花括号{}
中,左花括号需要紧跟$
。scala
还提供另外两种字符串插值器:raw
字符串插值器:其行为跟s
字符串插值器类似,但是raw
字符串插值器并不识别转义字符。xxxxxxxxxx
println(raw"No new line\n") // \n 不经过转义f
字符串插值器:其行为跟s
字符串插值器类似,但是允许为内嵌的表达式添加printf
风格的格式化指令。格式化指令位于表达式之后,以百分号%
开始,使用java.util.Formatter
给出的语法。xxxxxxxxxx
println(f"${math.Pi}%.5f") // 输出:3.14159如果不给出格式化指令,则默认采用
%s
,其含义是用toString
的值来替换,就像s
字符串插值器一样。
在
Scala
中,字符串插值器是通过编译期间重写代码来实现的。编译器会将任何由某个标记(如r
或者f
)紧跟着字符串字面量的左双引号这样的表达式当作字符串插值器表达式求值。你也可以定义自己的字符串插值器来满足不同的需求。
2.2 变量定义
Scala
的变量分为两种:val
和var
。val
:和Java
的final
变量类似,一旦初始化就不能被重新赋值。注意:当采用
val
定义一个变量时,变量本身不能被重新赋值,但是它指向的对象可能发生改变。如:xxxxxxxxxx
val string1 = new Array[String](3) string1(0)="hello" string1(1)=", " string1(2)="world!\n"不能将
string1
重新赋值为另一个数组,但是可以改变string1
指向的数组的元素。var
:类似于Java
的非final
变量,在整个生命周期内可以被重新赋值。
var
和val
都有各自的用武之地,本质上并没有哪个更好或者更坏。如果代码中包含任何
var
变量,则它通常是非函数式的。如果代码中完全没有var
,则它可能是函数式的。因此函数式风格的编程尽量不使用var
。Scala
鼓励使用函数式编程,尽量采用val
。因为这样的代码更容易阅读、更少出错。采用
val
的另一个好处是等式推理equational reasoing
的支持。引入的val
等于计算它的值的表达式(假设这个表达式没有副作用)。因此任何该val
变量名出现的地方,都可以直接用对应的表达式替代。Scala
的变量定义(以val
为例):xxxxxxxxxx
val msg:String = "Hello word!"这中定义方式显式给出了类型标注,方式为:在变量名之后添加冒号和类型 。
实际上
String
的完整形式为java.lang.String
。因为Scala
程序默认引入了java.lang
包,因此可以直接写作String
。由于
Scala
的类型推断可以推断出非显式指定的类型,因此上述定义可以修改为:xxxxxxxxxx
val msg = "Hello world!"这样的代码更紧凑、易读。
2.3 标识符
构成
Scala
标识符的两种最重要的形式:字母数字组合、操作符。字母数字组合标识符:以字母或者下划线开始,可以包含更多的字母、数字或下划线。
字符
$
也算字母,但是它预留给那些由Scala
编译器生成的标识符。Scala
遵循了Java
的驼峰命名法camel-case
的传统。如:toString、HashSet
。- 字段、方法参数、局部变量、函数的命名应该以小写字母开始。
- 类、特质的命名应该以大写字母开始。
虽然下划线是合法的标识符,但是它们在
Scala
中并不常用,原因有两个:一个原因是和Java
保持一致。另一个原因是,下划线在Scala
中还有很多其它非标识符的用法。在标识符结尾尽量不要使用下划线。如:
xxxxxxxxxx
val name_: Int = 1 // 错误 val name_ : Int = 1 // 正确第一行将被
Scala
识别为变量名name_:
,这会引起编译错误。Scala
中,常量命名只要求首字母大写,而Java
中要求全大写而且下划线分隔不同的单词。Scala
中的常量并不是val
。如:方法的参数是val
,但是每次被调用时,这些val
都得到不同的值。
操作符标识符:由一个或者多个操作符组成。操作符指的是那些可以被打印的
ASCII
字符,如+,:,?,&
。Scala
编译器会在内部将操作符标识符用内嵌的$
方式转换为合法的Java
标志符。如::->
被转换成$colon$minus$greater
。如果你希望从Java
代码中访问这些标识符,则需要使用这种内部形式。混合标识符:由一个字母数字组合操作符、一个下划线、一个符号操作符组成。如:
unary_+
用于表示类的+
操作符的方法名。字面标识符:用反引号括起来的任意字符串。
可以将任何能被
runtime
接收的字符串放在反引号当中,甚至当该字符串是Scala
保留字时也生效。如:xxxxxxxxxx
val `val` = "hello" // val 是个保留字
2.4 操作符
Scala
为基础数据类型提供了一组丰富的操作符,但这些操作符其实只是普通方法调用的语法糖
。如:1+2
实际上是1.+(2)
,它调用的是Int
类的一个名为+
的方法,该方法接收一个Int
参数并返回一个Int
结果。xxxxxxxxxx
val int = 1+2 // 等价于 1.+(2)实际上
Int
包含多个重载的+
方法,这些方法分别接收不同的参数类型。在
Scala
中,操作符表示法不仅仅局限于那些其它语言(如Java/Python
)中看起来像是操作符的那些方法,也可以包括任何方法。即:任何方法都可以是操作符。如:xxxxxxxxxx
val s = "hello world" val idx = s.indexOf('h') // 标准的方法调用 val idx2 = s indexOf 'h' // 操作符表示法如果方法的参数有多个,则在操作符表示法中需要将这些参数都放在圆括号里。
xxxxxxxxxx
val idx3 = s indexOf ('o',3) // 从第3个位置开始查找操作符方法虽然方便使用,但是不能滥用。过度使用操作符方法会使得代码难于阅读和理解。
Scala
将从数组到表达式的一切都视为带方法的对象来处理,从而实现了概念上的简化。这种统一描述并不会带来显著的性能开销,因为Scala
在编译代码时,会尽可能使用Java
数组、基本类型和原生的算术指令。如,
Scala
的数组Array
的访问方式是:将下标放置在圆括号里,如:string1(0)
。这一点与Java/C++/Python
都不同。在
Scala
中,当用一个圆括号包围一组值应用到某个对象上时,将调用该对象的.apply()
方法。因此string1(0)
等价于string1.apply(0)
。因此在
Scala
中访问数组的一个元素就是一个简单的方法调用,并没有任何特殊的地方。在
Scala
中,当用一个圆括号包围一组值应用到某个对象上并位于赋值=
的左侧时,将调用该对象的.update()
方法。因此string1(0)="hello"
等价于string1.update(0,"hello")
。
2.4.1 前/中/后缀操作符
像
+
这类操作符是中缀操作符,这意味着被调用的方法名位于调用对象和参数之间。如:1+2
。Scala
还提供了另外两类操作符:- 前缀操作符:方法名位于调用对象的前面。如:
-1
。 - 后缀操作符:方法名位于调用对象的后面。如:
1 toLong
。
- 前缀操作符:方法名位于调用对象的前面。如:
跟中缀操作符表示法不同,前缀操作符和后缀操作符是一元的:它们只接受一个操作元。前缀操作符中,操作元位于操作符右侧;后缀操作符中,操作元位于操作符左侧。
唯一能被用作前缀操作符的是
+,-,!,~
。前缀操作符对应的完整方法名是
unary_
加上操作符(注意:不包含圆括号)。如:xxxxxxxxxx
-1 // 前缀操作符 1.unary_- // 方法调用的形式
后缀操作符是那些不接收参数,并且在调用时没有用
.()
的方法。在Scala
中,可以在方法调用时省去空的圆括号。但是通常来讲,如果方法有副作用,则需要保留空的圆括号。如:
println()
。如果方法没有副作用,则可以省略空的圆括号。Scala
支持进一步去掉句点.
,从而演化为后缀操作符表示法。xxxxxxxxxx
val s = "Hello world" val s1 = s.toLowerCase() // 标准调用 val s2 = s.toLowerCase // 省略空的圆括号 val s3 = s toLowerCase // 后缀操作符表示法
2.4.2 各类操作符
算术操作符:
+,-,*,/,%
:加、减、乘、除、取余。它们都是中缀操作符,对任何数值类型调用对应的算术方法。当左右两个操作元都是整数类型时,
/
操作符会计算出商的整数部分,不包含余数。%
操作符得到整数除法后的余数。%
用于浮点数除法时,其余数与IEEE 754
标准不同。IEEE 754
的余数在计算时用四舍五入,而%
是截断。如果需要
IEEE 754
的余数,则采用scala.math.IEEEremainder()
方法。xxxxxxxxxx
11.0%4.0 // 结果:3.0 math.IEEEremainder(11.0,4.0) // 结果:-1.0
Scala
对数值类型还提供了+,-
两个一元前缀操作符(unary_+
方法和unary_-
方法),用于表示数值型字面量是正数还是负数。- 如果不给出
+,-
,则数值字面量默认为正数。 - 一元
+
仅仅是为了和一元-
对称,它没有任何作用。 - 一元
-
不仅可以作用于数值字面量,还可以作用于变量,用来给变量取负值。
- 如果不给出
Java
的++i,i++,--i,i--
在Scala
中并不工作。在Scala
中你可以使用:i = i+1
或者i+=1
表示自增,i = i-1
或者i-=1
表示自减。关系操作符:
>,<,>=,<=
:大于、小于、大于等于、小于等于。它们都是中缀操作符,用于比较数值类型的大小,返回Boolean
结果。一元前缀操作符
!
(unary_!
方法) ,用于对Boolean
值取反。逻辑操作符:
&&,&,||,|
:逻辑与,逻辑与,逻辑或,逻辑或。它们都是中缀操作符,用于对Boolean
操作元执行逻辑与/或,返回Boolean
结果。&&
和||
是短路求值的:只会对结果有决定性作用的部分求值。当操作符左侧的操作元能够决定表达式的结果时,右侧的操作元不会被求值。&
和|
是非短路求值的:它们会对所有的操作元进行求值。
xxxxxxxxxx
true || (1/0 >=0) // 返回 true,短路求值。右侧操作元不会被求值 true | (1/0 >=0) // runtime error。 右侧操作元会被求值在
Scala
中,所有方法都有一个机制来延迟对入参的求值,或者干脆不对其求值。这个机制叫做传名参数by-name parameter
。位运算符:
&,|,^
:按位与、按位或、按位异或。它们都是中缀操作符,用于对整数类型执行位运算。一元前缀操作符
~
(unary_~
方法) ,用于对操作元的每一位取反。位运算符:
<<,>>,>>>
:左移,右移,无符号右移。它们都是中缀操作符,用于将整数左移或者右移。左移和无符号右移会将空出的位自动填0,右移会将空出的位自动填上符号位(最高位)。
xxxxxxxxxx
-1 >> 31 // 结果: -1 -1 >>> 31 // 结果: 1 1 << 2 // 结果:4相等运算符:
==,!=
:相等比较、不等比较。它们都是中缀操作符,用于比较两个对象是否相等。这两个操作符实际上可以应用于所有对象,而不仅仅是数值类型。
可以比较不同类型的两个对象,甚至可以和
null
比较。背后的规则很简单:- 首先检查左侧是否为
null
,如果不是null
,则调用左侧对象的equals
方法。 - 如果左侧是
null
,则检查右侧是null
。
由于
Scala
有自动的null
检查,你不必亲自做这个检查。- 首先检查左侧是否为
在
Java
中,可以用==
来比较基本类型和引用类型。对于基本类型,==
比较的是值的相等性;对于引用类型,==
比较的是引用的相等性。而
Scala
中,==
对基本类型和引用类型都比较的是值的相等性。Scala
提供了eq
和ne
来用于比较引用相等性。
2.4.3 优先级和结合性
操作符优先级:决定了表达式中哪些部分优先求值。当然你也可以通过圆括号来指定求值顺序。
Scala
中的操作符仅是用操作符表示法来使用对象的方法而已,它根据方法名的首个字母来判定优先级。Scala
的操作符优先级(依次递减):(所有其它特殊字符)
、*,/,%
、+,-
、:
、=,!
、<,>
、&
、^
、|
、(所有字母)
、(所有赋值操作符)
。- 位于同一级的操作符具有相同的优先级。
- 操作符优先级查看的是该操作符打头的字符,如
&&
的优先级查看的是&
字符,<<
的优先级查看的是<
字符。 - 一个例外是赋值操作符,它们以
=
结尾,且不是比较操作符(不是<=,>=,!=
),它们的优先级和简单的赋值操作符=
相同。即:*=
优先级不是由*
决定,而是由=
决定。
操作符结合性:当多个同等优先级的操作符并排时,操作符的结合性决定了操作符的分组。
Scala
中,操作符的结合性由操作符的最后一个字符决定。任何以:
字符结尾的方法都是在它右侧的操作元上调用的,传入左侧的操作元;任何以其它字符结尾的方法都是在它左侧的操作元上调用的,传入右侧的操作元。如
a*b
等价于a.*(b)
,而a ::: b
等价于b.:::(a)
。不论操作符的结合性是哪一种,其操作元的求值顺序都是从左到右。
如:
a ::: b
等价于:xxxxxxxxxx
val x = a // 优先求值 val y = b y.:::(x) // 操作元求值顺序:从左到右
一个良好的编码风格是清晰的表达什么操作符被用在什么表达式上。你唯一可以放心的让其它程序员不查文档就能知道的优先级顺序是:乘除比加减优先级更高。因此通常添加圆括号
()
来显式的呈现表达式的优先级。
2.5 内建控制结构
Scala
只有很少的内建控制结构:if,while,for,try,match
和函数调用。Scala
所有的控制结构都有返回值,如:if、for
等结构都有返回值。这是函数式语言采取的策略:程序被认为是用于计算出某个值,因此程序的各组成部分也应该计算出某个值。
2.5.1 if
if
控制结构:首先测试某个条件,然后根据条件是否满足来执行两个不同分支中的一个。if
表达式的返回值就是被选中分支的值。xxxxxxxxxx
val s = "hello word" if(!args.isEmpty) s = args(0) val s = if(!args.isEmpty) args(0) else "hello word" // 等价形式
2.5.2 while
while
控制结构:包含了一个条件检查和一个循环体,只要条件检查为真则循环体继续执行。Scala
也有do while
循环,它跟while
循环类似,只是它会首先执行循环体然后再执行条件检查。while
和do-while
并不会返回一个有意义的值,即返回类型为Unit
。在
Scala
中赋值表达式的结果是Unit
,而Java
中赋值表达式的结果是被赋予的值。因此下面的做法在Scala
中是不可行的:xxxxxxxxxx
var line = "" while ((line=readLine())!="") println("read: "+line)由于
line=readLine()
返回Unit
,因此Unit!=""
永远成立,则循环体永远执行。
通常
while
循环是和var
成对出现的。由于while
循环没有返回值(或者说返回Unit
),它要想对程序产生任何效果则要么更新一个var
要么执行I/O
。因此,对于代码中的
while
循环尽量采用其它方案来替代,除非确实难以替代它。对于
while
循环通常可以用递归函数的方式来替代。xxxxxxxxxx
// while 循环版本 var i = 0 var found = false while (i<args.length && !found) { if (!args(i).startsWith("-")){ if (args(i).endsWith(".scala")) find = true } i += 1 } // 递归函数版本 def search(i:Int):Int = { if (i>=args.length) -1 else if (args(i).startsWith("-")) search(i+1) else if (args(i).endsWith(".scala")) i else search(i+1) } var i = search(0)这个递归函数比较特殊:所有的递归调用都发生在函数尾部,因此称作尾递归。编译器会将尾递归展开成和
while
循环类似的代码。
2.5.3 for
for
表达式:Scala
中的for
表达式有很多功能。最简单的功能是遍历集合的所有元素。
xxxxxxxxxx
val files = (new java.io.File(".")).listFiles for (file <- files) // file 是 val println(file)通过
file <- files
这样的生成器语法,我们将遍历files
的每个元素。每次迭代时,一个新的、名为file
的val
都被初始化成files
中一个元素的值。你也可以遍历一个索引。
xxxxxxxxxx
for (i <- 1 to 4) println(i)1 to 4
将生成一个区间Range
,范围是[1,4]
(闭区间)。如果希望得到一个左闭右开区间,则使用1 until 4
。有时需要遍历集合中的部分元素,而不是全部。此时可以在
for
中添加过滤器。形式为:for
表达式的圆括号中添加if
子句。可以包含任意多的过滤器,直接添加
if
子句即可。xxxxxxxxxx
val files = (new java.io.File(".")).listFiles for ( file <- files if file.isFile if file.getName.endsWith(".scala") ) // 添加过滤器 println(file)也可以添加多个
<-
子句,此时得到嵌套的“循环”。如果愿意,你也可以使用花括号
{}
而不是圆括号()
来包括生成器和过滤器,好处是可以在需要时省略某些分号。因为Scala
编译器在圆括号中并不会自动推断分号。x def getLines(file:java.io.File) = scala.io.Source.fromFile(file).getLines().toList val files = (new java.io.File(".")).listFiles for ( file <- files if file.getName.endsWith(".scala") ; //必须添加分号 ; line <- getLines(file) if line.trim.matches("test*.scala") ) // 添加过滤器 println(file+":"+line.trim)
在
for
的生成器和过滤器中,支持中途变量绑定:将表达式的结果绑定到新的变量上。被绑定的变量引入和使用就跟val
一样。上述例子中,
line.trim
被重复调用两次。通过中途变量绑定可以只需要调用一次。xxxxxxxxxx
def getLines(file:java.io.File) = scala.io.Source.fromFile(file).getLines().toList val files = (new java.io.File(".")).listFiles for { file <- files if file.getName.endsWith(".scala") //采用大括号,所以不用添加分号 ; line <- getLines(file) trimmed = line.trim // 中途变量绑定,trimmed 初始化为 line.trim 的结果 if trimmed.matches("test*.scala") } // 添加过滤器 println(file+":"+trimmed)for
表达式可以返回有效的值。这是通过yield
关键字实现的。xxxxxxxxxx
val files = (new java.io.File(".")).listFiles def scalaFiles = for { file <- files if file.getName.endsWith(".scala") } yield filefor
表达式的代码体每次都被执行,都会产出一个值。当for
表达式执行完毕后,其结果将包含所有产出的值,包含在一个集合中。结果集合的类型基于迭代子句中处理的集合种类。这个例子中,每次产出的值就是file
,返回的集合类型为Array[File]
。for
表达式的代码体如果有多行表达式,则最后一个表达式的返回值就是该表达式代码体的结果。注意:
yield
关键字的位置是:for 子句 yield 代码体
。如果代码体由花括号{}
包围的,则yield
必须在花括号之前:xxxxxxxxxx
for (file <- files if file.getName.endsWith(".scala")) { yield file // 语法错误 } // 应该是:yield {file}
2.5.4 match
match
表达式:让你从若干个可选项中选择,就像其它语言中的switch
语句一样。但是match
表达式允许你使用任意的pattern
来选择。xxxxxxxxxx
val firstArg = if (args.length >0) args(0) else "" firstArg match { case "apple" => "apple" case "orange" => "orange" case _ => "unknown" }缺省的
case
以下划线_
来表示,这个通配符在Scala
中经常用于表示某个完全不知道的值。Scala
的match
与Java
的switch
相比有一些重要区别:- 任何常量、字符串等等都可以用作
case
,而不仅局限于Java
的case
支持的整数、枚举和字符串常量。 - 在每个
case
结尾并没有break
。在Scala
中,break
是隐含的,并不会出现某个case
执行结束之后继续执行下一个case
的情况。 Scala
的match
表达式会返回值。匹配到的case
的子句的结果就是match
表达式的返回值。
- 任何常量、字符串等等都可以用作
2.5.5 break
Scala
中并没有break
和continue
关键字,如果想实现对应的功能,最简单的方式是采用if-else
结构。如果仍然需要
break
功能,则scala.util.control.Breaks
类给出了break
方法,它可以被用于退出包含它的、用breakable
标记的代码块。xxxxxxxxxx
import scala.util.control.Breaks._ import java.io._ val in = new BufferedReader(new InputStreamReader(System.in)) breakable{ // 标记 while(true){ println("hello") if (in.readLine()=="") break // break } }其实现方式为:由
Breaks.break
抛出一个异常,然后由外围的breakable
方法的应用所捕获。因此,对break
的调用并不需要一定和breakable
的调用放在同一个方法内。
2.6 异常
Scala
的异常处理也和其它语言类似,方法除了正常返回某个值意外,还可以通过抛出异常来终止执行。方法的调用方要么捕获并处理这个异常,要么自我终止并让该异常传播到更上层调用方。异常通过这种方式传播,逐个展开调用栈,直到某个方法处理该异常或者再没有更多方法了为止。
Scala
中抛出异常与Java
看起来一样,你首先创建一个异常对象,然后通过throw
关键字将其抛出。xxxxxxxxxx
throw new IllegalArgumentException与
Java
不同,Scala
中的throw
是一个有返回类型的表达式。技术上来讲,throw
表达式的类型是Nothing
。因此可以将throw
表达式的值当作任何类型的值来看待,因为任何想使用这个返回值的地方都没有机会真正使用它。xxxxxxxxxx
val half = if (n%2 ==0) n/2 else throw new RuntimeException("n must be even")如果
throw
表达式没有返回值,则上述的if/else
结构无法通过编译。可以通过
catch
子句来捕获异常,其语法与Scala
的模式匹配相一致。xxxxxxxxxx
import java.io.FileReader import java.io.FileNotFoundException import java.io.IOException try { val f = new FileReader("input.txt") // 使用并关闭文件 } catch { case ex : FileNotFoundException => // 处理找不到文件的情况 case ex : IOException => // 处理其它IO的情况 }try-catch
表达式和其它语言一样:首先代码体被执行。如果抛出异常,则依次尝试每个catch
子句,执行第一个匹配的catch
子句。如果所有的子句都不匹配,则异常继续向上传播。在
scala
中,并不会要求你捕获checked exception
或者在throws
子句里声明,这和Java
不同。当然你也可以通过
@ throws
注解来声明一个throws
子句,但这不是必须的。可以将那些无论是否抛出异常都想执行的代码以表达式的形式包括在
finally
子句里。xxxxxxxxxx
import java.io.FileReader import java.io.FileNotFoundException import java.io.IOException try { val f = new FileReader("input.txt") // 使用并关闭文件 } catch { case ex : FileNotFoundException => // 处理找不到文件的情况 case ex : IOException => // 处理其它IO的情况 } finally { f.colse() // 确保关闭文件 }这是确保那些非内存资源(如:文件、套接字、数据库连接)被正确关闭的惯用做法:首先获取资源,然后在
try
块中使用资源,最后在finally
块中释放资源。这和Java
是一致的。和
Scala
中的大多数控制结构一样,try-catch-finally
最终返回一个值:如果没有异常抛出,则
try
子句的结果就是整个表达式的结果。如果子句是多行表达式,则最后一个表达式的结果就是整个表达式的结果。
如果有异常抛出,且被
catch
子句捕获,则该catch
子句的结果就是整个表达式的结果。如果有异常抛出,且没有被
catch
子句捕获,则整个表达式就没有结果。如果有
finally
子句,则该子句计算出来的值会被丢弃。因此该子句一般执行清理工作,且不应该改变主体代码或者catch
子句中计算出来的值。当
finally
子句包含一个显式的return
语句或者抛出某个异常,则该返回值或者异常会“改写”任何在之前的try
代码块或者catch
子句中产生的值。xxxxxxxxxx
def f1():Int = try return 1 finally return 2 // 调用 f1() 返回 2 def f2():Int = try return 1 finally 2 // 调用 f2() 返回 1
与
Scala
不同,Java
的try-finally
并不返回值。
2.7 表达式&语句
与
Java
一样,在Scala
中的while/if
语句中的boolean
表达式必须放在圆括号里,不能像python
一样写if x<0
。和
Java
一样,在Scala
中如果if
代码块只有一条语句,则可以选择不写花括号{}
。Scala
支持使用分号;
来分隔语句,但是Scala
通常都不写分号。Scala
推荐使用foreach
来代替while
循环,因为foreach
是函数式风格,而while
是指令式风格。如:xxxxxxxxxx
var i =0 while(i<args.length) { println(args(i)) i+=1 }采用
foreach
替代为:xxxxxxxxxx
args.foreach((arg:String) => println(arg))其中
(arg:String) => println(arg)
是一个函数字面量,可以进一步简化为:args.foreach(println)
。Scala
不支持for
循环语句,但是支持for
表达式。其用法为:xxxxxxxxxx
for (arg <- args ) println(arg)其中
arg
是一个val
变量,这确保它无法在循环体被被重新赋值。
2.8 输入输出
Console.in,Console.out,Console.err
分别为标准输入流、标准输出流、标准异常流对象。- 输出:
Console.out/err
的print/printf/println
等方法。 - 输入:
Console.in
的read/readLine
等方法。
- 输出:
scala.io.Source
类提供了文件IO
的方法。Source.fromeFile(filename)
:打开指定的文件并返回一个Source
对象。source.getLines()
:读取source
对象指向的文件,并返回一个迭代器Iterator[String]
。
2.9 作用域
Scala
的变量作用域和Java
几乎完全一样,一个区别是:Scala
允许你在嵌套的作用域内定义同名变量。花括号
{}
一般都会引入一个新的作用域,因此任何在花括号中定义的元素都会在右花括号}
之后离开作用域。函数中定义的所有变量都是局部变量。这些变量在定义它们的函数内部有效。函数每次被调用时,都会使用全新的局部变量。
变量一旦定义好,就不能在相同作用域内定义相同名字的变量。但是可以在嵌套的作用域内定义一个跟外部作用域中同名的变量。内嵌作用域中的变量会屏蔽外部作用域中的同名变量。
一个良好的编程习惯是:在内嵌作用域内选择一个新的、有意义的变量名,而不是和外部作用域中的变量同名。
在解释器中,可以随心所欲的使用变量名,理论上解释器会对你录入的每一条语句创建一个新的作用域。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论