Adding local classes, lambdas and the toString() method to complete the previous two answers. Further, I add arrays of lambdas and arrays of anonymous classes (which do not make any sense in practice though):
package com.example; public final class TestClassNames { private static void showClass(Class<?> c) { System.out.println("getName(): " + c.getName()); System.out.println("getCanonicalName(): " + c.getCanonicalName()); System.out.println("getSimpleName(): " + c.getSimpleName()); System.out.println("toString(): " + c.toString()); System.out.println(); } private static void x(Runnable r) { showClass(r.getClass()); showClass(java.lang.reflect.Array.newInstance(r.getClass(), 1).getClass()); // Obtains an array class of a lambda base type. } public static class NestedClass {} public class InnerClass {} public static void main(String[] args) { class LocalClass {} showClass(void.class); showClass(int.class); showClass(String.class); showClass(Runnable.class); showClass(SomeEnum.class); showClass(SomeAnnotation.class); showClass(int[].class); showClass(String[].class); showClass(NestedClass.class); showClass(InnerClass.class); showClass(LocalClass.class); showClass(LocalClass[].class); Object anonymous = new java.io.Serializable() {}; showClass(anonymous.getClass()); showClass(java.lang.reflect.Array.newInstance(anonymous.getClass(), 1).getClass()); // Obtains an array class of an anonymous base type. x(() -> {}); } } enum SomeEnum { BLUE, YELLOW, RED; } @interface SomeAnnotation {}
This is the full output:
getName(): void getCanonicalName(): void getSimpleName(): void toString(): void getName(): int getCanonicalName(): int getSimpleName(): int toString(): int getName(): java.lang.String getCanonicalName(): java.lang.String getSimpleName(): String toString(): class java.lang.String getName(): java.lang.Runnable getCanonicalName(): java.lang.Runnable getSimpleName(): Runnable toString(): interface java.lang.Runnable getName(): com.example.SomeEnum getCanonicalName(): com.example.SomeEnum getSimpleName(): SomeEnum toString(): class com.example.SomeEnum getName(): com.example.SomeAnnotation getCanonicalName(): com.example.SomeAnnotation getSimpleName(): SomeAnnotation toString(): interface com.example.SomeAnnotation getName(): [I getCanonicalName(): int[] getSimpleName(): int[] toString(): class [I getName(): [Ljava.lang.String; getCanonicalName(): java.lang.String[] getSimpleName(): String[] toString(): class [Ljava.lang.String; getName(): com.example.TestClassNames$NestedClass getCanonicalName(): com.example.TestClassNames.NestedClass getSimpleName(): NestedClass toString(): class com.example.TestClassNames$NestedClass getName(): com.example.TestClassNames$InnerClass getCanonicalName(): com.example.TestClassNames.InnerClass getSimpleName(): InnerClass toString(): class com.example.TestClassNames$InnerClass getName(): com.example.TestClassNames$1LocalClass getCanonicalName(): null getSimpleName(): LocalClass toString(): class com.example.TestClassNames$1LocalClass getName(): [Lcom.example.TestClassNames$1LocalClass; getCanonicalName(): null getSimpleName(): LocalClass[] toString(): class [Lcom.example.TestClassNames$1LocalClass; getName(): com.example.TestClassNames$1 getCanonicalName(): null getSimpleName(): toString(): class com.example.TestClassNames$1 getName(): [Lcom.example.TestClassNames$1; getCanonicalName(): null getSimpleName(): [] toString(): class [Lcom.example.TestClassNames$1; getName(): com.example.TestClassNames$$Lambda$1/1175962212 getCanonicalName(): com.example.TestClassNames$$Lambda$1/1175962212 getSimpleName(): TestClassNames$$Lambda$1/1175962212 toString(): class com.example.TestClassNames$$Lambda$1/1175962212 getName(): [Lcom.example.TestClassNames$$Lambda$1; getCanonicalName(): com.example.TestClassNames$$Lambda$1/1175962212[] getSimpleName(): TestClassNames$$Lambda$1/1175962212[] toString(): class [Lcom.example.TestClassNames$$Lambda$1;
So, here are the rules. First, lets start with primitive types and void:
- If the class object represents a primitive type or
void, all the four methods simply returns its name.
Now the rules for the getName() method:
- Every non-lambda and non-array class or interface (i.e, top-level, nested, inner, local and anonymous) has a name (which is returned by
getName()) that is the package name followed by a dot (if there is a package), followed by the name of its class-file as generated by the compiler (whithout the suffix .class). If there is no package, it is simply the name of the class-file. If the class is an inner, nested, local or anonymous class, the compiler should generate at least one $ in its class-file name. Note that for anonymous classes, the class name would end with a dollar-sign followed by a number. - Lambda class names are generally unpredictable, and you shouldn't care about they anyway. Exactly, their name is the name of the enclosing class, followed by
$$Lambda$, followed by a number, followed by a slash, followed by another number. - The class descriptor of the primitives are
Z for boolean, B for byte, S for short, C for char, I for int, J for long, F for float and D for double. For non-array classes and interfaces the class descriptor is L followed by what is given by getName() followed by ;. For array classes, the class descriptor is [ followed by the class descriptor of the component type (which may be itself another array class). - For array classes, the
getName() method returns its class descriptor. This rule seems to fail only for array classes whose the component type is a lambda (which possibly is a bug), but hopefully this should not matter anyway because there is no point even on the existence of array classes whose component type is a lambda.
Now, the toString() method:
- If the class instance represents an interface (or an annotation, which is a special type of interface), the
toString() returns "interface " + getName(). If it is a primitive, it returns simply getName(). If it is something else (a class type, even if it is a pretty weird one), it returns "class " + getName().
The getCanonicalName() method:
- For top-level classes and interfaces, the
getCanonicalName() method returns just what the getName() method returns. - The
getCanonicalName() method returns null for anonymous or local classes and for array classes of those. - For inner and nested classes and interfaces, the
getCanonicalName() method returns what the getName() method would replacing the compiler-introduced dollar-signs by dots. - For array classes, the
getCanonicalName() method returns null if the canonical name of the component type is null. Otherwise, it returns the canonical name of the component type followed by [].
The getSimpleName() method:
- For top-level, nested, inner and local classes, the
getSimpleName() returns the name of the class as written in the source file. - For anonymous classes the
getSimpleName() returns an empty String. - For lambda classes the
getSimpleName() just returns what the getName() would return without the package name. This do not makes much sense and looks like a bug for me, but there is no point in calling getSimpleName() on a lambda class to start with. - For array classes the
getSimpleName() method returns the simple name of the component class followed by []. This have the funny/weird side-effect that array classes whose component type is an anonymous class have just [] as their simple names.