Scala实践12
1、内部类和抽象类型成员作为对象成员
- 内部类
在Scala中,可以让类将其他类作为成员。这些内部类是封闭类的成员。在Scala中,这样的内部类绑定到外部对象。假设希望编译器在编译时阻止我们混合哪些节点属于哪个图。路径相关类型提供了解决方案。
为了说明差异,绘制了图数据类型的实现:
class Graph {
class Node {
var connectedNodes: List[Node] = Nil
def connectTo(node: Node) {
if (connectedNodes.find(node.equals).isEmpty) {
connectedNodes = node :: connectedNodes
}
}
}
var nodes: List[Node] = Nil
def newNode: Node = {
val res = new Node
nodes = res :: nodes
res
}
}
上例将图表表示为节点列表(List[Node])。每个节点都有一个与其连接的其他节点的列表(connectedNodes)。这class Node是一个路径依赖类型,因为它嵌套在class Graph。因此,connectedNodes必须使用newNode来自同一实例的所有节点创建Graph。
val graph1: Graph = new Graph
val node1: graph1.Node = graph1.newNode
val node2: graph1.Node = graph1.newNode
val node3: graph1.Node = graph1.newNode
node1.connectTo(node2)
node3.connectTo(node1)
明确宣布的类型node1,node2以及node3作为graph1.Node为清楚起见,但编译器可能推断出它。这是因为当我们调用graph1.newNode哪些调用时new Node,该方法正在使用Node特定于实例的实例graph1。
如果现在有两个图形,Scala的类型系统不允许我们将一个图形中定义的节点与另一个图形的节点混合,因为另一个图形的节点具有不同的类型。这是一个非法的程序:
val graph1: Graph = new Graph
val node1: graph1.Node = graph1.newNode
val node2: graph1.Node = graph1.newNode
node1.connectTo(node2) // 合法的
val graph2: Graph = new Graph
val node3: graph2.Node = graph2.newNode
node1.connectTo(node3) // 非法的
graph1.Node与graph2.Node不同图表。在Scala中,这样的类型可以表示,它是写的Graph#Node。如果希望能够连接不同图形的节点,必须通过以下方式更改初始图形实现的定义:
class Graph {
class Node {
var connectedNodes: List[Graph#Node] = Nil
def connectTo(node: Graph#Node) { //Graph#Node与上面匹配
if (connectedNodes.find(node.equals).isEmpty) {
connectedNodes = node :: connectedNodes
}
}
}
var nodes: List[Node] = Nil
def newNode: Node = {
val res = new Node
nodes = res :: nodes
res
}
}
- 抽象类型
抽象类型(如traits和抽象类)又可以具有抽象类型成员。这意味着具体实现定义了实际类型。这是一个例子:
trait Buffer {
type T
val element: T
}
这里定义了一个摘要type T。它用于描述类型element,并使之更具体。
abstract class SeqBuffer extends Buffer {
type U
type T <: Seq[U]
def length = element.length
}
注意如何U在上类型绑定的规范中使用另一个抽象类型T。这class SeqBuffer允许我们仅通过声明类型T必须Seq[U]是新抽象类型的子类型来仅在缓冲区中存储序列U。
具有抽象类型成员的特征或类通常与匿名类实例一起使用。为了说明这一点,我们现在看一个程序,它处理一个引用整数列表的序列缓冲区:
abstract class IntSeqBuffer extends SeqBuffer {
type U = Int
}
def newIntSeqBuf(elem1: Int, elem2: Int): IntSeqBuffer =
new IntSeqBuffer {
type T = List[U]
val element = List(elem1, elem2)
}
val buf = newIntSeqBuf(7, 8)
println("length = " + buf.length)
println("content = " + buf.element)
这里工厂newIntSeqBuf使用IntSeqBuf(ie new IntSeqBuffer)的匿名类实现将抽象类型T设置为具体类型List[Int]。
也可以将抽象类型成员转换为类的类型参数,反之亦然。这是上面代码的一个版本,它只使用类型参数:
abstract class Buffer[+T] {
val element: T
}
abstract class SeqBuffer[U, +T <: Seq[U]] extends Buffer[T] {
def length = element.length
}
def newIntSeqBuf(e1: Int, e2: Int): SeqBuffer[Int, Seq[Int]] =
new SeqBuffer[Int, List[Int]] {
val element = List(e1, e2)
}
val buf = newIntSeqBuf(7, 8)
println("length = " + buf.length)
println("content = " + buf.element)
2、复合类型
有时需要表达对象的类型是其他几种类型的子类型。在Scala中,这可以在复合类型的帮助下表达,复合类型是对象类型的交叉点。
假设我们有两个特点Cloneable和Resetable:
trait Cloneable extends java.lang.Cloneable {
override def clone(): Cloneable = {
super.clone().asInstanceOf[Cloneable]
}
}
trait Resetable {
def reset: Unit
}
现在假设我们想编写一个cloneAndReset接受对象的函数,克隆它并重置原始对象:
def cloneAndReset(obj: ?): Cloneable = {
val cloned = obj.clone()
obj.reset
cloned
}
问题出现了参数的类型obj。如果是,Cloneable则对象可以是cloned,但不是reset; 如果它是Resetable我们可以reset,但没有clone操作。为了避免这种情况的类型转换,我们可以指定类型obj既Cloneable和Resetable。这种复合类型在Scala中是这样写的:Cloneable with Resetable。
更新的功能如下:
def cloneAndReset(obj: Cloneable with Resetable): Cloneable = {
//...
}
复合类型可以由多个对象类型组成,它们可以具有单个细化,可以用于缩小现有对象成员的签名。一般形式是:A with B with C ... { refinement }
3、自我类型
自我类型是一种声明特征必须混合到另一个特征中的方法,即使它没有直接扩展它。这使得依赖的成员可以在没有导入的情况下使用。
自我类型是一种缩小this别名的类型或另一个标识符的方法this。语法看起来像普通函数语法,但意味着完全不同的东西。
要在特征中使用自我类型,请写入标识符,要混合的另一个特征的类型,以及=>(例如someIdentifier: SomeOtherTrait =>)。
trait User {
def username: String
}
trait Tweeter {
this: User =>
def tweet(tweetText: String) = println(s"$username: $tweetText")
}
class VerifiedTweeter(val username_ : String) extends Tweeter with User {
def username = s"real $username_"
}
val realBeyoncé = new VerifiedTweeter("Beyoncé")
realBeyoncé.tweet("Just spilled my glass of lemonade")
因为this: User =>的trait Tweeter,现在的变量username是在范围上的tweet方法。这也意味着,自VerifiedTweeter扩展以来Tweeter,它还必须混合User(使用with User)。
Scala实践12的更多相关文章
- [翻译]The Neophyte's Guide to Scala Part 12: Type Classes
The Neophyte's Guide to Scala Part 12: Type Classes 过去的两周我们讨论了一些使我们保持DRY和灵活性的函数式编程技术,特别是函数组合,partial ...
- 【原创 Hadoop&Spark 动手实践 12】Spark MLLib 基础、应用与信用卡欺诈检测系统动手实践
[原创 Hadoop&Spark 动手实践 12]Spark MLLib 基础.应用与信用卡欺诈检测系统动手实践
- Scala实践4
一.数组 在Scala中,用()来访问元素,数组声明的语法格式如下 : var z:Array[String] = new Array[String](3) 或 var z = new Array[S ...
- Scala实践5
一.Scala的层级 1.1类层级 Scala中,Any是所其他类的超类,在底端定义了一些有趣的类NULL和Nothing,是所有其他类的子类. 根类Any有两个子类:AnyVal和AnyRef.其中 ...
- Socket编程实践(12) --UDP编程基础
UDP特点 无连接,面向数据报(基于消息,不会粘包)的传输数据服务; 不可靠(可能会丢包, 乱序, 反复), 但因此普通情况下UDP更加高效; UDP客户/服务器模型 UDP-API使用 #inclu ...
- 《高级软件测试》web测试实践--12月31日记录
今日的任务进度如上图所示.我们对华科软件学院和计算机学院的网站进行了对比分析,分析的角度包括基本功能分析.前端性能分析.用户调研等.在这里我们简单总结下我们得到的评测结果. 基本功能分析:计算机学院和 ...
- 《高级软件测试》web测试实践--12月30日记录
考完数学,我们正式开始web测试实践的作业,今天,我们主要进行了方案的选择和人员的分工.任务计划和安排如上图所示. 任务进展:完成题目选择和人员分工: 遇到问题:暂无: 下一步任务:完成软件评测.用户 ...
- Linux IPC实践(12) --System V信号量(2)
实践1:信号量实现进程互斥 父子进程执行流程如下: 父进程 子进程 P P O(print) X(print) sleep sleep O(print) X(print) V V sleep slee ...
- es6+最佳入门实践(12)
12.class基础用法和继承 12.1.class基础语法 在es5中,面向对象我们通常写成这样 function Person(name,age) { this.name = name; this ...
随机推荐
- Linux中ifcfg-eth0配置参数说明
ifcfg-eth0在/etc/sysconfig/network-scripts下, 其配置如下: DEVICE=物理设备名IPADDR=IP地址NETMASK=掩码值NETWORK=网络地址BRO ...
- RBF神经网络的matlab简单实现
径向基神经网络 1.径向基函数 (Radial Basis Function,RBF) 神经网络是一种性能良好的前向网络,具有最佳逼近.训练简洁.学习收敛速度快以及克服局部最小值问题的性能,目前已经证 ...
- $_GET $_POST $_REQUEST
<form action="__APP__/View/editArticle?id=5" method="GET"> <form>表单提 ...
- CSS 实现单行及多行文字省略
单行文字省略 很多时候不确定字数限制,但换行可能影响整体设计,这个时候常用就是文字省略加全文字提示了 .dom{ text-overflow: ellipsis; overflow: hidden; ...
- InetAddress与Socket
InetAddress:构造方法私有,不能直接创建对象. InetAddress getByName(String host):在给定主机名的情况下确定主机的ip地址. InetAddress get ...
- JavaScript数据类型总结
1. 六种简单数据类型:Undefined.Null.Boolean.Number.String.Symbol(新增): 一种复杂数据类型:Object: (1)基本数据类型保存在栈内存中,是按值传 ...
- HDU 5912 Fraction(模拟)
Problem Description Mr. Frog recently studied how to add two fractions up, and he came up with an ev ...
- springdata jpa使用Example快速实现动态查询
Example官方介绍 Query by Example (QBE) is a user-friendly querying technique with a simple interface. It ...
- RabbitMQ-工作原理
使用场景 在我们秒杀抢购商品的时候,系统会提醒我们稍等排队中,而不是像几年前一样页面卡死或报错给用户. 像这种排队结算就用到了消息队列机制,放入通道里面一个一个结算处理,而不是某个时间断突然涌入大批量 ...
- 土旦:移动端 Vue+Vant 的Uploader 实现 :上传、压缩、旋转图片
面向百度开发 html <van-uploader :after-read="onRead" accept="image/*"> <img s ...