1

Hello ) I have one abstract class and several concrete classes in Java

abstract class SpecificRecordBase {}
class A extends SpecificRecordBase 
class B extends SpecificRecordBase
....

Also I have a map in Scala. I want to put key String and value as a class to use it in method:

def test(m: Map[String, Class[_ <: SpecificRecordBase]]):Unit = ???

I tried to ClassTag(ClassLoader.getSystemClassLoader.loadClass(stringClassName)).runtimeClass but got an error. Also I tried to create method and use it

  def getClass[T <: SpecificRecordBase](ct: ClassTag[T]): Class[T] = ct.runtimeClass

And also have an error
 Error:(10, 73) type mismatch;
 found   : Class[_$1] where type _$1
 required: Class[T]
  def getClass[T <: SpecificRecordBase](ct: ClassTag[T]): Class[T] = ct.runtimeClass

Could you help me please?

Vadim
  • 753
  • 8
  • 22

2 Answers2

2

The problem is the signature of runtimeClass:

def runtimeClass: Class[_]

A class representing the type U to which T would be erased. Note that there is no subtyping relationship between T and U.

The reason is types like Any, Nothing, etc. But really, ClassTag isn't useful here because you are doing ClassTag(<something>).runtimeClass which can be simplified to <something>.

So just cast it:

ClassLoader.getSystemClassLoader.loadClass(stringClassName).asInstanceOf[Class[_ <: SpecificRecordBase]]

(EDIT: as pointed out be Mario Galic, this should be

ClassLoader.getSystemClassLoader.loadClass(stringClassName).asSubclass(classOf[SpecificRecordBase])

Similar changes applied below.)

or even simpler

Class.forName(stringClassName).asSubclass(classOf[SpecificRecordBase])

If you aren't sure whether you'll get a subtype of SpecificRecordBase, you can test it:

val cls = Class.forName(stringClassName)
Try { cls.asSubclass(classOf[SpecificRecordBase]) } match {
  case Success(cls1) =>
    // handle a subclass
  case Failure(_) =>
    // handle a non-subclass
}

or without exception handling

val cls = Class.forName(stringClassName)
if (classOf[SpecificRecordBase].isAssignableFrom(cls)) {
  val cls1 = cls.asInstanceOf[Class[_ <: SpecificRecordBase]]
  // handle a subclass
} else {
  // handle a non-subclass
}
Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
2

Try asSubclass like so

def getSubclass[T <: SpecificRecordBase](implicit ct: ClassTag[T]): Class[_ <: SpecificRecordBase] =
  ct.runtimeClass.asSubclass(classOf[SpecificRecordBase])

getSubclass[A]
getSubclass[B]
// res0: Class[_ <: SpecificRecordBase] = class A
// res1: Class[_ <: SpecificRecordBase] = class B

Out-of-the-box asSubclass Java implementation is similar to Alexey's Scala one

public <U> Class<? extends U> asSubclass(Class<U> clazz) {
    if (clazz.isAssignableFrom(this))
        return (Class<? extends U>) this;
    else
        throw new ClassCastException(this.toString());
}
Mario Galic
  • 47,285
  • 6
  • 56
  • 98