The Class object in Java
getClass(), .class literals, Class.forName.
One ID card per type
Every loaded type has **exactly one Class object describing it. Three ways to get it: obj.getClass() (the object's runtime type), a class literal** like String.class (type known at compile time), or **Class.forName("java.util.ArrayList")** (look up by name at runtime).
Class<?> a = "hi".getClass();
Class<?> b = String.class;
Class<?> c = Class.forName("java.lang.String");
// a == b && b == cRuntime type wins
What does this print?
Object o = 42;
Number n = 3.5;
System.out.println(o.getClass().getSimpleName());
System.out.println(n.getClass().getSimpleName());Object NumberInteger Doubleint double
Show the answer
getClass() reports the runtime class of the object, not the declared type of the variable. Autoboxing turned 42 into an Integer and 3.5 into a Double. Likewise, Animal a = new Dog() gives Dog.class.
Erasure: one Class per raw type
Generic type arguments are erased at runtime. List<String> and List<Integer> built with new ArrayList<>() share one Class object: ArrayList.class.
List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
a.getClass() == b.getClass(); // truePrimitives and arrays
What does this print?
System.out.println(int.class.getName());
System.out.println(int[].class.getName());
var arr = String[].class;
System.out.println(arr.getSimpleName());int [I String[]int int[] String[]Integer [I [String
Show the answer
Even primitives have Class objects: int.class (the same object as Integer.TYPE). Array classes use JVM descriptor names in getName(), so int[] is **[I**, while getSimpleName() gives the friendly String[].
Names are just strings
Class.forName takes a plain string, so the compiler can't check it. A typo compiles fine and fails only at runtime with the checked **ClassNotFoundException**.
void main() throws Exception {
// typo: ArrayLst
Class.forName("java.util.ArrayLst");
} // ClassNotFoundExceptionConfig-driven loading
A plugin system reads names like com.acme.CsvExporter from a config file. A class literal is impossible there: it needs the type at compile time. Class.forName(className) finds it at runtime. Reflection also uses primitive Class objects to describe parameters: getMethod("max", int.class, int.class).
Key takeaways
- obj.getClass() returns the runtime type
- Foo.class works for any type, even int.class
- Class.forName(name) loads and initializes by name
- Generics are erased: one Class per raw type
💡 A Class object is a type's ID card: one card per type, which anyone can ask to see at runtime.
int.class == Integer.TYPE is true, but int.class == Integer.class is false: the primitive int and the wrapper Integer have different Class objects.
Practice questions
What does this print?
Object o = "hello";
List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
System.out.println(o.getClass().getSimpleName());
System.out.println(a.getClass() == b.getClass());
System.out.println(int[].class.getName());- String false int[]
- String true [I
- String true int[]
- Object true [I
Check your answer
String true [I. getClass() reports the runtime type (String). Both lists are just ArrayList at runtime. Array classes use JVM descriptor names, so int[] is "[I".
What does this print?
void main() throws Exception {
String name = "java.util.ArrayLst";
Class<?> c = Class.forName(name);
System.out.println(c.getSimpleName());
}- Throws ClassNotFoundException
- null
- Compile error
- ArrayLst
Check your answer
Throws ClassNotFoundException. Class names passed to forName are just strings, so the typo compiles fine and fails at runtime with the checked ClassNotFoundException.