Scala泛型方法-使用集合时没有ClassTag可用于T

qhhrdooz  于 2022-11-09  发布在  Scala
关注(0)|答案(1)|浏览(250)

我想利用Scala反射来隐式传递ClassTag。
关于如何实现这一点,StackOverflow上有很多解决方案。

import scala.reflect.ClassTag

// animal input
trait AnimalInput {}
case class DogInput() extends AnimalInput
case class CatInput() extends AnimalInput

// animals
trait Animal[T <: AnimalInput] {
  def getInput(implicit ct: ClassTag[T]): Class[T] = {
    ct.runtimeClass.asInstanceOf[Class[T]]
  }
}
object Dog extends Animal[DogInput] {}
object Cat extends Animal[CatInput] {}

我可以测试这个很好用:

println(Dog.getInput) // Success: "DogInput"
println(Cat.getInput) // Success: "CatInput"

问题是,当我在任何集合中引用这些对象时,我就遇到了麻烦:

// Failure: "No ClassTag available for animal.T"
List(Dog, Cat).foreach(animal => println(animal.getInput))

我想我理解为什么会发生这种情况,但我不确定如何解决这个问题。
提前感谢您的帮助!

vqlkdk9b

vqlkdk9b1#

实际上,我不能复制No ClassTag available for animal.T。您的代码在Scala 2.13.9:https://scastie.scala-lang.org/DmytroMitin/lonnNg0fR1qb4Lg7ChDBqg中编译和运行
也许失败的意思是,对于集合,您不会收到类DogInputCatInput

这个问题非常接近最近的问题Trying to extract the TypeTag of a Sequence of classes that extend a trait with different generic type parameters
请看那里的原因。
或者使用异类集合

object classPoly extends Poly1 {
  implicit def cse[T <: AnimalInput : ClassTag, A](implicit
    ev: A <:< Animal[T]
  ): Case.Aux[A, Class[T]] =
    at(_ => classTag[T].runtimeClass.asInstanceOf[Class[T]])
}

(Dog :: Cat :: HNil).map(classPoly).toList.foreach(println)
// class DogInput
// class CatInput

或使用运行时反射

trait Animal[T <: AnimalInput] {
  def getInput: Class[T] = {
    val classSymbol = runtimeMirror.classSymbol(this.getClass)
    val animalSymbol = typeOf[Animal[_]].typeSymbol 
    val extendeeType = classSymbol.typeSignature.baseType(animalSymbol)
    val extenderSymbol = extendeeType.typeArgs.head.typeSymbol.asClass
    runtimeMirror.runtimeClass(extenderSymbol).asInstanceOf[Class[T]]
  }
}

List(Dog, Cat).foreach(animal => println(animal.getInput))
// class DogInput
// class CatInput

最简单的是

trait Animal[T <: AnimalInput] {
  def getInput: Class[T]
}
object Dog extends Animal[DogInput] {
  val getInput = classOf[DogInput]
}
object Cat extends Animal[CatInput] {
  val getInput = classOf[CatInput]
}

另一个选项是磁铁图案(12345 6)

trait AnimalMagnet[T <: AnimalInput] {
  def getInput: Class[T]
}

import scala.language.implicitConversions

implicit def animalToMagnet[A, T <: AnimalInput : ClassTag](a: A)(implicit
  ev: A <:< Animal[T]
): AnimalMagnet[T] = new AnimalMagnet[T] {
  override def getInput: Class[T] = classTag[T].runtimeClass.asInstanceOf[Class[T]]
}

List[AnimalMagnet[_]](Dog, Cat).foreach(animal => println(animal.getInput))
//class DogInput
//class CatInput

您还可以将ClassTag隐式从方法移动到特征(并使特征成为抽象类)

abstract class Animal[T <: AnimalInput](implicit ct: ClassTag[T]) {
  def getInput: Class[T] = ct.runtimeClass.asInstanceOf[Class[T]]
}

List(Dog, Cat).foreach(animal => println(animal.getInput))
// class DogInput
// class CatInput

相关问题