数学基础
- 线性代数
- 概率论与随机过程
- 数值计算
- 蒙特卡洛方法与 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
- 并发
五、Trait
Scala
和Java
中,不支持多重继承,任何一个类都继承自单个父类。C++
支持多重继承,一个类可以继承自多个父类。为了实现类似多重继承的效果,
Java
采用接口interface
来实现,而Scala
采用特质trait
来实现。trait
是Scala
代码复用的基础单元:trait
将方法、字段封装起来,然后通过将它们混入mix in
类的方式来实现复用。特质很像
Java
接口,但是特质比接口更强大:它不仅可以定义方法,还可以定义字段来保持状态。通过
trait
关键字来定义特质,其定义除了关键字不一样,其它都和类的定义相同。xxxxxxxxxx
trait T { def f = println("This is a trait") }在特质定义中你可以做任何在类定义中做的事,语法也完全相同。除了以下两种情况:
特质不能有任何“类”参数,即:那些传入类的主构造方法的参数。
你可以这样定义一个类:
class C:(x:Int,y:Int)
,但是无法这样定义一个特质:trait C:(x:Int,y:Int)
。类中的
super
调用是静态绑定的,而在特质中super
是动态绑定的。在类中编写
super.toString
这样的代码时,你可以明确知道实际调用的是哪个实现。在特质中编写
super.toString
这种代码时,你无法知道实际调用的是哪个实现,因为定义特质时super
并没有被定义。具体哪个实现被调用,在每次该特质混入到某个具体类时,都会重新判定。
这种
super
的奇怪行为是特征能够实现可叠加修改stackable modification
的关键。
特质也有继承关系,如果未明确给出超类,则它默认继承自
AnyRef
。一旦定义好特质,就可以通过
extends
或者with
关键字将其混入mix in
到类中。一般是混入特质,而不是继承特质。因为混入和继承有很多重要区别。
可以通过
extends
来混入特质,这种情况下,类会隐式的继承了特质的超类。从特质继承的方法跟从超类继承的方法用起来一样。xxxxxxxxxx
class Parent{ def f1 = println("This is Parent class") } trait T extends Parent{ def f2 = println("This is a trait") } class Child extends T{ // Child 隐式继承了 Parent,Child 还可以使用 T 的方法 def f3 = println("This is Child class") } val c = new Child() c.f1 // 打印:This is Parent class c.f2 // 打印:This is a trait c.f3 // 打印:This is Child class同时特质也定义了一个类型,但是有几个约束:
- 不能直接
new
一个特质。 - 只能使用特质中定义、以及特质从它的超类中继承而来的字段和方法。此时也能够支持多态和动态绑定。
xxxxxxxxxx
val c: T = new Child() // c 的类型是 T c.f3 // 编译失败,因为 T 没有 f3 成员 c.f2 // 打印:This is a trait c.f1 // 打印:This is Parent class val t = new T() // 编译失败,无法直接 new 一个特质- 不能直接
如果类已经通过
extends
继承自某个超类,如果还希望混入特质,则通过with
关键字。xxxxxxxxxx
class Parent{ def f1 = println("This is Parent class") } trait T { def f2 = println("This is a trait") } class Child extends Parent with T { // Child 显式继承了 Parent,并混入了 T def f3 = println("This is Child class") } val c = new Child() c.f1 // 打印:This is Parent class c.f2 // 打印:This is a trait c.f3 // 打印:This is Child class如果希望混入多个特质,则可以通过多个
with
子句添加。- 如果显式继承自父类,则父类采用
extends
,每个特质一个with
子句。 - 如果没有显式继承,则第一个特质采用
extends
,后面每个特质一个with
子句。
xxxxxxxxxx
class Parent{ def f1 = println("This is Parent class") } trait T1 { def f2 = println("This is a trait1") } trait T2 { def f3 = println("This is a trait2") } class Child extends Parent with T1 with T2 { // Child 显式继承了 Parent,并混入了 T1,T2 def f4 = println("This is Child class") }- 如果显式继承自父类,则父类采用
类可以重写特质的成员,语法和重写超类中的成员一样。
特质的一个主要用途是:自动给类添加基于已有方法的新方法。即:特质可以丰富一个瘦接口,使其成为一个富接口。
瘦接口和富接口代表了面向对象设计中经常面临的取舍,在接口实现方和接口使用方之间的权衡。
- 富接口有很多方法,对调用方很方便,但是实现方需要做更多的工作。
- 瘦接口方法较少,因此实现起来更容易,但是需要调用方编写更多的代码。
Java
通常采用瘦接口,而Scala
倾向于采用富接口。因为Scala
可以通过给特质添加具体方法的形式,而这种投入是一次性的。你只需要在特质中实现这些方法一次,然后混入类中。你不需要在每个混入该特质的类中重新实现一遍。因此和没有特质的语言相比,
Scala
中实现富接口的代价更小。要用特质来实现富接口,只需要定义一个拥有为数不多的抽象方法(接口中的瘦的部分)和可能数量很多的具体方法(这些具体方法基于那些抽象方法编写)的特质。然后你可以将这个特质混入到某个类,在类中实现接口中瘦的部分,最终得到一个拥有完整富接口实现的类。
xxxxxxxxxx
abstract trait T{ /***** 瘦接口部分:抽象 *****/ def interface1(n:Int):Int def interface2(name:String):String /***** 富接口部分:具体 *****/ def f1(m:Int) = {/* 调用 interface1 和 interface2 的具体实现 */} def f2(m:Int,n:String) = {/* 调用 interface1 和 interface2 的具体实现 */} } class C extends T{ // C 拥有了富接口 /****** 实现瘦接口 ******/ override def interface1(n:Int) = {/* 具体实现 */} override def interface2(name:String) = {/* 具体实现 */} }当需要实现某个可复用的行为集合时,都需要决定是用特质还是抽象类。有一些参考意见:
如果某个行为不会被复用,则用具体的类。
如果某个行为可能被用于多个互不相关的类,则用特质。
如果想要从
Java
代码中继承某个行为,用抽象类。从
Java
类继承和从Scala
类继承几乎一样,唯有一个例外:如果某个Scala
只有抽象方法,则它会被翻译成Java
的接口。如果计划将某个行为以编译好的形式分发,且预期会有外部的组织编写继承自它的类,则倾向于使用抽象类。因为当某个特质增加或者减少成员时,任何继承自该特质的类都需要被重新编译,哪怕这些类并没有任何改变。
如果外部的使用方只是调用到这个行为,而没有继承,则特质也是可以的。
如果没有任何线索,则推荐从特质开始。因为特质能让你保留更多选择。
许多有经验的
Scala
程序员都在实现的初期采用特质。每个特质可以描述整个概念的一小块。随着设计逐步固化和稳定,这些小块可以通过特质混入,被组合成更完整的概念。
5.1 Ordered Trait
特质的一个应用场景是比较大小。通常为了支持大小比较操作,我们会定义一些方法:
xxxxxxxxxx
class C { def < (that:C) = {/* 小于比较 */ } def > (that:C) = that < this def <= (that:C) = (this < that) || (this == that) def >= (that:C) = (this > that) || (this == that) }实际上这个场景太普遍,因此
Scala
提供了专用的特质来解决,这个特质叫Ordered
。Ordered
特质定义了<,>,<=,>=
,你需要实现compare
方法,而这些方法将基于compare
方法来实现。因此
Ordered
特质允许你通过只实现一个compare
方法来增强某个类,从而使其具有完整的比较操作。xxxxxxxxxx
class C(n:Int) extends Ordered[C]{ val num = n def compare(that:C) = this.num - that.num }Ordered
特质要求混入时提供一个类型参数,如这里的Ordered[C]
,其中C
是你要比较元素的类。compare
方法用于比较调用者(这里是this
),和传入对象(这里是that
)。- 如果二者相等,则返回 0 。
- 如果调用者较小,则返回负值。
- 如果调用者较大,则返回正值。
注意:
Ordered
特质并不会帮你定义equals
方法,因为它做不到。因为当你用compare
来实现equals
时,需要检查传入对象的类型。而由于Java
的类型擦除机制,Ordered
特质自己无法完成这个检查。因此你需要自定义
equals
方法,哪怕你已经继承了Ordered
。
5.2 可叠加的修改
特质除了用于将瘦接口转化成富接口之外,还可以用于为类提供可叠加的修改。
一个典型的例子:对整数队列进行修改:
- 可以将所有放入队列的整数翻倍。
- 可以将所有放入队列的整数加一。
- 可以将队列中的负数去掉。
xxxxxxxxxx
import scala.collection.mutable.ArrayBuffer abstract class MyQueue{ def get(): Int def put(x:Int) } class BasicQueue extends MyQueue{ private val buf = new ArrayBuffer[Int] def get() = buf.remove(0) def put(x:Int) = {buf += x} } val queue = new BasicQueue queue.put(10) println(queue.get()) // 打印结果:10 trait DoubleOp extends MyQueue{ abstract override def put(x:Int) = {super.put(2*x)} } trait IncOp extends MyQueue{ abstract override def put(x:Int) = {super.put(x+1)} } class DoubleBasicQueue extends BasicQueue with DoubleOp val queue2 = new DoubleBasicQueue queue2.put(10) println(queue2.get()) // 打印结果:20这里有两个要点:
DoubleOp
声明了一个超类MyQueue
,因此该特质只能够被混入那些同样继承自MyQueue
的类。该特质在一个声明为抽象的方法里做了一个
super
的调用。- 对于普通的类而言,这样的调用是非法的,因为它们在运行时必定会失败。因为父类的
put
方法是抽象的,并未实现。 - 对于特质来说,这样的调用实际上可以成功。因为特质中的
super
是动态绑定的,只要在给出了该方法具体定义的特质或类之后混入,DoubleOp
特质里的super
调用就可以正常工作。
为了告诉编译器你是特意如此,必须将这样的方法标记为
abstract override
。这样的修饰符组合只允许用在特质的成员上,不允许用在类的成员上。其含义是:该特质必须混入某个拥有该方法具体定义的类中。- 对于普通的类而言,这样的调用是非法的,因为它们在运行时必定会失败。因为父类的
如果类仅仅是继承然后混入特质,而并没有任何新的代码,这时候可以直接
new
而不必定义一个有名字的类。xxxxxxxxxx
class DoubleBasicQueue extends BasicQueue with DoubleOp val queue2 = new DoubleBasicQueue可以简化为:
xxxxxxxxxx
val queue2 = new BasicQueue with DoubleOp这里的特质主要用作是代表某种修改
modification
,因为它们修改了底层类的状态,而不是定义并修改自己的状态。- 这些特质是可叠加的
stackable
。 - 可以从这些特质中任意选择,将它们混入类。
- 这些特质是可叠加的
混入特质的顺序是重要的。大致上讲,越靠右出现的特质越先起作用。
当你调用某个带有混入的类的方法时,最靠右端的特质中的方法最先被调用。如果该方法调用
super
,则它将调用左侧紧挨着它的那个特质的方法,以此类推。
5.3 特质和多重继承
特质是一种从多个像类一样结构继承的方式,但是它和
C++
中的多重继承有重大区别。其中一个尤为重要的区别是:对super
的解读。- 在多重继承中,
super
调用的方法在调用的地方就确定了。 - 在特质中,
super
调用的方法取决于类和混入类的特质的线性化linearization
。
- 在多重继承中,
以一个简单例子来说明:
xxxxxxxxxx
import scala.collection.mutable.ArrayBuffer abstract class MyQueue{ def get(): Int def put(x:Int) } class BasicQueue extends MyQueue{ private val buf = new ArrayBuffer[Int] def get() = buf.remove(0) def put(x:Int) = {buf += x} } trait DoubleOp extends MyQueue{ abstract override def put(x:Int) = {super.put(2*x)} } trait IncOp extends MyQueue{ abstract override def put(x:Int) = {super.put(x+1)} }对于代码:
xxxxxxxxxx
val q = new BasicQueue with DoubleOp with IncOp q.put(1)如果是多重继承,则需要考虑
q.put
究竟调用的是哪一个put
方法。如果规则是最后一个特质胜出,则
DoubleOp
中的put
会被执行;如果规则是第一个特质胜出,则IncOp
中的put
会被执行。因此,这种方式无法实线可叠加的修改。
Scala
的规则是:线性化。当用
new
实例化一个类的时候,Scala
会将类及其所有继承的类和特质都拿出来,将其线性的排列在一起。当你在某个类中调用
super
的时,被调用的方法是这个链条中向上最近的那个。如果除了最后一个方法外,所有的方法都调用了super
,则最终的结果就是叠加在一起的行为。
在任何线性化中,类总是位于所有它的超类和混入的特质之前。因此当你写下调用
super
的方法时,该方法绝对是在修改超类和混入特质的行为。Scala
线性化的主要性质可以通过下面的例子说明:xxxxxxxxxx
class Animal { def f = println("This is Animal") } trait Furry extends Animal { override abstract def f = { println("This is Furry") super.f } } trait HasLegs extends Animal { override abstract def f = { println("This is HasLegs") super.f } } trait FourLegged extends HasLegs { override abstract def f = { println("This is FourLegged") super.f } } class Cat extends Animal with Furry with FourLegged { override def f = { println("This is Cat") super.f } }其继承体系为:
注意:
- 即使
Animal
的f
是具体的,trait
中的f
也可以是抽象的。 - 具体的类
Cat
中,f
必须是具体的,也就是不能有abstract
。
执行代码:
xxxxxxxxxx
var cat = new Cat cat.f /* 输出为: This is Cat This is FourLegged This is HasLegs This is Furry This is Animal /*Cat
(class Cat extends Animal with Furry with FourLegged
) 的线性化从后到前计算过程如下:最后一部分是超类
Animal
的线性化。这段线性化被直接复制过来而不加修改。由于
Animal
并不显式扩展自某个超类,也没有混入任何特质,因此其线性化看起来是这样的:Animal -> AnyRef -> Any
。线性化倒数第二部分是首个混入特质
Furry
的线性化。因为所有已经出现在Animal
线性化中的类都不再重复出现,每个类在Cat
的线性化当中只出现一次。因此结果为:Furry ->Animal -> AnyRef -> Any
。接下来是
FourLegged
的线性化。同样的,任何之前出现的类都不再重复出现,因此结果为:FourLegged -> HasLegs -> Furry ->Animal -> AnyRef -> Any
。最后,
Cat
线性化中的第一个类是它自己,最终Cat
的线性化结果为:Cat -> FourLegged -> HasLegs -> Furry ->Animal -> AnyRef -> Any
。
当这些类和特质中的任何一个通过
supper
调用某个方法时,被调用的是在线性化链条中出现在其右侧的首个实现。如果定义为:
xxxxxxxxxx
class Cat extends Animal with Furry with FourLegged { override def f = { println("This is Cat") } }因为
Cat
中没有super
调用,则并不会执行线性化链条搜索。xxxxxxxxxx
var cat = new Cat cat.f /* 输出为: This is Cat */- 即使
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论