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

1155
在Java中,这些有什么区别:
Object o1 = ....
o1.getClass().getSimpleName();
o1.getClass().getName();
o1.getClass().getCanonicalName();

我已经多次查看了Javadoc,但它从未很好地解释过这个问题。我还进行了一些测试,但这些方法的调用方式背后的真正含义并没有得到反映。


3
http://docs.oracle.com/javase/specs/jls/se7/html/jls-6.html#jls-6.7 - NPE
1
请参考 http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html 或者编写一个测试。 - Nick Holt
7
Java文档中说:“由Java语言规范定义”,所以你可以在该文档中查找。虽然它不是可点击的链接,但人们仍然可以花费最少的精力并单击第一个搜索引擎结果。 - vbence
80
大多数人宁愿完成任务,也不想为像这样的琐事查看JLS。因此,这是谷歌的第一个搜索结果 :) - pathikrit
参见:https://coderwall.com/p/lap9ww/don-t-rely-on-class-getsimplename-or-class-getcanonicalname - Stephan
9个回答

1320
如果您对某些事情不确定,可以先编写一个测试。我就是这样做的:
class ClassNameTest {
    public static void main(final String... arguments) {
        printNamesForClass(
            int.class,
            "int.class (primitive)");
        printNamesForClass(
            String.class,
            "String.class (ordinary class)");
        printNamesForClass(
            java.util.HashMap.SimpleEntry.class,
            "java.util.HashMap.SimpleEntry.class (nested class)");
        printNamesForClass(
            new java.io.Serializable(){}.getClass(),
            "new java.io.Serializable(){}.getClass() (anonymous inner class)");
    }

    private static void printNamesForClass(final Class<?> clazz, final String label) {
        System.out.println(label + ":");
        System.out.println("    getName():          " + clazz.getName());
        System.out.println("    getCanonicalName(): " + clazz.getCanonicalName());
        System.out.println("    getSimpleName():    " + clazz.getSimpleName());
        System.out.println("    getTypeName():      " + clazz.getTypeName()); // added in Java 8
        System.out.println();
    }
}

输出:

int.class (primitive):
    getName():          int
    getCanonicalName(): int
    getSimpleName():    int
    getTypeName():      int

String.class (ordinary class):
    getName():          java.lang.String
    getCanonicalName(): java.lang.String
    getSimpleName():    String
    getTypeName():      java.lang.String

java.util.HashMap.SimpleEntry.class (nested class):
    getName():          java.util.AbstractMap$SimpleEntry
    getCanonicalName(): java.util.AbstractMap.SimpleEntry
    getSimpleName():    SimpleEntry
    getTypeName():      java.util.AbstractMap$SimpleEntry

new java.io.Serializable(){}.getClass() (anonymous inner class):
    getName():          ClassNameTest$1
    getCanonicalName(): null
    getSimpleName():    
    getTypeName():      ClassNameTest$1

在最后一个块中有一个空条目,其中getSimpleName返回一个空字符串。
从这个看法来看,结论是: 名称是您用于动态加载类的名称,例如使用默认ClassLoader调用Class.forName。在特定ClassLoader的范围内,所有类都具有唯一的名称。 规范名称是在导入语句中使用的名称。它可能在toString或日志记录操作期间有用。当javac编译器完全查看类路径时,它通过在编译时碰撞完全限定的类和包名称来强制执行其内部的规范名称的唯一性。但是JVM必须接受这种名称冲突,因此规范名称不能在ClassLoader内唯一标识类。(事后看来,这个getter的更好名称应该是getJavaName;但是这个方法的日期是JVM仅用于运行Java程序的时代。) 简单名称松散地标识类,同样可能在toString或日志记录操作期间有用,但不保证是唯一的。 类型名称返回“有关此类型名称的信息字符串”,“就像toString:纯粹是信息性的,没有合同价值”。(由sir4ur0n编写)

您可以参考Java语言规范文档,了解这些类型的技术Java API细节:

示例6.7-2示例6.7-2分别介绍了完全限定名称完全限定名称与规范名称


5
你认为还需要什么额外的东西吗? - Nick Holt
3
尽管这种假设可能有些疯狂,但这会让恶意行为者有可乘之机。有人可能会说:“哦,我们知道类名永远不会以小写字母开头/包名永远不会以大写字母开头。”诚然,已经能够访问您的类装载器的恶意行为者可能已经做了可怕的事情,因此这可能不是一个完全糟糕的假设。 - corsiKa
3
怎么做呢?你只需要知道你想要测试的方法名即可。 - fool4jesus
26
Java 8新增了getTypeName()方法...你能否更新一下? - Theodore Murdock
3
在查看jdk8_102的实现(标准Oracle JDK)时,它似乎只是在getName()的基础上添加了一些差别,其中数组不像[[[Ljava.lang.Object;那样表示,而是表示为java.lang.Object[][][] - Adowrath
显示剩余10条评论

123

添加本地类、lambda表达式和toString()方法来完成前两个答案。此外,我还添加了lambda数组和匿名类数组(虽然在实践中没有任何意义):

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 {}

这是完整的输出:

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 开始:

  1. 如果类对象表示原始类型或 void,那么所有四个方法都只返回其名称。

现在是 getName() 方法的规则:

  1. 每个非 lambda 和非数组类或接口(即顶级、嵌套、内部、本地和匿名)都有一个名称(由 getName() 返回),它是包名称后跟一个点(如果有包),后跟其类文件的名称(编译器生成的不带后缀 .class)。如果没有包,则简单地使用类文件的名称。如果该类是内部、嵌套、本地或匿名类,则编译器应在其类文件名称中生成至少一个 $。请注意,对于匿名类,类名将以美元符号结尾,后跟一个数字。
  2. Lambda 类的名称通常是不可预测的,你也不需要关心它们。实际上,它们的名称是封闭类的名称,后跟 $$Lambda$,后跟一个数字,后跟斜杠,后跟另一个数字。
  3. 原始类型的类描述符为 Z 表示 booleanB 表示 byteS 表示 shortC 表示 charI 表示 intJ 表示 longF 表示 floatD 表示 double。对于非数组类和接口,类描述符为 L,后跟 getName() 给出的内容,后跟 ;。对于数组类,类描述符为 [,后跟组件类型的类描述符(可能是另一个数组类)。
  4. 对于数组类,getName() 方法返回其类描述符。这个规则似乎仅在组件类型为 lambda 的数组类中失败(这可能是一个错误),但希望这也没有关系,因为甚至存在组件类型为 lambda 的数组类也没有任何意义。

现在是 toString() 方法:

  1. 如果类实例表示接口(或注释,这是一种特殊类型的接口),toString() 返回 "interface " + getName()。如果它是原始类型,则简单地返回 getName()。如果它是其他东西(即使是相当奇怪的类类型),则返回 "class " + getName()

getCanonicalName() 方法:

  1. getCanonicalName()方法针对顶层类和接口,返回的结果与getName()方法相同。
  2. getCanonicalName()方法对于匿名或局部类以及这些类的数组类将返回null
  3. getCanonicalName()方法针对内部和嵌套类和接口,返回的结果是用点号替换了编译器生成的美元符号后getName()方法的结果。
  4. getCanonicalName()方法针对数组类,如果组件类型的规范名称为null则返回null。否则,返回组件类型的规范名称后跟[]

getSimpleName()方法:

  1. getSimpleName()方法针对顶层、嵌套、内部和局部类,返回源文件中编写的类名。
  2. getSimpleName()方法针对匿名类返回一个空字符串。
  3. 对于lambda类,getSimpleName()方法仅返回getName()方法返回的不带包名的名称。虽然这看起来有些奇怪,但在lambda类上调用getSimpleName()方法没有意义。
  4. getSimpleName()方法针对数组类返回组件类的简单名称后跟[]。这会导致一个有趣/怪异的副作用,就是组件类型为匿名类的数组类只有[]作为它们的简单名称。

4
只有作为分隔符引入的美元符号才会被替换为点号。如果美元符号只是一个名称的一部分,它将保持原样。 - MvG
哦不!作为类名的一部分!我正在开发一个类转换器,我认为“/”会是类和包名之间的安全分隔符 :/ - José Roberto Araújo Júnior

83

除了Nick Holt的观察外,我还对Array数据类型运行了几个案例:

//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());

以上代码片段打印出:

[I
int[]
int[]

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

29
最好对上面的答案提出修改意见。 - LoKi

22
我也曾被不同的命名方案搞糊涂,本来想在这里提问并回答自己的问题,但发现已经有人问过了。我的研究结果与此问题相符,并补充了一些其他相关术语,重点是寻找各种术语的文档,并添加一些在其他地方可能出现的相关术语。
考虑以下示例:
package a.b;
class C {
  static class D extends C {
  }
  D d;
  D[] ds;
}

  • D的简单名称是D。这只是在声明类时编写的部分。匿名类没有简单名称。{{link2:Class.getSimpleName()}}返回此名称或空字符串。如果您像这样编写它,则简单名称可能包含$,因为根据JLS第3.8节$是标识符的有效部分(即使有点不鼓励)。

  • 根据JLS第6.7节a.b.C.Da.b.C.D.D.D都将是完全限定名称,但只有a.b.C.D才是D规范名称。因此,每个规范名称都是完全限定名称,但反之则不总是如此。{{link5:Class.getCanonicalName()}}将返回规范名称或null

  • {{link6:Class.getName()}}的文档记录了返回二进制名称,如JLS第13.1节所指定。在这种情况下,D返回a.b.C$DD[]返回[La.b.C$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附加到它。


4
这应该是被接受的答案,因为它是唯一一个引用了JLS并使用了正确术语的答案。 - MEE

10

这是我找到的最好的关于getName()、getSimpleName()和getCanonicalName()方法的描述。

https://javahowtodoit.wordpress.com/2014/09/09/java-lang-class-what-is-the-difference-between-class-getname-class-getcanonicalname-and-class-getsimplename/

// 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[]

5

有趣的是,当类名格式不正确时,getCanonicalName()getSimpleName()会引发InternalError。这在一些非Java JVM语言(例如Scala)中发生。

考虑以下示例(Scala 2.11在Java 8上):

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

这可能会在混合语言环境或动态加载字节码的环境中造成问题,例如应用服务器和其他平台软件。

2

getName() – 返回由该Class对象所表示的实体(类、接口、数组类、原始类型或void)的名称,以字符串形式返回。

getCanonicalName() – 返回Java语言规范定义的基础类的规范名称。

getSimpleName() – 返回基础类的简单名称,即在源代码中赋予它的名称。

package com.practice;

public class ClassName {
public static void main(String[] args) {

  ClassName c = new ClassName();
  Class cls = c.getClass();

  // returns the canonical name of the underlying class if it exists
  System.out.println("Class = " + cls.getCanonicalName());    //Class = com.practice.ClassName
  System.out.println("Class = " + cls.getName());             //Class = com.practice.ClassName
  System.out.println("Class = " + cls.getSimpleName());       //Class = ClassName
  System.out.println("Class = " + Map.Entry.class.getName());             // -> Class = java.util.Map$Entry
  System.out.println("Class = " + Map.Entry.class.getCanonicalName());    // -> Class = java.util.Map.Entry
  System.out.println("Class = " + Map.Entry.class.getSimpleName());       // -> Class = Entry 
  }
}

一个区别是,如果您使用一个匿名类,当尝试使用`getCanonicalName()`获取类的名称时,您可以获得一个空值。
另一个事实是,对于内部类,`getName()`方法的行为与`getCanonicalName()`方法不同。`getName()`使用$符号作为封闭类规范名称和内部类简单名称之间的分隔符。
要了解更多关于在Java中检索类名的信息,请访问此处

1
    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

1
方法内的前两行可以简化为 Class<StringBuffer> clazz = StringBuffer.class - ThePyroEagle

0
Java 类中规范名称(canonical name)、简单名称(simple name)和类名(class name)有何区别?
在 Java 中,getClass() 方法用于获取与特定对象实例相关联的 Class 对象。该 Class 对象 表示有关对象所属的信息。 getSimpleName()getName() getCanonicalName() 方法有以下不同:
  • getSimpleName() 是返回由 Class 对象表示的类的简单名称的方法,没有关于包的信息。
例如: 如果 objectOnejava.util.ArrayList 类的一个实例,则objectOne.getClass().getSimpleName() 将返回 ArrayList
  • getName() 是返回由 Class 对象表示的类的完全限定名称的方法,包括包名。

例如:如果objectOnejava.util.ArrayList类的一个实例,那么objectOne.getClass().getName()将返回java.util.ArrayList

  • getCanonicalName()是返回由Class对象表示的类的规范化名称的方法。规范名称是类的完全限定名称,但删除了任何通用类型信息。

例如:如果objectOne是具有String泛型类型参数java.util.ArrayList类的实例,则o1.getClass().getCanonicalName()将返回java.util.ArrayList


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接