Scala(或Java)中泛型函数的专门化

Scala(或Java)中泛型函数的专门化,java,scala,generics,specialized-annotation,Java,Scala,Generics,Specialized Annotation,可以在Scala中专门化泛型函数(或类)吗?例如,我想编写一个将数据写入ByteBuffer的通用函数: def writeData[T](buffer: ByteBuffer, data: T) = buffer.put(data) 但是,由于put方法只需要一个字节并将其放入缓冲区,因此我需要将其专门化为int和long,如下所示: def writeData[Int](buffer: ByteBuffer, data: Int) = buffer.putInt(data) def wri

可以在Scala中专门化泛型函数(或类)吗?例如,我想编写一个将数据写入ByteBuffer的通用函数:

def writeData[T](buffer: ByteBuffer, data: T) = buffer.put(data)
但是,由于put方法只需要一个字节并将其放入缓冲区,因此我需要将其专门化为int和long,如下所示:

def writeData[Int](buffer: ByteBuffer, data: Int) = buffer.putInt(data)
def writeData[Long](buffer: ByteBuffer, data: Long) = buffer.putLong(data)
而且它不会编译。当然,我可以分别编写3个不同的函数writeByte、writeInt和writeLong,但假设数组还有另一个函数:

def writeArray[T](buffer: ByteBuffer, array: Array[T]) {
  for (elem <- array) writeData(buffer, elem)
}
def writeArray[T](缓冲区:字节缓冲区,数组:数组[T]){
对于(elem这个怎么样:

def writeData(buffer: ByteBuffer, data: AnyVal) {
  data match {
    case d: Byte => buffer put d
    case d: Int  => buffer putInt d
    case d: Long => buffer putLong d
    ...
  }
}
在这里,您可以在
writeData
方法中区分大小写,这使得所有其他方法都非常简单:

def writeArray(buffer: ByteBuffer, array: Array[AnyVal]) {
  for (elem <- array) writeData(buffer, elem)
}

顺便说一句,由于Scala严格的面向对象特性,这项工作非常容易。在Java中,原语类型不是对象,这将非常麻烦。在Java中,您实际上必须为每个原语类型创建一个单独的方法,除非您想执行一些难看的装箱和拆箱操作。

使用类型类模式。它有与instanceOf check(或模式匹配)相比,它的优点是类型安全

import java.nio.ByteBuffer

trait BufferWriter[A] {
  def write(buffer: ByteBuffer, a: A)
}

class BuffPimp(buffer: ByteBuffer) {
  def writeData[A: BufferWriter](data: A) = { 
    implicitly[BufferWriter[A]].write(buffer, data)
  }
}

object BuffPimp {
  implicit def intWriter = new BufferWriter[Int] {
    def write(buffer: ByteBuffer, a: Int) = buffer.putInt(a)
  }
  implicit def doubleWriter = new BufferWriter[Double] {
    def write(buffer: ByteBuffer, a: Double) = buffer.putDouble(a)
  }
  implicit def longWriter = new BufferWriter[Long] {
    def write(buffer: ByteBuffer, a: Long) = buffer.putLong(a)
  }
  implicit def wrap(buffer: ByteBuffer) = new BuffPimp(buffer)
}

object Test {
  import BuffPimp._
  val someByteBuffer: ByteBuffer
  someByteBuffer.writeData(1)
  someByteBuffer.writeData(1.0)
  someByteBuffer.writeData(1L)
}
因此,这段代码并不是TypeClass的最佳演示。我对它们还是很陌生。这段视频对它们的优点以及如何使用它们进行了非常全面的概述:

  • 声明

    def writeData[Int](buffer: ByteBuffer, data: Int) 
    def writeData[Long](buffer: ByteBuffer, data: Long)
    
  • 不要编译,因为Int和Long是等价的,因为Int和Long是
    形式的
    类型参数,而不是标准的Scala类型。要定义具有标准Scala类型的函数,只需编写:

    def writeData(buffer: ByteBuffer, data: Int) = buffer.putInt(data)
    def writeData(buffer: ByteBuffer, data: Long) = buffer.putLong(data)
    
    通过这种方式,可以使用相同的名称声明不同的函数

  • 由于它们是不同的函数,您无法将它们应用于静态未知类型列表的元素。您必须首先确定列表的类型。请注意,可能会发生这种情况:列表的类型为AnyRef,然后您必须动态确定每个元素的类型。可以使用原始代码,或模式匹配,如
    rolve
    所建议的。我认为这将产生相同的字节码

  • 总之,您必须选择:

    • 具有多种功能的快速代码,如
      writeByteArray、writeInArray
      等。它们都可以具有相同的名称
      writeArray
      ,但可以通过其实际参数进行静态区分。Dominic Bou Sa建议的变体就是这种类型

    • 简洁但速度慢的代码,具有运行时类型确定功能


  • 不幸的是,你不能同时拥有快速和简洁的代码。

    如果你能同时拥有一个紧凑和高效的解决方案,那不是很好吗?事实证明,考虑到Scala的
    @specialized
    功能,你可以做到。首先,警告:该功能有点缺陷,如果你试图将其用于过于复杂的事情,它可能会崩溃。但对于这一点就我的情况而言,这几乎是完美的

    @specialized
    注释为每个基元类型创建单独的类和/或方法,然后在编译器确定基元类型是什么时调用该类和/或方法,而不是泛型版本。唯一的缺点是它完全自动完成所有这一切--您不必填写自己的方法。这就是k这很遗憾,但是使用类型类可以克服这个问题

    让我们看一些代码:

    import java.nio.ByteBuffer
    trait BufferWriter[@specialized(Byte,Int) A]{
      def write(b: ByteBuffer, a: A): Unit
    }
    class ByteWriter extends BufferWriter[Byte] {
      def write(b: ByteBuffer, a: Byte) { b.put(a) }
    }
    class IntWriter extends BufferWriter[Int] {
      def write(b: ByteBuffer, a: Int) { b.putInt(a) }
    }
    object BufferWriters {
      implicit val byteWriter = new ByteWriter
      implicit val intWriter = new IntWriter
    }
    
    这给了我们一个
    BufferWriter
    特性,它是泛型的,但是我们覆盖了我们想要的每个特定的基元类型(在本例中是
    Byte
    Int
    )使用适当的实现。专门化足够聪明,可以将此显式版本与它通常用于专门化的隐藏版本连接起来。因此,您有自定义代码,但如何使用它?这就是隐式VAL的作用(我这样做是为了速度和清晰度):

    如果您将这些东西放在一个文件中,并开始使用
    javap-c-private
    查看类,您将看到使用了适当的原语方法


    (请注意,如果不使用专门化,此策略仍然有效,但必须在循环中装箱值才能将数组复制出来。)

    等待3…2…1.中的类型类答案。添加了
    专用的
    标记,因为这在Scala中意味着某种特定的东西,而这正是您所需要的。@RexKerr:我不认为
    @specialized
    在这里有什么关系,它无助于选择
    putInt
    putLong
    ,“@TravisBrown-不,但这样做会让你不必为自己这样做而感到遗憾,而不是每次都从头开始实现所有东西。向缓冲区添加字节并不是你通常想要调用装箱的事情。你应该在示例中添加对数组的支持。现在看来,这似乎是一种不必要的复杂方式。”ng旧的重载可以实现什么。只要您尝试处理复合结构,类型类模式就会显示出它的优势:使用此模式,无需定义WriteInArray、writeLongArray等。只需编写一个通用writeArray meyhod,它对元素类型+1采用隐式BufferWriter实例,只需很少的ob注意:您可以使用
    隐式对象intWriter[Int]{…}
    对于非泛型的实例,在我看来,它使意图更加清晰,并且可能更有效。+1-这是一个很好的答案,但还有一个额外的缺陷,使它能够以全速工作,即使使用数组也是如此。这并没有让我觉得是一个重大变化(这只是稍微更惯用,更有效),尤其是当他明确要求清理时。他深思熟虑的回答是完整的,只是稍微增强了。正如我上面所建议的,添加对数组的支持更像是对答案的实际更改,这就是为什么我没有自己做,并建议将更改作为注释。没问题!Typecl
    def writeData(buffer: ByteBuffer, data: Int) = buffer.putInt(data)
    def writeData(buffer: ByteBuffer, data: Long) = buffer.putLong(data)
    
    import java.nio.ByteBuffer
    trait BufferWriter[@specialized(Byte,Int) A]{
      def write(b: ByteBuffer, a: A): Unit
    }
    class ByteWriter extends BufferWriter[Byte] {
      def write(b: ByteBuffer, a: Byte) { b.put(a) }
    }
    class IntWriter extends BufferWriter[Int] {
      def write(b: ByteBuffer, a: Int) { b.putInt(a) }
    }
    object BufferWriters {
      implicit val byteWriter = new ByteWriter
      implicit val intWriter = new IntWriter
    }
    
    import BufferWriters._
    def write[@specialized(Byte,Int) A: BufferWriter](b: ByteBuffer, ar: Array[A]) {
      val writer = implicitly[BufferWriter[A]]
      var i = 0
      while (i < ar.length) {
        writer.write(b, ar(i))
        i += 1
      }
    }
    
    val b = ByteBuffer.allocate(6)
    write(b, Array[Byte](1,2))
    write(b, Array[Int](0x03040506))
    scala> b.array
    res3: Array[Byte] = Array(1, 2, 3, 4, 5, 6)