您可以获得结构化类型的许多好处,而不会使用类型类导致运行时损失。类型类是一个概念。这是多态性的一种方法。特别是,它是ad-hoc polymorphism。它是我们可以获得编译时、静态检查的鸭子类型的方式!
让我们制作Reader[I]
和Writer[O]
特征,其中I
和O
分别是输入和输出流类型。
trait Reader[I] {
def read(input: I, buffer: Array[Byte]): Int
}
trait Writer[O] {
def write(output: O, buffer: Array[Byte], startAt: Int, nBytesToWrite: Int): Unit
}
我们现在可以创建一个通用的复制方法,该方法可以对订阅这些接口的事物进行操作。
object CopyStreams {
type Bytes = Int
def apply[I, O](input: I, output: O, chunkSize: Bytes = 1024)(implicit r: Reader[I], w: Writer[O]): Unit = {
val buffer = Array.ofDim[Byte](chunkSize)
var count = -1
while ({count = r.read(input, buffer); count > 0})
w.write(output, buffer, 0, count)
}
}
注意这里的隐式r
和w
参数。本质上,我们是说如果存在且范围内的值,CopyStreams[I,O].apply
它将起作用。这将使我们能够无缝地调用 CopyStreams(input, output)。Reader[I]
Writer[O]
我们可以使用上下文边界apply
并将签名重写为
def apply[I: Reader, O: Writer](input: I, output: O, chunkSize: Bytes = 1024): Unit
然后,我们将分别使用和访问我们的Reader[I]
和Writer[O]
实例。请注意,这种形式等同于第一种形式。Scala 编译器实际上会将这个上下文绑定版本重写为我们提供的第一个版本。implicitly[Reader[I]]
implicitly[Writer[O]]
然而,重要的是,请注意此实现是通用的。它对独立于实际流实现的类型进行操作。
在我的特定用例中,我需要将 S3 对象复制到本地文件。所以我做了以下隐含值。
object Reader {
implicit val s3ObjectISReader = new Reader[S3ObjectInputStream] {
@inline override def read(input: S3ObjectInputStream, buffer: Array[Byte]): Int =
input.read(buffer)
}
}
object Writer {
implicit val fileOSWriter = new Writer[FileOutputStream] {
@inline override def write(output: FileOutputStream,
buffer: Array[Byte],
startAt: Int,
nBytesToWrite: Int): Unit =
output.write(buffer, startAt, nBytesToWrite)
}
}
所以现在我可以执行以下操作:
val input:S3ObjectStream = ...
val output = new FileOutputStream(new File(...))
import Reader._
import Writer._
CopyStreams(input, output)
// close and such...
如果我们需要复制不同的流类型,我们只需要写入一个新的Reader
或Writer
隐含的值。我们可以在CopyStreams
不更改代码的情况下使用它!