Java Class getName() vs getSimpleName()

Work with Java reflection and invoke getSimpleName() met java.lang.NoClassDefFoundError

Use getName() instead, the logic seems work well.

Before compare these two methods’ difference go through the code quickly.

Class::getName()

for getName()

1
2
3
4
5
6
public String getName() {
String name = this.name;
if (name == null)
this.name = name = getName0();
return name;
}

and the getName0() is native method

1
private native String getName0();

Class::getSimpleName()

for getSimpleName()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public String getSimpleName() {
if (isArray())
return getComponentType().getSimpleName()+"[]";

String simpleName = getSimpleBinaryName();
if (simpleName == null) { // top level class
simpleName = getName();
return simpleName.substring(simpleName.lastIndexOf(".")+1); // strip the package name
}
// According to JLS3 "Binary Compatibility" (13.1) the binary
// name of non-package classes (not top level) is the binary
// name of the immediately enclosing class followed by a '$' followed by:
// (for nested and inner classes): the simple name.
// (for local classes): 1 or more digits followed by the simple name.
// (for anonymous classes): 1 or more digits.

// Since getSimpleBinaryName() will strip the binary name of
// the immediatly enclosing class, we are now looking at a
// string that matches the regular expression "\$[0-9]*"
// followed by a simple name (considering the simple of an
// anonymous class to be the empty string).

// Remove leading "\$[0-9]*" from the name
int length = simpleName.length();
if (length < 1 || simpleName.charAt(0) != '$')
throw new InternalError("Malformed class name");
int index = 1;
while (index < length && isAsciiDigit(simpleName.charAt(index)))
index++;
// Eventually, this is the empty string iff this is an anonymous class
return simpleName.substring(index);
}

The main part is

1
String simpleName = getSimpleBinaryName();

And then get enclosingClass:

1
2
3
4
5
6
7
8
9
10
11
private String getSimpleBinaryName() {
Class<?> enclosingClass = getEnclosingClass();
if (enclosingClass == null) // top level class
return null;
// Otherwise, strip the enclosing class' name
try {
return getName().substring(enclosingClass.getName().length());
} catch (IndexOutOfBoundsException ex) {
throw new InternalError("Malformed class name", ex);
}
}

what is enclosingClass:

1
2
3
4
5
6
// There are five kinds of classes (or interfaces):
// a) Top level classes
// b) Nested classes (static member classes)
// c) Inner classes (non-static member classes)
// d) Local classes (named classes declared within a method)
// e) Anonymous classes

in my case it tend to be a) Top level classes, so next part of code goes to getDeclaringClass()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// JVM Spec 4.8.6: A class must have an EnclosingMethod
// attribute if and only if it is a local class or an
// anonymous class.
EnclosingMethodInfo enclosingInfo = getEnclosingMethodInfo();
Class<?> enclosingCandidate;

if (enclosingInfo == null) {
// This is a top level or a nested class or an inner class (a, b, or c)
enclosingCandidate = getDeclaringClass();
} else {
Class<?> enclosingClass = enclosingInfo.getEnclosingClass();
// This is a local class or an anonymous class (d or e)
if (enclosingClass == this || enclosingClass == null)
throw new InternalError("Malformed enclosing method information");
else
enclosingCandidate = enclosingClass;
}

and then getDeclaringClass0() will be used.

1
2
3
4
5
6
7
8
9
10
@CallerSensitive
public Class<?> getDeclaringClass() throws SecurityException {
final Class<?> candidate = getDeclaringClass0();

if (candidate != null)
candidate.checkPackageAccess(
ClassLoader.getClassLoader(Reflection.getCallerClass()), true);
return candidate;
}

which is a native method:

1
private native Class<?> getDeclaringClass0();

Comparision

From the code before this section, aboveously the getSimpleName() always call native method but getName() may use Class’s local variable directly.

So see the local variable before we come to conclusion.

1
2
// cache the name to reduce the number of calls into the VM
private transient String name;

the name used by getName() use a transient String as cache to reduce the number of calls into VM.

And combine to getName()’s implement, the first time getName0() invoked, this name is set.

And go for Java doc:

1
public String getSimpleName()

Returns the simple name of the underlying class as given in the source code. Returns an empty string if the underlying class is anonymous.

The simple name of an array is the simple name of the component type with “[]” appended. In particular the simple name of an array whose component type is anonymous is “[]”.

  • Returns:

    the simple name of the underlying class

  • Since:

    1.5

1
public String getName()

Returns the name of the entity (class, interface, array class, primitive type, or void) represented by this Class object, as a String.

If this class object represents a reference type that is not an array type then the binary name of the class is returned, as specified by The Java™ Language Specification.

If this class object represents a primitive type or void, then the name returned is a String equal to the Java language keyword corresponding to the primitive type or void.

If this class object represents a class of arrays, then the internal form of the name consists of the name of the element type preceded by one or more ‘[‘ characters representing the depth of the array nesting. The encoding of element type names is as follows:

Element Type Encoding
boolean Z
byte B
char C
class or interface Lclassname;
double D
float F
int I
long J
short S

The class or interface name classname is the binary name of the class specified above.

Examples:

1
2
3
4
5
6
7
8
9
String.class.getName()
returns "java.lang.String"
byte.class.getName()
returns "byte"
(new Object[3]).getClass().getName()
returns "[Ljava.lang.Object;"
(new int[3][4][5][6][7][8][9]).getClass().getName()
returns "[[[[[[[I"

  • Returns:

    the name of the class or interface represented by this object.

and more details for the error:

1
2
public class NoClassDefFoundError
extends LinkageError

Thrown if the Java Virtual Machine or a ClassLoader instance tries to load in the definition of a class (as part of a normal method call or as part of creating a new instance using the new expression) and no definition of the class could be found.

The searched-for class definition existed when the currently executing class was compiled, but the definition can no longer be found.

So that means class found at compile time but not available at runtime Refer this link

Check for our configuration:

1
2
3
4
5
6
7
<dependency>
<groupId>xxx</groupId>
<artifactId>xxx</artifactId>
<version>1.1.1</version>
<scope>system</scope>
<systemPath>${project.basedir}/ext-libs/xxx</systemPath>
</dependency>

use a system scope.

According to maven doc:

There are 6 scopes:

  • compile
    This is the default scope, used if none is specified. Compile dependencies are available in all classpaths of a project. Furthermore, those dependencies are propagated to dependent projects.
  • provided
    This is much like compile, but indicates you expect the JDK or a container to provide the dependency at runtime. For example, when building a web application for the Java Enterprise Edition, you would set the dependency on the Servlet API and related Java EE APIs to scope provided because the web container provides those classes. A dependency with this scope is added to the classpath used for compilation and test, but not the runtime classpath. It is not transitive.
  • runtime
    This scope indicates that the dependency is not required for compilation, but is for execution. Maven includes a dependency with this scope in the runtime and test classpaths, but not the compile classpath.
  • test
    This scope indicates that the dependency is not required for normal use of the application, and is only available for the test compilation and execution phases. This scope is not transitive. Typically this scope is used for test libraries such as JUnit and Mockito. It is also used for non-test libraries such as Apache Commons IO if those libraries are used in unit tests (src/test/java) but not in the model code (src/main/java).
  • system
    This scope is similar to provided except that you have to provide the JAR which contains it explicitly. The artifact is always available and is not looked up in a repository.
  • import
    This scope is only supported on a dependency of type pom in the <dependencyManagement> section. It indicates the dependency is to be replaced with the effective list of dependencies in the specified POM’s <dependencyManagement> section. Since they are replaced, dependencies with a scope of import do not actually participate in limiting the transitivity of a dependency.

system most like provided but a dependency with this scope is added to the classpath used for compilation and test, but not the runtime classpath

so runtime getSimpleName() will met exception.

Class loader

get back to the error call trace again:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Caused by: java.lang.NoClassDefFoundError: xxxxx
at java.lang.ClassLoader.defineClass1(Native Method) ~[?:1.8.0_161]
at java.lang.ClassLoader.defineClass(ClassLoader.java:763) ~[?:1.8.0_161]
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) ~[?:1.8.0_161]
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467) ~[?:1.8.0_161]
at java.net.URLClassLoader.access$100(URLClassLoader.java:73) ~[?:1.8.0_161]
at java.net.URLClassLoader$1.run(URLClassLoader.java:368) ~[?:1.8.0_161]
at java.net.URLClassLoader$1.run(URLClassLoader.java:362) ~[?:1.8.0_161]
at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_161]
at java.net.URLClassLoader.findClass(URLClassLoader.java:361) ~[?:1.8.0_161]
at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[?:1.8.0_161]
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338) ~[?:1.8.0_161]
at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[?:1.8.0_161]
at java.lang.Class.getDeclaringClass0(Native Method) ~[?:1.8.0_161]

and another class not found exception:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Caused by: java.lang.ClassNotFoundException: xxxxx
at java.net.URLClassLoader.findClass(URLClassLoader.java:381) ~[?:1.8.0_161]
at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[?:1.8.0_161]
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338) ~[?:1.8.0_161]
at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[?:1.8.0_161]
at java.lang.ClassLoader.defineClass1(Native Method) ~[?:1.8.0_161]
at java.lang.ClassLoader.defineClass(ClassLoader.java:763) ~[?:1.8.0_161]
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) ~[?:1.8.0_161]
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467) ~[?:1.8.0_161]
at java.net.URLClassLoader.access$100(URLClassLoader.java:73) ~[?:1.8.0_161]
at java.net.URLClassLoader$1.run(URLClassLoader.java:368) ~[?:1.8.0_161]
at java.net.URLClassLoader$1.run(URLClassLoader.java:362) ~[?:1.8.0_161]
at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_161]
at java.net.URLClassLoader.findClass(URLClassLoader.java:361) ~[?:1.8.0_161]
at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[?:1.8.0_161]
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338) ~[?:1.8.0_161]
at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[?:1.8.0_161]
at java.lang.Class.getDeclaringClass0(Native Method) ~[?:1.8.0_161]

when try to get simple name of class A extends B

find A and try to define A but need to define B first, but B is not available at runtime so a exception raised.