我通过本书的练习来学习Scala" Scala for the Impatient"。一个练习要求:
在
Armor shield = new Armor(); shield.setDisplayName("Wooden Shield"); shield.setFireproof(false); shield.setRupees(50);
库中,使用a将缓冲添加到输入流java.io
装饰者。重新实现缓冲作为特征。对于 简单,覆盖BufferedInputStream
方法。
即使我不清楚(对我来说)我应该覆盖哪个read
方法(在新创建的特征或输入流中),我想出了以下内容:
read
问题:
trait Buffered {
self: InputStream =>
val bufferSize: Int = 1024
val buff = new Array[Byte](bufferSize)
/* Number of bytes read so far */
var pos = 0
/* Number of bytes stored in the buffer */
var count = -1
private def fill() = {
self.read(buff)
}
def readBuffered(): Int = {
if (isAvailableInBuffer) {
println("Data available in buffer.")
incrementAndGet()
} else {
println("Data not available in buffer.")
count = fill()
println(f"Read $count%d bytes into buffer.")
if (isAvailableInBuffer) incrementAndGet() else count
}
}
private def isAvailableInBuffer = {
count > (pos % bufferSize)
}
private def incrementAndGet() = {
val x = buff(pos % bufferSize)
pos += 1
x
}
}
class MyBufferedInputStream(val input: InputStream, val size: Int) extends InputStream with Buffered {
override def read() = {
input.read()
}
}
中指定val bufferSize
的早期定义?所有在线示例都显示了具有单一特征的早期定义,而不是具有多个类和特征。修改 根据Kulu Limpa的回答,我修改了我的代码。以下是有效的(忽略日志):
MyBufferedInputStream
测试:
trait Buffered extends Logged {
self: InputStream =>
val bufferSize: Int = 1024
val buff = new Array[Byte](bufferSize)
/* Number of bytes read so far */
var pos = 0
/* Number of bytes stored in the buffer */
var count = 0
def readBuffered(): Int = {
if (isTimeToRefillBuffer) {
log(f"Time to refill buffer. bufferSize = $bufferSize%d, pos = $pos%d, count = $count%d")
count = fill()
}
if (isDataAvailableInbuffer) {
log(f"Data available in buffer. bufferSize = $bufferSize%d, pos = $pos%d, count = $count%d")
getFromBuffer()
} else -1
}
/* Invoke the read method of the InputStream it'll be mixed in with */
private def fill() = self.read(buff)
private def isDataAvailableInbuffer = count > 0
private def isTimeToRefillBuffer = !isDataAvailableInbuffer
private def getFromBuffer() = {
val x = buff(pos % bufferSize)
pos += 1
count -= 1
x
}
}
class MyBufferedInputStream(val input: InputStream, override val bufferSize: Int) extends InputStream with Buffered {
override def read() = {
input.read()
}
}
答案 0 :(得分:1)
修改强>
实现具有预初始化字段的类的语法是
class ImplementingClass extends {val field1 = ???; val field2 = ???} with AbstractSuperclass with Trait1 with Trait2
请注意,尽管有关键字with
,但(摘要)超类需要位于第一个位置,并且不能扩展多个类。
在示例中,
class MyBufferedInputStream(val input: InputStream, size: Int) extends {val bufferSize: Int = size} with InputStream with Buffered
有效,但使bufferSize
构造函数参数在这种情况下更具可读性
class MyBufferedInputStream(val input: InputStream, val bufferSize: Int) extends InputStream with Buffered
如何在课程
val bufferSize
中指定MyBufferedInputStream
的早期定义?在线的所有示例都显示了具有单一特征的早期定义,而不是具有多个类和特征。
我认为在早期定义中没有区别 - 或者在Chapter 20.5 in Programming in Scala, First Edition中调用“预初始化字段” - 基于您混合的特征数量。但让我们看一下示例:
我们假设您未在bufferSize
中初始化Buffered
,即
trait Buffered {
self: InputStream =>
val bufferSize: Int
val buff = new Array[Byte](bufferSize)
/* ... */
}
首先,实现缓冲输入流的错误方法是:
class WrongBufferedInputStream(val input: InputStream) extends InputStream with Buffered {
val bufferSize = 1024
override def read() = input.read()
}
因为超类的字段,即Buffered.buff
在子类的字段之前被初始化,所以在new Array[Byte](bufferSize)
被初始化之前调用bufferSize
,因此bufferSize
是0
,导致长度为零的缓冲区。
这个问题有多种解决方案:
bufferSize
中生成lazy val
,以便在首次使用时初始化bufferSize
构造函数参数代码:
class MyBufferedInputStreamLazyInitialization(val input: InputStream) extends InputStream with Buffered {
lazy val bufferSize = 1024
override def read() = input.read()
}
class MyBufferedInputStream(val input: InputStream) extends {val bufferSize: Int = 1024} with InputStream with Buffered {
override def read() = input.read()
}
class MyBufferedInputStreamWithCustomSize(val input: InputStream, val bufferSize: Int = 1024) extends InputStream with Buffered {
override def read() = input.read()
}
使用示例:
println(new WrongBufferedInputStream(System.in).buff.length) // 0
println(new MyBufferedInputStreamLazyInitialization(System.in).buff.length) // 1024
println(new MyBufferedInputStream(System.in).buff.length) // 1024
println(new MyBufferedInputStreamWithCustomSize(System.in).buff.length) // 1024
println(new MyBufferedInputStreamWithCustomSize(System.in, 512).buff.length) // 512
这看起来像问题所要求的正确方法吗?
我没有 Scala for the Impatient 的副本,但这种方法对我来说很好。
感谢自我类型,您可以注入任何InputStream
并创建,例如,缓冲文件输入流:
class BufferedFileInputStream(file: File) extends {val bufferSize = 1024} with FileInputStream(file) with Buffered
您还可以创建InputStream
的其他装饰器并将它们混合在一起,例如
trait Awesomeness {
self: InputStream =>
/* add some awesome functionality */
}
class BufferedAwesomeInputStream(val input: InputStream) extends {val bufferSize = 1024} with InputStream with Buffered with Awesomeness {
override def read() = input.read()
}
虽然方法看起来不错,但具体实现有点奇怪。例如,readBuffered
返回当前位置的缓冲值,或缓冲区中存储的字节数,巧合的是,它们都可以看作是Int
,但在概念上是不同的。此外,在公共接口中公开var
字段和可变数组buff
是危险的,因为这使客户端代码能够改变Buffered
的内部状态。
我了解如果到达流的末尾,InputStream.read()
会返回“代码”-1
。然而,如果缓冲区中没有可用数据,则更惯用的readBuffered()
实现将返回Option[Byte]
,返回None
:
def readBuffered(): Option[Byte] = {
if (isAvailableInBuffer) {
Some(incrementAndGet())
} else {
count = fill()
if (isAvailableInBuffer) Some(incrementAndGet()) else None
}
}
使用示例:
val f = new MyBufferedInputStream(new InputStream {
val data = "hello" map (_.toByte)
var pos = 0
override def read(): Int = if (pos < data.length){pos = pos + 1; data(pos - 1).asInstanceOf[Int]} else -1
}, 1024)
println(f.readBuffered() map (_.asInstanceOf[Char])) // Some(h)
println(f.readBuffered() map (_.asInstanceOf[Char])) // Some(e)
println(f.readBuffered() map (_.asInstanceOf[Char])) // Some(l)
println(f.readBuffered() map (_.asInstanceOf[Char])) // Some(l)
println(f.readBuffered() map (_.asInstanceOf[Char])) // Some(o)
println(f.readBuffered() map (_.asInstanceOf[Char])) // None