First of all, Class
is a type dedicated to a certain purpose. There are a lot of classes, whose instances we could simply replace by a String
and it would work if we accept a bit of ambiguity and possibly a performance loss. E.g. why use numbers instead of String
s containing their representation, or why use enum
s instead of their names? So having an instance of the dedicated type ensures that the creating, lookup, parsing or whatever action is required to get that instance has already successfully performed.
So having a Class
object representing int.class
, you known that you are referring to an existing type, something you can’t say about the String
"int"
. A String
parameter doesn’t necessarily refer to an existing type— it doesn’t even have to contain a valid name. And if you lookup a constructor having two int
arguments, passing "int", "int"
would imply to make the entire work of verifying the correctness and looking up the appropriate type twice. And so on…
And, since the restrictions of the Java programming language do not apply in the JVM, a String
is ambiguous. It’s not clear whether "int"
refers to a class
named int
or the primitive type int
. Note that when you call loadClass("int")
on a ClassLoader
, it is always assumed that you are referring to a class
named int
as that method is not appropriate to look up primitive types. That’s the reason why int.class
gets compiled to an access to Integer.TYPE
, as primitive types can’t be looked up the same way as reference types.
Further, as already explained here, a name is ambiguous at runtime as there can be multiple classes having the same name, being defined by different ClassLoader
s.
See JVMS §5.3 “Creation and Loading”:
At run time, a class or interface is determined not by its name alone, but by a pair: its binary name (§4.2.1) and its defining class loader.
And also JLS §12.2 “Loading of Classes and Interfaces”
Well-behaved class loaders maintain these properties:
Given the same name, a good class loader should always return the same class object.
If a class loader L1 delegates loading of a class C to another loader L2, then for any type T that occurs as the direct superclass or a direct superinterface of C, or as the type of a field in C, or as the type of a formal parameter of a method or constructor in C, or as a return type of a method in C, L1 and L2 should return the same Class object.
A malicious class loader could violate these properties. However, it could not undermine the security of the type system, because the Java Virtual Machine guards against this.