1. Overview

The Java Virtual Machine (JVM) is the runtime that runs applications developed in Java and other JVM languages like Scala.

JVM’s Runtime represents the capabilities and properties of the objects created by a program with a metaobject representing their class. That metaobject is defined by java.lang.Class<T>.

We only need to concern ourselves with these metaobjects when we must dynamically inspect and manipulate classes at run time. The feature that allows us to do this in the JVM is called Reflection.

2. Java and java.lang.Class<T>

Class<T> objects allow us to write programs bridging compile-time type safety and dynamic runtime flexibility.

Java provides different mechanisms to obtain a Class<T> object:

  • the static .class property
  • the getClass() method in every object
  • using Class.forName() with a String holding the fully qualified name of the class

Many libraries use Class<T> objects extensively. For example, in the Java Persistence API (JPA), entities are often dynamically instantiated and managed based on their class metadata. 

3. Scala and java.lang.Class<T>

Scala can target different runtimes, but it’s primarily a JVM language that shares core concepts with Java to maintain compatibility, including the core Class and Object classes. 

Scala, like Java, uses java.lang.Class<T> objects to represent the classes loaded at run time, but there are minor differences in accessing them, given the language differences.

In Scala, we have two primary ways to obtain Class[T] objects: classOf[T] and .getClass().

classOf[T] is a generic function and is ideal when we know the exact class we need at compile time. This is equivalent to using the .class property in Java:

"createInstance with classOf[T]" should "successfully create an instance of Person" in {
  val personClass: Class[Person] = classOf[Person]
  val personInstance = createInstance(personClass, Array("John Doe": AnyRef))

  personInstance should not be empty
  personInstance.get.toString should include("Person with name: John Doe")
}

classOf[T] knows the exact types involved at run time, and this is why we can assign its results to a variable with a full generic type, such as Class[Person].

On the other hand, .getClass() is the same as in Java, defined in java.lang.Object, and because of erasure, we lose the exact type of the type parameter. Its return type in Java is Class<?>:

"createInstance with .getClass" should "successfully create another instance of Person" in {
  val dummyPerson = new Person("Dummy")
  val personClass: Class[_ <: Person] = dummyPerson.getClass
  val personInstance = createInstance(personClass, Array("Jane Doe": AnyRef))

  personInstance should not be empty
  personInstance.get.toString should include("Person with name: Jane Doe")
}

"createInstance with .getClass" should "use the type of the variable to bound the type" in {
  val dummyPerson: Object = new Person("Dummy")
  val personClass: Class[_ <: AnyRef] = dummyPerson.getClass
  val personInstance = createInstance(personClass, Array("Jane Doe": AnyRef))

  personInstance should not be empty
  personInstance.get.toString should include("Person with name: Jane Doe")
}

Since Scala 2.9, we obtain some extra information because the Scala compiler infers an upper type bound (T) based on the variable type we call the method on and passes this information to our program by returning Class[? <: T].

We’ll get compilation errors if we try to replace AnyRef with Person in the second test. The compiler gets the extra type information from the type of the variable holding the object. As a result, the type bound in the second test is a wider AnyRef rather than Person.

Scala 3 adopted the Java syntax for wildcard types, and the question mark became the way to write existential types.

4. Conclusion

In this article, we covered the methods to access Class[?] objects: classOf[T], and .getClass().

We discussed their differences and showed how to decide which one to use. classOf[T] offers compile-time type safety, which should be our choice when we know the class name at compile time.

On the other hand, .getClass() provides runtime flexibility and is useful when dealing with dynamic class types or reflection-related operations.

As usual, all the source code used in this tutorial can be found over on GitHub.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.