Scala - obtaining a class object from a generic type

18,558

Solution 1

You can cast the result of m.erasure to a Class[T]:

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

This works fine for basic (non-generic) types:

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

But note what happens if I use an instantiated type constructor like List[String] for T:

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

Due to erasure, there is only one Class object for all the possible instantiations of a given type constructor.

Edit

I'm not sure why Manifest[T].erasure returns Class[_] instead of Class[T], but if I had to speculate, I would say it's to discourage you from using the methods on Class which allow you to compare two classes for equality or a subtype relationship, since those methods will give you wrong answers when the Class is parameterized with an instantiated generic type.

For example,

scala> classOf[List[String]] == classOf[List[Int]]
res25: Boolean = true

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

These results might surprise you and/or lead to a bug in your program. Instead of comparing classes this way, you should normally just pass around Manifests instead and compare them, since they have more information*:

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

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

As I understand it, Manifests are meant to supersede Classes for most use cases... but of course, if you're using a framework that requires a Class, there's not much choice. I would suppose that the imposition of casting the result of erasure is just a sort of "acknowledgement of liability" that you're using an inferior product at your own risk :)

* Note that, as the documentation for Manifest says, these manifest comparison operators "should be considered approximations only, as there are numerous aspects of type conformance which are not yet adequately represented in manifests."

Solution 2

def myClassOf[T:ClassTag] = implicitly[ClassTag[T]].runtimeClass

Solution 3

.erasure gives you the type to which your type erases to. If you want a full type information, you should return the Manifest instead.

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]
Share:
18,558
dhg
Author by

dhg

http://dhg.ai

Updated on June 07, 2022

Comments

  • dhg
    dhg almost 2 years

    Is it possible to create a Class object purely from a generic parameter? For example:

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

    Since the type will have been erased at runtime, it seems like this a job for manifests, but I haven't found an example that demonstrates this particular usage. I tried the following, but it doesn't work either:

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

    I suspect this failure is due to, as the API points out, there is no subtype relationship between the type of m.erasure's result and T.

    EDIT: I'm not really interested in what the type T is, I just need an object of type Class[_ <: T] to pass to a method in the hadoop framework.

    Any pointers?

  • dhg
    dhg over 12 years
    So what if I need an actual Class[_ <: T] object? Can I extract such a thing from the Manifest[_ <: T]? (I need it because I'm using a framework that requires passing a Class as a parameter to a method.)
  • dhg
    dhg over 12 years
    Good call on the casting. Any reason why scala doesn't just make Class[T] the return type of erasure? It seems like this can be guaranteed during compilation.
  • Luigi Plinge
    Luigi Plinge over 12 years
    @dhg I think erasure doesn't give you a parameterized Class because by definition it's "A class representing the type U to which T would be erased". The erasure of a class doesn't contain its parameters. But why there's no other method to return the parameterized class, I'm not sure. It might be some limitation of the Class class, which comes from Java. For instance, List(1,2,3).getClass returns a Class[_].
  • dhg
    dhg over 12 years
    @pelotom Thanks for the great response. You analysis is actually really interesting and makes a lot of sense. Like other scala users, I avoid casting because it usually means you're doing something wrong. As you noted, manifests would be the appropriate way to handle things if it weren't for the interaction with an outside framework, and the "acknowledgement of liability" seems spot on.
  • david.perez
    david.perez almost 10 years
    This answer is prepared for Scala 2.10 and above, as Manifest is deprecated.
  • Rich
    Rich about 4 years
    This returns a Class[_] in Scala 2.12 and the docs on "runtimeClass" say "Note that there is no subtyping relationship between T and [the "_"]." Any idea how to actually get a Class[T]?
  • Tristan McPherson
    Tristan McPherson about 4 years
    @Rich, you can cast the Class[_] runtimeClass using .asInstanceOf[Class[T]]
  • Rich
    Rich about 4 years
    That seems to work, thanks. The docs suggest that there will be cases when that will not work though... "there is no subtyping relationship between T and [that]". I can't think of an example at the moment.