The reflection classes and methods as well as class loaders etc. need the so called \"binary\" names of classes to work with.
The question is, how does one get the binar
I think its a safe bet that canonical names specify unique classes. As mentioned above javac will not let you create two classes with the same canonical name from withen a single compilation unit. If you have 2 compilations then you can get into trouble regarding which class you load, but at that point I'd be more worried about a library's package name colliding with your package names, which is avoided by all but the malicious.
For this reason, I think its a safe bet to assume you wont run into that scenario. Along those lines, for those who are interested, I implemented the OP's suggestion (flipping $
s to .
s), and simply throwing a ClassNotFoundException
in the event that it doesn't find any classes with that canonical name, or if it finds two or more that have that name.
/**
* Returns the single class at the specified canonical name, or throws a {@link java.lang.ClassNotFoundException}.
*
* Read about the issues of fully-qualified class paths vs the canonical name string
* discussed here.
*/
public static Class classForCanonicalName(String canonicalName)
throws ClassNotFoundException {
if (canonicalName == null) { throw new IllegalArgumentException("canonicalName"); }
int lastDotIndex = canonicalName.length();
boolean hasMoreDots = true;
String attemptedClassName = canonicalName;
Set resolvedClasses = new HashSet<>();
while (hasMoreDots) try {
Class resolvedClass = Class.forName(attemptedClassName);
resolvedClasses.add(resolvedClass);
}
catch (ClassNotFoundException e) {
continue;
}
finally {
if(hasMoreDots){
lastDotIndex = attemptedClassName.lastIndexOf('.');
attemptedClassName = new StringBuilder(attemptedClassName)
.replace(lastDotIndex, lastDotIndex + 1, "$")
.toString();
hasMoreDots = attemptedClassName.contains(".");
}
}
if (resolvedClasses.isEmpty()) {
throw new ClassNotFoundException(canonicalName);
}
if (resolvedClasses.size() >= 2) {
StringBuilder builder = new StringBuilder();
for (Class clazz : resolvedClasses) {
builder.append("'").append(clazz.getName()).append("'");
builder.append(" in ");
builder.append("'").append(
clazz.getProtectionDomain().getCodeSource() != null
? clazz.getProtectionDomain().getCodeSource().getLocation()
: ""
).append("'");
builder.append(System.lineSeparator());
}
builder.replace(builder.length() - System.lineSeparator().length(), builder.length(), "");
throw new ClassNotFoundException(
"found multiple classes with the same canonical names:" + System.lineSeparator() +
builder.toString()
);
}
return resolvedClasses.iterator().next();
}
it still annoys me greatly that the "expected" flow is to hit that catch(NoClass) continue
code, but if you've ever told eclipse or intelliJ to auto-break on any exceptions thrown, you'll know this kind of behavior is par for the course.