Scala 中trait的线性化规则(Linearization Rule)和 super 的调用行为
在 Scala 中,特质(Trait)是一种强大的工具,用于实现代码的复用和组合。当一个类混入(with)多个特质时,可能会出现方法冲突的情况。为了解决这种冲突,Scala 引入了最右优先原则(Rightmost First Rule),也称为线性化规则(Linearization Rule)。
最右优先原则
最右优先原则的核心思想是:在混入多个特质时,最右边的特质会优先生效。也就是说,如果一个方法在多个特质中都有定义,那么最右边的特质中的方法会覆盖左边特质中的方法。
示例1
trait A {def greet(): String = "Hello from A"
}trait B {def greet(): String = "Hello from B"
}class C extends A with B {override def greet(): String = super.greet()
}val obj = new C
println(obj.greet()) // 输出: Hello from B
在上面的例子中:
-
类
C混入了特质A和B。 -
根据最右优先原则,
B中的greet方法会覆盖A中的greet方法。 -
因此,调用
obj.greet()时,输出的是B中的实现。
线性化规则
最右优先原则是 Scala 线性化规则的一部分。Scala 会为每个类生成一个线性化顺序(Linearization Order),这个顺序决定了方法调用的优先级。
线性化顺序的生成规则
-
类的线性化顺序从最具体的类开始,逐步向更通用的类扩展。
-
混入的特质按照从右到左的顺序排列。
-
每个特质只会在线性化顺序中出现一次。
示例2
trait A {def greet(): String = "Hello from A"
}trait B extends A {override def greet(): String = "Hello from B"
}trait C extends A {override def greet(): String = "Hello from C"
}class D extends B with C {override def greet(): String = super.greet()
}val obj = new D
println(obj.greet()) // 输出: Hello from C
在这个例子中:
-
类
D的线性化顺序是:D -> C -> B -> A。 -
根据最右优先原则,
C中的greet方法会覆盖B中的greet方法。 -
因此,调用
obj.greet()时,输出的是C中的实现。
super 的调用
在特质中,super 的调用是动态绑定的,它会根据线性化顺序调用下一个特质或类中的方法。
示例3
trait A {def greet(): String = "Hello from A"
}trait B extends A {override def greet(): String = s"${super.greet()} and Hello from B"
}trait C extends A {override def greet(): String = s"${super.greet()} and Hello from C"
}class D extends B with C {override def greet(): String = super.greet()
}val obj = new D
println(obj.greet()) // 输出: Hello from A and Hello from B and Hello from C
如果你还是有疑问,接下来,是更加具体的分析:
在示例3中,输出的是Hello from A and Hello from B and Hello from C,而不是 Hello from A and Hello from C and Hello from B。这看起来似乎与最右优先原则相矛盾,但实际上这是由 Scala 的线性化规则(Linearization Rule)决定的。
线性化规则详解
Scala 的线性化规则决定了方法调用的顺序。具体来说,当一个类混入多个特质时,Scala 会生成一个线性化顺序,这个顺序决定了 super 调用的行为。
线性化顺序的生成规则
-
从最具体的类开始,逐步向更通用的类扩展。
-
混入的特质按照从右到左的顺序排列。
-
每个特质只会在线性化顺序中出现一次。
在示例3中:
class D extends B with C
-
D的线性化顺序是:D -> C -> B -> A。
线性化顺序的解释
-
D:最具体的类。 -
C:因为C是最右边的特质,所以它排在B前面。 -
B:B是左边的特质,排在C后面。 -
A:A是B和C的共同父特质,排在最后。
因此,D 的线性化顺序是:D -> C -> B -> A。
super 的调用行为
在 Scala 中,super 的调用是动态绑定的,它会根据线性化顺序调用下一个特质或类中的方法。
例子分析
trait A {def greet(): String = "Hello from A"
}trait B extends A {override def greet(): String = s"${super.greet()} and Hello from B"
}trait C extends A {override def greet(): String = s"${super.greet()} and Hello from C"
}class D extends B with C {override def greet(): String = super.greet()
}val obj = new D
println(obj.greet()) // 输出: Hello from A and Hello from B and Hello from C
-
D中的greet方法:-
调用
super.greet(),根据线性化顺序,super指向C。
-
-
C中的greet方法:-
调用
super.greet(),根据线性化顺序,super指向B。
-
-
B中的greet方法:-
调用
super.greet(),根据线性化顺序,super指向A。
-
-
A中的greet方法:-
返回
"Hello from A"。
-
-
方法调用的堆栈:
-
A返回"Hello from A"。 -
B在其基础上追加" and Hello from B",得到"Hello from A and Hello from B"。 -
C在其基础上追加" and Hello from C",得到"Hello from A and Hello from B and Hello from C"。
-
为什么不是 Hello from A and Hello from C and Hello from B?
-
因为
super的调用是根据线性化顺序动态绑定的,而不是简单地按照最右优先原则直接覆盖。 -
线性化顺序是
D -> C -> B -> A,所以C的super指向B,B的super指向A。 -
因此,
C的greet方法会先调用B的greet方法,而B的greet方法会调用A的greet方法。
总结
-
最右优先原则:决定了特质的优先级,最右边的特质会优先生效。
-
线性化规则:决定了
super的调用顺序,super会根据线性化顺序动态绑定到下一个特质或类。 -
在示例3中,线性化顺序是
D -> C -> B -> A,因此输出的顺序是Hello from A and Hello from B and Hello from C。
在示例2中,为什么输出是 Hello from C,而不是 Hello from A and Hello from C?
代码分析
trait A {def greet(): String = "Hello from A"
}trait B extends A {override def greet(): String = "Hello from B"
}trait C extends A {override def greet(): String = "Hello from C"
}class D extends B with C {override def greet(): String = super.greet()
}val obj = new D
println(obj.greet()) // 输出: Hello from C
-
特质的继承关系:
-
B和C都继承自A,并且都重写了greet方法。 -
D混入了B和C,并且重写了greet方法,调用了super.greet()。
-
-
线性化顺序:
-
当
D混入B和C时,Scala 会生成一个线性化顺序。线性化顺序的规则是:-
从最具体的类开始,逐步向更通用的类扩展。
-
混入的特质按照从右到左的顺序排列。
-
每个特质只会在线性化顺序中出现一次。
-
-
对于
class D extends B with C,线性化顺序是:D -> C -> B -> A。
-
-
super的调用行为:-
在
D的greet方法中,super.greet()会根据线性化顺序调用下一个特质或类中的greet方法。 -
线性化顺序是
D -> C -> B -> A,因此super.greet()会调用C中的greet方法。
-
-
C中的greet方法:-
C中的greet方法直接返回"Hello from C",没有调用super.greet()。 -
因此,
C的greet方法不会继续调用B或A的greet方法。
-
为什么输出是 Hello from C?
-
在
D的greet方法中,super.greet()调用的是C的greet方法。 -
C的greet方法直接返回"Hello from C",没有继续调用super.greet()(即没有调用B或A的greet方法)。 -
因此,最终的输出是
"Hello from C"。
为什么不是 Hello from A and Hello from C?
-
如果希望输出
Hello from A and Hello from C,需要在C的greet方法中显式调用super.greet(),将A的行为与C的行为组合起来。 -
例如:
trait C extends A {override def greet(): String = s"${super.greet()} and Hello from C"
}
修改后,C 的 greet 方法会先调用 A 的 greet 方法,然后追加 " and Hello from C"。此时,输出会是 Hello from A and Hello from C。
修改后的代码
trait A {def greet(): String = "Hello from A"
}trait B extends A {override def greet(): String = "Hello from B"
}trait C extends A {override def greet(): String = s"${super.greet()} and Hello from C"
}class D extends B with C {override def greet(): String = super.greet()
}val obj = new D
println(obj.greet()) // 输出: Hello from A and Hello from C
总结
-
默认行为:在
C的greet方法中,如果没有调用super.greet(),则只会执行C的逻辑,输出Hello from C。 -
组合行为:如果希望将父特质的行为与当前特质的行为组合起来,需要在重写方法时显式调用
super.greet()。 -
线性化顺序:
super的调用是根据线性化顺序动态绑定的,线性化顺序决定了方法调用的优先级。
相关文章:
Scala 中trait的线性化规则(Linearization Rule)和 super 的调用行为
在 Scala 中,特质(Trait)是一种强大的工具,用于实现代码的复用和组合。当一个类混入(with)多个特质时,可能会出现方法冲突的情况。为了解决这种冲突,Scala 引入了最右优先原则&#…...
C++入门——引用
C入门——引用 一、引用的概念 引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。这就好比《水浒传》中,一百零八位好汉都有自己的绰号。通过&…...
深度学习模型组件之优化器—Lookahead:通过“快慢”两组优化器协同工作,提升训练稳定性
深度学习模型组件之优化器—Lookahead:通过“快/慢”两组优化器协同工作,提升训练稳定性 文章目录 深度学习模型组件之优化器—Lookahead:通过“快/慢”两组优化器协同工作,提升训练稳定性1. Lookahead优化器的背景2. Lookahead优…...
K8s 1.27.1 实战系列(五)Namespace
Kubernetes 1.27.1 中的 Namespace(命名空间)是集群中实现多租户资源隔离的核心机制。以下从功能、操作、配置及实践角度进行详细解析: 一、核心功能与特性 1、资源隔离 Namespace 将集群资源划分为逻辑组,实现 Pod、Service、Deployment 等资源的虚拟隔离。例如,…...
Spring Boot整合ArangoDB教程
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 一、环境准备 JDK 17Maven 3.8Spring Boot 3.2ArangoDB 3.11(本地安装或Docker运行) Docker启动ArangoDB docker run -d --name ar…...
虚幻基础:动画层接口
文章目录 动画层:动画图表中的函数接口:名字,没有实现。动画层接口:由动画蓝图实现1.动画层可直接调用实现功能2.动画层接口必须安装3.动画层默认使用本身实现4.动画层也可使用其他动画蓝图实现,但必须在角色蓝图中关联…...
从 GitHub 批量下载项目各版本的方法
一、脚本功能概述 这个 Python 脚本的主要功能是从 GitHub 上下载指定项目的各个发布版本的压缩包(.zip 和 .tar.gz 格式)。用户需要提供两个参数:一个是包含项目信息的 CSV 文件,另一个是用于保存下载版本信息的 CSV 文件。脚本…...
一、对lora_sx1278v1.2模块通信记录梳理
一、通信测试: 注意: 1、检查供电是否满足。 2、检测引脚是否松动或虚焊。 3、检测触发是否能触发。 引脚作用: SPI:通信(仅作一次初始化,初始化后会进行模块通信返回测试,返回值和预定值相否即…...
Java在word中动态增加表格行并写入数据
SpringBoot项目中在word中动态增加表格行并写入数据,不废话,直接上配置和代码: 模板内容如下图所示: 模板是一个空word表格即可,模板放在resources下的自定义目录下,如下图示例。 实体类定义如下: @Data @AllArgsConstructor @NoArgsConstructor public class Person …...
[通讯协议]232通信
RS-232 简介 RS-232是一种广泛应用的串行通信接口标准,使用的协议就是串口协议。 通信能力 单端信号传输:信号以地线为参考,逻辑“1”为-3V至-15V,逻辑“0”为3V至15V。点对点通信:仅支持两个设备之间的通信&#x…...
Refreshtoken 前端 安全 前端安全方面
网络安全 前端不需要过硬的网络安全方面的知识,但是能够了解大多数的网络安全,并且可以进行简单的防御前两三个是需要的 介绍一下常见的安全问题,解决方式,和小的Demo,希望大家喜欢 网络安全汇总 XSSCSRF点击劫持SQL注入OS注入请求劫持DDOS 在我看来,前端可以了解并且防御前…...
EasyRTC嵌入式音视频通话SDK:基于ICE与STUN/TURN的实时音视频通信解决方案
在当今数字化时代,实时音视频通信技术已成为人们生活和工作中不可或缺的一部分。无论是家庭中的远程看护、办公场景中的远程协作,还是工业领域的远程巡检和智能设备的互联互通,高效、稳定的通信技术都是实现这些功能的核心。 EasyRTC嵌入式音…...
AI终章.展望未来2026-2030年预测与DeepSeek的角色
人工智能(AI)近年来发展迅速,正在改变行业、商业模式以及我们与技术互动的方式。展望2026-2030年,预计在多模态AI、自主代理和自动化驱动的新职业创造方面将出现革命性发展。本章将探讨这些趋势,以及DeepSeek将如何在这…...
PyTorch系列教程:编写高效模型训练流程
当使用PyTorch开发机器学习模型时,建立一个有效的训练循环是至关重要的。这个过程包括组织和执行对数据、参数和计算资源的操作序列。让我们深入了解关键组件,并演示如何构建一个精细的训练循环流程,有效地处理数据处理,向前和向后…...
【面试】Zookeeper
Zookeeper 1、ZooKeeper 介绍2、znode 节点里面的存储3、znode 节点上监听机制4、ZooKeeper 集群部署5、ZooKeeper 选举机制6、何为集群脑裂7、如何保证数据一致性8、讲一下 zk 分布式锁实现原理吧9、Eureka 与 Zk 有什么区别 1、ZooKeeper 介绍 ZooKeeper 的核心特性 高可用…...
电力系统中各参数的详细解释【智能电表】
一、核心电力参数 电压 (Voltage) 单位:伏特(V) 含义:电势差,推动电流流动的动力 类型:线电压(三相系统)、相电压,如220V(家用)或380Vÿ…...
前端系统测试(单元、集成、数据|性能|回归)
有关前端测试的面试题 系统测试 首先,功能测试部分。根据资料,单元测试是验证最小可测试单元的正确性,比如函数或组件。都提到了单元测试的重要性,强调其在开发早期发现问题,并通过自动化提高效率。需要整合我搜索到的资料中的观点,比如单元测试的方法(接口测试、路径覆…...
软件开发过程总揽
开发模型 传统开发模型 瀑布模型 #mermaid-svg-yDNBSwh3gDYETWou {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-yDNBSwh3gDYETWou .error-icon{fill:#552222;}#mermaid-svg-yDNBSwh3gDYETWou .error-text{fill:#…...
VBA第二十期 VBA最简单复制整张表格Cells的用法
前面讲过复制整张表格的方法,使用语句Workbooks("实例.xlsm").Sheets("表格1").Copy Workbooks(wjm).Sheets(1)实现,这里用我们熟悉的Cells属性也可以实现整表复制。实例如下: Sheets("全部").Activate Cells…...
Redis为什么要自定义序列化?如何实现自定义序列化器?
在 Redis中,通常会使用自定义序列化器,那么,Redis为什么需要自定义序列化器,该如何实现它? 1、为什么需要自定义序列化器? 整体来说,Redis需要自定义序列化器,主要有以下几个原因&…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...
智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...
MySQL 主从同步异常处理
阅读原文:https://www.xiaozaoshu.top/articles/mysql-m-s-update-pk MySQL 做双主,遇到的这个错误: Could not execute Update_rows event on table ... Error_code: 1032是 MySQL 主从复制时的经典错误之一,通常表示ÿ…...
Linux部署私有文件管理系统MinIO
最近需要用到一个文件管理服务,但是又不想花钱,所以就想着自己搭建一个,刚好我们用的一个开源框架已经集成了MinIO,所以就选了这个 我这边对文件服务性能要求不是太高,单机版就可以 安装非常简单,几个命令就…...
Python实现简单音频数据压缩与解压算法
Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中,压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言,提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...
es6+和css3新增的特性有哪些
一:ECMAScript 新特性(ES6) ES6 (2015) - 革命性更新 1,记住的方法,从一个方法里面用到了哪些技术 1,let /const块级作用域声明2,**默认参数**:函数参数可以设置默认值。3&#x…...
针对药品仓库的效期管理问题,如何利用WMS系统“破局”
案例: 某医药分销企业,主要经营各类药品的批发与零售。由于药品的特殊性,效期管理至关重要,但该企业一直面临效期问题的困扰。在未使用WMS系统之前,其药品入库、存储、出库等环节的效期管理主要依赖人工记录与检查。库…...
