Java类中的规范名称、简单名称和类名有什么区别?

What is the difference between canonical name, simple name and class name in Java Class?

在爪哇,这些区别是什么?

1
2
3
4
Object o1 = ....
o1.getClass().getSimpleName();
o1.getClass().getName();
o1.getClass().getCanonicalName();

我已经多次检查了JavaDoc,但这并不能很好地解释它。我还进行了一次测试,但这并没有反映出这些方法被调用的背后的任何真正意义。


如果你对某件事不确定,试着先写一个测试。

我这样做了:

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
33
34
35
36
import java.io.Serializable;
import java.util.HashMap;

class ClassNameTest {
    public static void main(String[] arguments) {
        //primitive
        System.out.println(int.class.getName());
        System.out.println(int.class.getCanonicalName());
        System.out.println(int.class.getSimpleName());
        System.out.println(int.class.getTypeName()); // Added in Java 8

        System.out.println();

        //class
        System.out.println(String.class.getName());
        System.out.println(String.class.getCanonicalName());
        System.out.println(String.class.getSimpleName());
        System.out.println(String.class.getTypeName());

        System.out.println();

        //inner class
        System.out.println(HashMap.SimpleEntry.class.getName());
        System.out.println(HashMap.SimpleEntry.class.getCanonicalName());
        System.out.println(HashMap.SimpleEntry.class.getSimpleName());
        System.out.println(HashMap.SimpleEntry.class.getTypeName());

        System.out.println();

        //anonymous inner class
        System.out.println(new Serializable(){}.getClass().getName());
        System.out.println(new Serializable(){}.getClass().getCanonicalName());
        System.out.println(new Serializable(){}.getClass().getSimpleName());
        System.out.println(new Serializable(){}.getClass().getTypeName());
    }
}

印刷品:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int
int
int
int

java.lang.String
java.lang.String
String
java.lang.String

java.util.AbstractMap$SimpleEntry
java.util.AbstractMap.SimpleEntry
SimpleEntry
java.util.AbstractMap$SimpleEntry

ClassNameTest$1
null

ClassNameTest$4

在最后一个块中有一个空行,其中getSimpleName返回一个空字符串。

结果是:

  • 该名称是用于动态加载类的名称,例如,使用默认的ClassLoader调用Class.forName。在某个ClassLoader的范围内,所有类都有唯一的名称。
  • 规范名称是将在导入语句中使用的名称。它可能在toString或日志操作期间有用。当javac编译器具有类路径的完整视图时,它通过在编译时冲突完全限定的类和包名称来强制其内部规范名称的唯一性。然而,JVM必须接受这样的名称冲突,因此规范名称不能唯一地标识ClassLoader中的类。(事后看来,这个吸气剂的更好名称应该是EDOCX1×7;但是这种方法的日期可以追溯到JVM只用于运行Java程序的时候。)
  • 这个简单的名称松散地标识了类,在toString或日志操作期间可能同样有用,但不能保证是唯一的。
  • 类型名返回"此类型名称的信息字符串","它类似于toString():它完全是信息性的,没有合同值"(由sir4ur0n编写)


除了Nick Holt的观察,我还对Array数据类型进行了一些案例分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//primitive Array
int demo[] = new int[5];
Class<? extends int[]> clzz = demo.getClass();
System.out.println(clzz.getName());
System.out.println(clzz.getCanonicalName());
System.out.println(clzz.getSimpleName());      

System.out.println();


//Object Array
Integer demo[] = new Integer[5];
Class<? extends Integer[]> clzz = demo.getClass();
System.out.println(clzz.getName());
System.out.println(clzz.getCanonicalName());
System.out.println(clzz.getSimpleName());

上面的代码段打印:

1
2
3
4
5
6
7
[I
int[]
int[]

[Ljava.lang.Integer;
java.lang.Integer[]
Integer[]


添加局部类、lambdas和toString()方法来完成前面的两个答案。此外,我还添加了lambda数组和匿名类数组(尽管在实践中没有任何意义):

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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 {}

这是完整输出:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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;

所以,这里是规则。首先,让我们从原始类型和void开始:

  • 如果class对象表示基元类型或void,那么这四个方法都只返回其名称。
  • 现在,getName()方法的规则是:

  • 每个非lambda和非数组类或接口(即顶级、嵌套、内部、本地和匿名)都有一个名称(由getName()返回),即包名后接一个点(如果有包),然后接编译器生成的类文件名(不含后缀.class)。如果没有包,它只是类文件的名称。如果类是内部、嵌套、本地或匿名类,编译器应在其类文件名中至少生成一个$。注意,对于匿名类,类名将以美元符号结尾,后面跟一个数字。
  • lambda类名通常是不可预测的,您无论如何都不应该关心它们。确切地说,它们的名称是封闭类的名称,后跟$$Lambda$,后跟一个数字,后跟一个斜杠,后跟另一个数字。
  • 原语的类描述符为:ZbooleanZbooleanB为edcx1〔11〕的S为edcx1〔13〕的S为edcx1〔13〕的C为edcx1〔15〕的edcx1〔14〕为edcx1〔16〕为edcx1〔17〕的edcx1〔18〕为edcx1〔19〕的edcx1〔8〕为edcx1〔18〕为edcx1〔19〕的edcx11〔19〕的edcx1〔20〕为edcx1〔20〕为edcx1〔20〕为edcx1〔20〕1〔20〕为edcx1〔20〕21〕和D表示double。对于非数组类和接口,类描述符是L,后面是getName()给出的,后面是;。对于数组类,类描述符是[,后跟组件类型的类描述符(它本身可能是另一个数组类)。
  • 对于数组类,getName()方法返回其类描述符。此规则似乎只适用于组件类型为lambda的数组类(可能是bug),但希望这无论如何都不重要,因为即使存在组件类型为lambda的数组类也没有意义。
  • 现在,toString()方法:

  • 如果类实例表示接口(或注释,这是一种特殊类型的接口),那么toString()返回"interface" + getName()。如果它是原语,它只返回getName()。如果它是其他类型(类类型,即使它很奇怪),则返回"class" + getName()
  • getCanonicalName()法:

  • 对于顶级类和接口,getCanonicalName()方法返回的只是getName()方法返回的内容。
  • 对于匿名类或本地类以及这些类的数组类,getCanonicalName()方法返回null
  • 对于内部和嵌套类和接口,getCanonicalName()方法返回getName()方法将用点替换编译器引入的美元符号。
  • 对于数组类,如果组件类型的规范名称是null,则getCanonicalName()方法返回null。否则,它返回组件类型的规范名称,后跟[]
  • getSimpleName()法:

  • 对于顶级、嵌套、内部和本地类,getSimpleName()返回在源文件中写入的类的名称。
  • 对于匿名类,getSimpleName()返回一个空的String
  • 对于lambda类,getSimpleName()只返回没有包名称的getName()将返回的内容。对于我来说,这没有多大意义,看起来像一个bug,但是在lambda类上调用getSimpleName()是没有意义的。
  • 对于数组类,getSimpleName()方法返回组件类的简单名称,后跟[]。这有一个有趣/奇怪的副作用,即组件类型为匿名类的数组类只使用[]作为它们的简单名称。

  • 这是我找到的描述getname()、getsimplename()、getcanonicalname()的最佳文档

    https://javahowtooit.wordpress.com/2014/09/09/java-lang-class-what-is-the-difference-between-class-getname-class-getcanonicalname-and-class-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
    // Primitive type
    int.class.getName();          // -> int
    int.class.getCanonicalName(); // -> int
    int.class.getSimpleName();    // -> int

    // Standard class
    Integer.class.getName();          // -> java.lang.Integer
    Integer.class.getCanonicalName(); // -> java.lang.Integer
    Integer.class.getSimpleName();    // -> Integer

    // Inner class
    Map.Entry.class.getName();          // -> java.util.Map$Entry
    Map.Entry.class.getCanonicalName(); // -> java.util.Map.Entry
    Map.Entry.class.getSimpleName();    // -> Entry    

    // Anonymous inner class
    Class<?> anonymousInnerClass = new Cloneable() {}.getClass();
    anonymousInnerClass.getName();          // -> somepackage.SomeClass$1
    anonymousInnerClass.getCanonicalName(); // -> null
    anonymousInnerClass.getSimpleName();    // -> // An empty string

    // Array of primitives
    Class<?> primitiveArrayClass = new int[0].getClass();
    primitiveArrayClass.getName();          // -> [I
    primitiveArrayClass.getCanonicalName(); // -> int[]
    primitiveArrayClass.getSimpleName();    // -> int[]

    // Array of objects
    Class<?> objectArrayClass = new Integer[0].getClass();
    objectArrayClass.getName();          // -> [Ljava.lang.Integer;
    objectArrayClass.getCanonicalName(); // -> java.lang.Integer[]
    objectArrayClass.getSimpleName();    // -> Integer[]

    我也被各种不同的命名方案搞糊涂了,当我在这里发现这个问题时,我正准备就这个问题问和回答我自己的问题。我认为我的发现很好地符合它,并且补充了已经存在的东西。我的重点是寻找各种术语的文档,并添加一些可能在其他地方出现的更相关的术语。

    请考虑以下示例:

    1
    2
    3
    4
    5
    6
    7
    package a.b;
    class C {
      static class D extends C {
      }
      D d;
      D[] ds;
    }
    • D的简单名称是D。这就是您在声明类时编写的部分。匿名类没有简单的名称。Class.getSimpleName()返回此名称或空字符串。如果您这样写的话,简单名称可以包含$,因为$是JLS第3.8节规定的标识符的有效部分(即使有点不鼓励)。

    • 根据JLS第6.7节,a.b.C.Da.b.C.D.D.D都是完全限定名,但只有a.b.C.D才是D的规范名。所以每个规范名称都是完全限定的名称,但converes并不总是正确的。Class.getCanonicalName()将返回规范名或null

    • 根据JLS第13.1节的规定,记录Class.getName()以返回二进制名称。在这种情况下,它返回a.b.C$D代表D[La.b.C$D;代表D[]

    • 这个答案表明,由同一个类装入器装入的两个类可能具有相同的规范名称,但具有不同的二进制名称。两个名称都不足以可靠地推断出另一个:如果您有规范名称,那么您就不知道名称的哪些部分是包,哪些部分包含类。如果您有二进制名称,则不知道哪个$作为分隔符引入,哪些是简单名称的一部分。(类文件存储类本身及其封闭类的二进制名称,这允许运行时进行区分。)

    • 匿名类和本地类没有完全限定的名称,但仍然有二进制名称。对于嵌套在此类类中的类也是如此。每个类都有一个二进制名称。

    • a/b/C.class上运行javap -v -private表明,字节码将D的类型称为La/b/C$D;,数组ds的类型称为[La/b/C$D;。这些称为描述符,它们在JVMS第4.3节中指定。

    • 在这两个描述符中使用的类名a/b/C$D是用二进制名称中的/替换.得到的。显然,jvm规范将其称为二进制名称的内部形式。JVMS第4.2.1节对此进行了描述,并指出与二进制名称的区别是出于历史原因。

    • 在一个典型的基于文件名的类加载器中,如果您以二进制名称的内部形式将/解释为目录分隔符,并将文件扩展名.class附加到该文件中,就会得到类的文件名。它是相对于所讨论的类加载器使用的类路径来解析的。


    有趣的是,当类名畸形时,getCanonicalName()getSimpleName()可以提高InternalError。这种情况发生在一些非Java JVM语言中,例如斯卡拉。

    考虑下面的(Java 8上的Scala 2.11):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    scala> case class C()
    defined class C

    scala> val c = C()
    c: C = C()

    scala> c.getClass.getSimpleName
    java.lang.InternalError: Malformed class name
      at java.lang.Class.getSimpleName(Class.java:1330)
      ... 32 elided

    scala> c.getClass.getCanonicalName
    java.lang.InternalError: Malformed class name
      at java.lang.Class.getSimpleName(Class.java:1330)
      at java.lang.Class.getCanonicalName(Class.java:1399)
      ... 32 elided

    scala> c.getClass.getName
    res2: String = C

    对于混合语言环境或动态加载字节码的环境(如应用服务器和其他平台软件),这可能是一个问题。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
        public void printReflectionClassNames(){
        StringBuffer buffer = new StringBuffer();
        Class clazz= buffer.getClass();
        System.out.println("Reflection on String Buffer Class");
        System.out.println("Name:"+clazz.getName());
        System.out.println("Simple Name:"+clazz.getSimpleName());
        System.out.println("Canonical Name:"+clazz.getCanonicalName());
        System.out.println("Type Name:"+clazz.getTypeName());
    }

    outputs:
    Reflection on String Buffer Class
    Name: java.lang.StringBuffer
    Simple Name: StringBuffer
    Canonical Name: java.lang.StringBuffer
    Type Name: java.lang.StringBuffer