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
则对象可以是clone
d,但不是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 ...
随机推荐
- git查看当前分支所属
1.git branch -vv 2.git config --lis
- JS正则验证两位小数,验证数字最简单正则表达式大全
<h3>输入完按回车后即可验证!</h3> 正整数: <input type="text" size="20" onkeydown ...
- springboot整合mybatis完整示例, mapper注解方式和xml配置文件方式实现(我们要优雅地编程)
一.注解方式 pom <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId& ...
- 2019-4-12-WPF-绑定的默认模式
title author date CreateTime categories WPF 绑定的默认模式 lindexi 2019-04-12 09:38:58 +0800 2019-04-12 09: ...
- Python3 dir() 函数
Python dir() 函数 描述 dir() 函数不带参数时,返回当前范围内的变量.方法和定义的类型列表:带参数时,返回参数的属性.方法列表.如果参数包含方法__dir__(),该方法将被调用.如 ...
- html input onfocus
<input type="text" value="请输入内容" onfocus="javascript:if(this.value=='请输入 ...
- 【js】 vue 2.5.1 源码学习(六) initProxy initLifeCycle 渲染函数的作用域代理
大体思路 (五) 1. initProxy 渲染函数的作用域代理 ==> es6 如果支持proxy (hasProxy) 就用proxy 不支持就用 defineProperty() prox ...
- 【js】vue 2.5.1 源码学习(二) 策略合并
一. 整体思路 1 首先是代码的大体构造,先判断引入代码的环境,即对应amd 和cmd的处理 2 vue_init 需要借助 initMinxin ==>>> ...
- win10 uwp 好看的时间选择控件
本文告诉大家我找到的好看的时间选择控件 先给大家看一下图,然后就知道我说的是什么 首先需要安装 Nuget ,搜索 DeanChalk.UWP.TimePicker 或输入Install-Packag ...
- 【2016常州一中夏令营Day4】
小 W 走迷宫[问题描述]小 W 被小 M 困在了一个方格矩阵迷宫里,矩阵边界在无穷远处,我们做出如下的假设:a. 每走一步时,只能从当前方格移动一格,走到某个相邻的方格上:b. 走过的格子立即塌陷无 ...