Scala 特征、超类和早期定义语法

Scala trait, superclass and early definition syntax

我正在通过书中的练习来学习 Scala "Scala for the Impatient"。一项练习要求:

In the java.io library, you add buffering to an input stream with a BufferedInputStream decorator. Reimplement buffering as a trait. For simplicity, override the read method.

尽管(对我而言)我不清楚我应该覆盖哪个 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()
  }
}

问题:

  1. 如何在 class MyBufferedInputStream 中指定 val bufferSize 的早期定义?所有在线示例都显示了具有单个特征的早期定义,而不是多个 classes 和特征。
  2. 对于问题的要求,这看起来是正确的方法吗?

编辑: 根据 Kulu Limpa 的回答,我修改了我的代码。以下是有效的(忽略日志内容):

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()
  }
}

测试:

class MyBufferedInputStreamSpec extends UnitSpec {  

"Buffer size" should "be overridable and 4" in {
    new MyBufferedInputStream(newInputStream("abc"), 4).bufferSize should be(4)
  }

  "Buffered Stream" should "read data twice into buffer when bufferSize is smaller than number of bytes available" in {
    val str = new MyBufferedInputStream(newInputStream("abc"), 2) with ConsoleLogger with TimestampLogger

    str.readBuffered() should be('a')
    str.readBuffered() should be('b')
    str.readBuffered() should be('c')
    str.readBuffered() should be(-1)
  }

  "Buffered Stream" should "read data once into buffer when bufferSize is larger than number of bytes available" in {
    val str = new MyBufferedInputStream(newInputStream("abc"), 4) with ConsoleLogger with TimestampLogger

    str.readBuffered() should be('a')
    str.readBuffered() should be('b')
    str.readBuffered() should be('c')
    str.readBuffered() should be(-1)
  }

  private def newInputStream(str: String) = {
    new ByteArrayInputStream(str.getBytes(UTF_8))
  }
}

编辑:

使用预初始化字段实现 class 的语法是

class ImplementingClass extends {val field1 = ???; val field2 = ???} with AbstractSuperclass with Trait1 with Trait2

请注意,尽管有关键字 with,(抽象)superclass 需要位于第一个位置,并且您不能扩展多个 class。

在示例中,

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

How do I specify an early definition for val bufferSize in class MyBufferedInputStream? All examples online show early definition with a single trait, not with multiple classes and traits.

我认为早期定义 - 或 Chapter 20.5 in Programming in Scala, First Edition 中所谓的 "Pre-initialized fields" - 基于你混合的特征数量没有区别。但是让我们通过示例:

让我们假设您没有在 Buffered 中初始化 bufferSize,即

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()
}

因为superclass的字段,即Buffered.buff在subclass的字段之前被初始化,所以new Array[Byte](bufferSize)在[=22=之前被调用] 被初始化,因此当 bufferSize0 时,导致长度为零的缓冲区。

这个问题有多种解决方案:

  • make bufferSize 在实现 class a 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

Does this look like a correct approach to what the question is asking for?

我没有 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 returns 要么是当前位置的缓冲值,要么是缓冲区中存储的字节数,巧合的是,它们都可以看作是一个 Int,但在概念上是不同的.此外,在 public 接口中公开 var 字段和可变数组 buff 是危险的,因为这使客户端代码能够改变 Buffered 的内部状态。

我理解 InputStream.read() return 是 "code" -1 如果到达流的末尾。然而,如果缓冲区中没有可用数据,更惯用的 readBuffered() 实现将 return 和 Option[Byte]、returning 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