【问题标题】:Scala - obtaining a class object from a generic typeScala - 从泛型类型获取类对象
【发布时间】:2011-11-21 06:43:43
【问题描述】:

是否可以纯粹从泛型参数创建一个 Class 对象?例如:

class myclass[T] { 
  def something(): Class[_ <: T] = 
    classOf[T] //this doesn't work
}

由于该类型将在运行时被删除,这似乎是清单的一项工作,但我还没有找到演示这种特殊用法的示例。我尝试了以下方法,但它也不起作用:

class myclass[T] { 
  def something()(implicit m: Manifest[T]): Class[_ <: T] = 
    m.erasure //this doesn't work
}

我怀疑此失败是由于,正如 API 指出的那样,m.erasure 的结果类型与T 之间没有子类型关系。

编辑:我对 T 的类型不是很感兴趣,我只需要一个 Class[_ &lt;: T] 类型的对象来传递给 hadoop 框架中的方法。

任何指针?

【问题讨论】:

    标签: generics scala manifest


    【解决方案1】:
    def myClassOf[T:ClassTag] = implicitly[ClassTag[T]].runtimeClass
    

    【讨论】:

    • 这个答案是为 Scala 2.10 及更高版本准备的,因为 Manifest 已被弃用。
    • 这会在 Scala 2.12 中返回 Class[_],并且“runtimeClass”上的文档说“请注意,T 和 [“_”] 之间没有子类型关系。”知道如何实际获得Class[T] 吗?
    • @Rich,您可以使用.asInstanceOf[Class[T]] 强制转换Class[_] runtimeClass
    • 这似乎可行,谢谢。文档表明,在某些情况下,这将不起作用……“T 和 [that] 之间没有子类型关系”。我暂时想不出一个例子。
    【解决方案2】:

    您可以将m.erasure 的结果转换为Class[T]

    class myclass[T] { 
        def something()(implicit m: Manifest[T]): Class[T] = 
            m.erasure.asInstanceOf[Class[T]]
    }
    

    这适用于基本(非泛型)类型:

    scala> new myclass[String]().something()
    res5: Class[String] = class java.lang.String
    

    但请注意,如果我将 List[String] 之类的实例化类型构造函数用于 T,会发生什么情况:

    scala> new myclass[List[String]]().something()
    res6: Class[List[String]] = class scala.collection.immutable.List
    

    由于擦除,对于给定类型构造函数的所有可能实例化,只有一个 Class 对象。

    编辑

    我不确定为什么 Manifest[T].erasure 返回 Class[_] 而不是 Class[T],但如果我不得不推测,我会说这是为了阻止您使用 Class 上的方法,这些方法允许您比较两个用于相等或子类型关系的类,因为当 Class 使用实例化的泛型类型参数化时,这些方法会给您错误的答案。

    例如,

    scala> classOf[List[String]] == classOf[List[Int]]
    res25: Boolean = true
    
    scala> classOf[List[String]].isAssignableFrom(classOf[List[Int]])
    res26: Boolean = true
    

    这些结果可能会让您感到惊讶和/或导致您的程序出现错误。您通常不应以这种方式比较类,而应直接传递 Manifests 并比较它们,因为它们有更多信息*:

    scala> manifest[List[String]] == manifest[List[Int]]
    res27: Boolean = false
    
    scala> manifest[List[String]] >:> manifest[List[Int]]
    res28: Boolean = false
    

    据我了解,Manifests 旨在取代 Classes 对于大多数用例...但当然,如果您使用的框架需要 Class,则没有太多选择。我认为强制转换erasure 的结果只是一种“承认责任”,您使用劣质产品需要您自担风险:)

    * 请注意,正如documentation for Manifest 所说,这些清单比较运算符“应仅视为近似值,因为类型一致性的许多方面尚未在清单中充分表示。”

    【讨论】:

    • 很好的选角。为什么 scala 不只是使 Class[T] 成为 erasure 的返回类型?在编译过程中似乎可以保证这一点。
    • @dhg 我认为erasure 不会给你一个参数化的Class,因为根据定义它是“一个代表类型U 的类,T 将被删除”。类的擦除不包含其参数。但是为什么没有其他方法可以返回参数化类,我不确定。这可能是来自 Java 的 Class 类的一些限制。例如,List(1,2,3).getClass 返回一个Class[_]
    • @pelotom 感谢您的好评。您的分析实际上非常有趣并且很有意义。像其他 scala 用户一样,我避免强制转换,因为这通常意味着你做错了什么。正如您所指出的,如果不是为了与外部框架进行交互,清单将是处理事情的适当方式,并且“承认责任”似乎是正确的。
    【解决方案3】:

    .erasure 为您提供您的类型擦除到的类型。如果您需要完整的类型信息,则应返回 Manifest

    scala> class MyClass[A] {
         |   def stuff(implicit m: Manifest[A]): Class[_] = m.erasure
         | }
    defined class MyClass
    
    scala> new MyClass[Int].stuff
    res551: java.lang.Class[_] = int
    
    scala> new MyClass[List[Int]].stuff
    res552: java.lang.Class[_] = class scala.collection.immutable.List
    
    scala> class MyClass[A] {
         |   def stuff(implicit m: Manifest[A]): Manifest[A] = m
         | }
    defined class MyClass
    
    scala> new MyClass[Int].stuff
    res553: Manifest[Int] = Int
    
    scala> new MyClass[List[Int]].stuff
    res554: Manifest[List[Int]] = scala.collection.immutable.List[Int]
    

    【讨论】:

    • 如果我需要一个实际的Class[_ &lt;: T] 对象怎么办?我可以从Manifest[_ &lt;: T] 中提取这样的东西吗? (我需要它,因为我使用的框架需要将 Class 作为参数传递给方法。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-26
    • 2021-10-15
    • 1970-01-01
    • 2012-02-28
    • 1970-01-01
    • 1970-01-01
    • 2011-01-17
    相关资源
    最近更新 更多