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 Strings containing their representation, or why use enums 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 ClassLoaders.
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.