数学基础
- 线性代数
- 概率论与随机过程
- 数值计算
- 蒙特卡洛方法与 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
- 并发
九、抽象成员
如果类或者特质的某个成员在当前类中没有完整的定义,则它就是抽象的。抽象的本意是为了让声明该成员的类的子类来实现。不过
Scala
走的更远,它将这个概念完全泛化:除了可以声明抽象方法之外,还可以声明抽象字段甚至抽象类型作为类或特质的成员。下面这个特质声明了四种抽象成员:抽象类型
T
、抽象方法transform
、抽象val
字段initial
、抽象var
字段current
。xxxxxxxxxx
trait Abstract{ type T def transform(x: T): T val initial : T var current : T }因此
Abstract
特质的具体实现需要填充每个抽象成员的定义。xxxxxxxxxx
class MyCls extends Abstract{ type T = String def transform(x: String) = x + x val initial = "hi" val current = initial }抽象类型
abstract type
成员:用type
关键字声明为某个类或者特质的成员、但是未给出定义的类型。注意:抽象类不等于抽象类型,抽象类型永远是类或者特质的成员。而抽象类指的是通过修饰符
abstract
定义的类。xxxxxxxxxx
trait Abstract{ type T // 抽象类型 ... } abstract class Element { // 抽象类 def contents: Array[String] }与抽象类型成员对应的是具体类型成员,具体类型成员可以认为是某个具体类型的别名。
xxxxxxxxxx
class MyCls extends Abstract{ type T = String // T 是 String 的别名 }使用类型成员的原因之一是:给名字冗长的类型、或者含义不清晰的类型一个简短的、含义明确的别名。
另一个原因是:声明子类必须定义的抽象类型(如这里的
type T
)。抽象的
val
成员:xxxxxxxxxx
val initial : String该声明给出了
val
的名称和类型,但是未给出具体值。这个值必须由子类中具体的val
定义提供。这种抽象成员的应用场景:不知道变量具体的值,但是明确的知道它在当前类的每个实例中都不可变时,可以采用抽象的
val
成员。采用抽象
val
成员可以有如下保证:每次对.initial
的引用都会交出相同的值。如果采用抽象的无参方法成员:
xxxxxxxxxx
def initial : String则每次方法调用无法给出这个保证。
因此抽象
val
限制了它的合法实现:每个子类的实现必须是由一个val
定义,而不能是def
或者var
。与此对比,抽象方法成员可以用具体的方法定义或者具体的
val
定义来实现。
xxxxxxxxxx
abstract class Fruit{ val v : String def m : String } class Apple extends Fruit{ val v : String = "apple" val m : String = "example" // 用 val 重写 def 是 OK 的 } class Orange extends Fruit{ def v : String = "apple" // 用 def 覆盖 val 是不允许的 val m : String = "example" }跟抽象的
val
成员类似,抽象的var
成员也只是声明了名称和类型,但是并不给出初始值。声明为类成员的
var
都默认带上了getter
方法和setter
方法,这对于抽象的var
成员也成立。xxxxxxxxxx
trait AbstractTime { var hour: Int var minute: Int }这里等价于:
xxxxxxxxxx
trait AbstractTime{ def hour: Int // hour 的 getter 方法 def hour_=(x: Int) // hour 的 setter 方法 def minute: Int // minute 的 getter 方法 def minute_=(x: Int) // minute 的 setter 方法 }
9.1 初始化抽象的 val
抽象
val
成员有时候会承担超类的参数化功能:它允许我们在子类中提供那些在超类中缺失的细节。这对于特质尤其重要,因为特质并没有让我们传入参数的构造方法。因此,通常对于特质的参数化是通过在子类中实现抽象
val
成员来实现的。如:
xxxxxxxxxx
trait RationalTrait{ // 有理数 val numerArg: Int // 分子 val denomArg: Int // 分母 }要实例化该特质的一个具体实例,需要实现抽象的
val
定义:xxxxxxxxxx
new RationalTrait{ val numerArg = 1 val denomArg = 2 }这个表达式交出的是一个混入了特质,并且由定义体 (由
{}
提供) 定义的匿名类anonymous class
的实例。它和以下代码效果类似:xxxxxxxxxx
class Rational(n: Int, d: Int){ val numer: Int = n val denom: Int = d } new Rational(1,2)但是二者有细微区别:
new Rational(expr1, expr2)
中,expr1
和expr2
这两个表达式会在Rational
类初始化之前被求值,因此expr1
和expr2
的值在Rational
类初始化过程中是可见的、已知的。对于特质来讲,情况不同。
xxxxxxxxxx
new RationalTrait{ val numerArg = expr1 val denomArg = expr2 }expr1
和expr2
这两个表达式作为匿名类初始化过程的一部分被求值的,但是匿名类是在RationalTrait
特质之后被初始化的。因此在RationalTrait
初始化过程中,numerArg, demoArg
的值不可用。更准确的说,对于这两个值当中任何一个的选取都会交出类型Int
的默认值 0 。对于这里的例子,似乎并没有什么问题,因为特质的初始化过程并没有用到
numerArg
和denomArg
。但是对于下面的示例,却很致命:xxxxxxxxxx
trait RationalTrait{ // 有理数 val numerArg: Int // 分子 val denomArg: Int // 分母 require(denomArg != 0) // 前置条件 private val g = gcd(numerArg, denomArg) // 最大公约数 val numer = numerArg / g // 归一化分子 val denom = denomArg / g // 归一化分母 private def gcd(a:Int, b:Int) : Int= if(b == 0 ) a else gcd(b, a % b) // 求最大公约数 override def toString = numer + "/" + denom // 打印 } val x = 2 new RationalTrait{ val numerArg = 1 * x val denomArg = 2 * x }在初始化匿名类之前,编译器先初始化特质,此时
1*x
和2*x
尚未求值,因此numerArg,denomArg
为默认值0
,因此特质的require
前置条件不满足。这个例子说明:类参数和抽象字段的初始化顺序并不相同。类参数在传入类构造方法之前被求值(传名参数除外),而在子类中实现的
val
抽象成员则在超类初始化之后被求值。为解决后者的问题,
scala
提供了两种方案:预初始化字段pre-initialized field
和lazy
惰性的val
。
预初始化字段
pre-initialized field
:在超类被调用之前就初始化子类的字段。只需要在把字段定义放在超类的构造方法之前的花括号中即可。xxxxxxxxxx
val x = 2 new { val numerArg = 1 * x val denomArg = 2 * x } with RationalTrait初始化代码段出现在超类特质
RationalTrait
之前,用with
隔开。预初始化字段不仅局限于匿名类,也可以用于对象或者具名子类中。
xxxxxxxxxx
object ABC extends{ // 用于对象中 val numerArg = 2 val denomArg = 3 } with RationalTrait object MyClass(n: Int, d: Int) extends{ // 用于具名子类 val numerArg = n val denomArg = d } with RationalTrait{ def + (that: MyClass) = new MyClass( .... ) }由于预初始化字段在超类的构造方法被调用之前初始化,因此它们的代码不能引用那个正在被构造的对象。因此,如果这样的初始化代码使用了
this
,那么这个引用将指向包含当前被构造的类或对象的对象,而不是被构造的对象本身。xxxxxxxxxx
new { val numerArg = 2 val denomArg = 3 * this.numerArg // 错误, this 指向的不是当前对象,因此没有 numerArg 属性 } with RationalTrait这个例子无法编译,因为
this.numerArg
引用的是包含了new
的对象,在解释器中对应于名为$iw
的合成对象。预初始化字段这方面的行为类似于类构造方法的入参行为。我们可以通过预初始化字段来精确模拟类构造方法入参的初始化行为,但是有时候我们希望系统能自己搞定初始化顺序。
可以将
val
定义为惰性的来实现。如果在val
定义之前添加lazy
修饰符,则右侧的初始化表达式只会在val
第一次被用到时求值。xxxxxxxxxx
object Demo{ lazy val x = {println("initialize x"); "done"} }这里
Demo
的初始化不涉及对x
的初始化。对x
的初始化延迟到第一次访问x
的时候。这和将
x
用def
定义成无参方法的情况类似,区别在于:不同于
def
,惰性val
永远不会被求值多次,只会被求值一次。事实上对惰性
val
首次求值之后其结果会被保存起来,在后续的使用中都会复用该值。def
每次使用时都会被求值。
因此一种修改方案为:
xxxxxxxxxx
trait RationalTrait{ // 有理数 val numerArg: Int // 分子 val denomArg: Int // 分母 lazy val numer = numerArg / g // 归一化分子 lazy val denom = denomArg / g // 归一化分母 private lazy val g = { require(denomArg != 0) // 前置条件 gcd(numerArg, denomArg) // 最大公约数 } private def gcd(a:Int, b:Int) : Int= if(b == 0 ) a else gcd(b, a % b) // 求最大公约数 override def toString = numer + "/" + denom // 打印 } val x = 2 val r = new RationalTrait{ val numerArg = 1 * x val denomArg = 2 * x } println(r)当
RationalTrait
匿名类的一个对象被创建时,RationalTrait
的初始化代码被执行。此时RationalTrait
的初始化代码仅初始化numerArg
和denomArg
。一旦后续调用
println(r)
,则调用对象的toString
方法。该方法用到denom
这个lazy val
于是denom
被求值。而denom
用到了lazy val g
,于是g
被求值。尽管
g
出现在numer, denom
之后,但是它在numer, denom
初始化之前初始化。因此对于惰性val
,其初始化顺序和定义顺序无关。这个优势仅在惰性的val
的初始化既不产生副作用、也不依赖副作用时有效。在有副作用参与时,初始化顺序就变得相当重要。这时候跟踪惰性
val
的初始化顺序非常困难。因此惰性val
在函数式编程的使用场景比较合适,因为函数式对象的初始化顺序不重要。
9.2 抽象类型
跟其它所有抽象声明一样,抽象类型声明是某种将会在子类中具体定义的东西的占位符:在类继承关系的下游中将被定义的类型,不同的子类可以提供不同的类型实现。
下面的例子并不能编译通过:
xxxxxxxxxx
class Food abstract class Animal{ def eat(food: Food) } class Grass extends Food class Cow extends Animal{ override def eat(food: Grass) ={} // 编译不通过 }实际上
Cow
类的eat
方法并没有重写Animal
类的eat
方法,因为它们的参数类型不同:一个是Food
另一个是Grass
,虽然Grass
是Food
子类。事实上如果上述代码能给通过编译,则容易写出下面的代码:
xxxxxxxxxx
class Fish extends Food val abc: Animal = new Cow abc.eat(new Fish)这明显是不合理的。
可以通过抽象类型来实现这段逻辑:
xxxxxxxxxx
class Food abstract class Animal{ type T <: Food def eat(food: T) }这里
Animal
只能吃那些适合它吃的食物。至于什么食物是合适的,并不能在Animal
类这个层级确定。这就是为什么T
定义为一个抽象类型。这个抽象类型有个上界Food
,以<: Food
表示。这意味着Animal
每个子类对于T
的实例化都必须是Food
的子类。xxxxxxxxxx
class Grass extends Food class Cow extends Animal{ type T = Grass override def eat(food: Grass) = {} }
9.3 路径依赖类型
路径依赖类型的语法跟
Java
的内部类类型相似,不过有个重要区别:路径依赖类型用的是外部对象的名称,内部类用的是外部类的名称。在Scala
中,内部类的寻址是通过Outer#Inner
这样的表达式而不是Java
的Outer.Inner
,Scala
的.
语法只为对象保留。xxxxxxxxxx
class Outer{ class Inner } val o1 = new Outer val o2 = new Outero1.Inner
这样的类型称作路径依赖类型path-dependent type
。这里的路径指的是对对象的引用,它可以是一个简单的名称,比如o1
,也可以是更长的访问路径。一般不同的路径(这里是不同的对象)催生出不同的类型,如
o1.Inner
和o2.Inner
是两个路径依赖的类型(它们是不同的类型),这两个类型都是Outer#Inner
类的子类型。o1.Inner
指的是特定外部对象(即o1
引用的那个对象)的Inner
类。o2.Inner
指的是特定外部对象(即o2
引用的那个对象)的Inner
类。
跟
Java
一样,Scala
的内部类的实例会保存一个到外部类实例的引用。这允许内部类访问外部类的成员。因此我们在实例化内部类的时候必须以某种方式给出外部类实例。一种方式是在外部类的定义体中实例化内部类。此时通过
this
来访问外部类实例本身。另一种方式是采用路径依赖类型。如
o1.Inner
这个类型是一个特定于外部对象,我们可以实例化它:xxxxxxxxxx
new o1.Inner得到的内部对象将包含一个指向其外部对象(即
o1
)的引用。于此对应,由于
Outer#Inner
并没有指明Outer
的特定实例,因此不能创建它的实例。
9.4 改良类型
当类从另一个类继承时,将前者称为后者的名义
nominal
子类。之所以称作nomial
,是因为每个类型都有一个名称,而这些名称被显式声明为存在子类关系。除此之外
Scala
还支持结构structural
子类,即:只要两个类型有兼容的成员就可以说它们之间存在子类关系。Scala
实现结构子类的方式是改良类型refinement type
。名义子类通常更方便使用,因此应该在任何新的设计中优先尝试名义子类。
但是结构子类灵活性更高。比如,希望定义一个包含食草动物的 ”牧场“类:
xxxxxxxxxx
class Pasture { var animals: List[Animal {type SuitableFood = Grass}] = Nil }这里的改良类型只需要写基类型
Animal
,然后加上一系列用花括号括起来的成员即可。花括号中的成员进一步指定(或者说改良)了基类中的成员类型。
9.5 枚举
别的语言,如
Java,C#
都有内建的语法结构来支持枚举类型,而Scala
不需要特殊的语法来支持枚举。这是通过路径依赖类型来实现的。Scala
在标准库中提供了一个类scala.Enumeration
,可以通过定义一个扩展自该类的对象来创建新枚举。xxxxxxxxxx
object Color extends Enumeration { val Red = Value val Green = Value val Blue = Value }Scala
还允许我们用同一个右侧表达式来简化多个连续的val
或者var
定义,上述定义等价于:xxxxxxxxxx
object Color extends Enumeration { val Red,Green,Blue = Value }这个对象定义了三个值:
Color.Red,Color.Green,Color.Blue
。Enumeration
定义了一个名为Value
的内部类,跟这个内部类同名的、不带参数的Value
方法每次都返回该类的全新实例。因此类似Color.Red
的类型为Color.Value
,而Color.Value
是所有定义在Color
对象中的Value
的类型。这里面的关键点在于:这是一个完全的新类型,不同于其它所有类型。
因此如果我们定义了另外一个枚举类型:
xxxxxxxxxx
object Direction extends Enumeration { val North, East, South, West = Value }那么
Direction.Value
将不同于Color.Value
,因为这两个类型的路径部分是不同的。Scala
的Enumeration
类型还提供了其它编程语言的枚举不支持的其它功能。可以用一个重载的
Value
方法给枚举值关联特定的名称:xxxxxxxxxx
object Color extends Enumeration { val Red = Value("Red") val Green = Value("Green") val Blue = Value("Blue") }可以通过枚举的
values
方法返回的Set
来遍历枚举的Value
:xxxxxxxxxx
for (c <- Color.values) print(c + " ") // 打印结果: Red Green Blue枚举的值从
0
开始编号,可以通过枚举Value
的id
方法获取编号:xxxxxxxxxx
println(Color.Red.id) // 打印结果: 0可以从一个非负整数编号获取对应的枚举值:
xxxxxxxxxx
val c = Color(1) // c 为: Color.Green
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论