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

在Java中,这些区别是什么:

Object o1 = ....o1.getClass().getSimpleName();o1.getClass().getName();o1.getClass().getCanonicalName();

我已经检查了Javadoc多次,但这从来没有解释清楚。我还运行了一个测试,并没有反映出这些方法调用方式背后的任何真正意义

305721 次浏览

如果您对某些事情不确定,请先尝试编写一个测试。

我是这样做的:

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 8System.out.println();}}

打印:

int.class (primitive):getName():          intgetCanonicalName(): intgetSimpleName():    intgetTypeName():      int
String.class (ordinary class):getName():          java.lang.StringgetCanonicalName(): java.lang.StringgetSimpleName():    StringgetTypeName():      java.lang.String
java.util.HashMap.SimpleEntry.class (nested class):getName():          java.util.AbstractMap$SimpleEntrygetCanonicalName(): java.util.AbstractMap.SimpleEntrygetSimpleName():    SimpleEntrygetTypeName():      java.util.AbstractMap$SimpleEntry
new java.io.Serializable(){}.getClass() (anonymous inner class):getName():          ClassNameTest$1getCanonicalName(): nullgetSimpleName():getTypeName():      ClassNameTest$1

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

这样做的结果是:

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

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

Example 6.7-2.Example 6.7-2.分别超过Fully Qualified NamesFully Qualified Names v. Canonical Name

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

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

上面的代码片段打印:

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

添加本地类、lambdas和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():          voidgetCanonicalName(): voidgetSimpleName():    voidtoString():         void
getName():          intgetCanonicalName(): intgetSimpleName():    inttoString():         int
getName():          java.lang.StringgetCanonicalName(): java.lang.StringgetSimpleName():    StringtoString():         class java.lang.String
getName():          java.lang.RunnablegetCanonicalName(): java.lang.RunnablegetSimpleName():    RunnabletoString():         interface java.lang.Runnable
getName():          com.example.SomeEnumgetCanonicalName(): com.example.SomeEnumgetSimpleName():    SomeEnumtoString():         class com.example.SomeEnum
getName():          com.example.SomeAnnotationgetCanonicalName(): com.example.SomeAnnotationgetSimpleName():    SomeAnnotationtoString():         interface com.example.SomeAnnotation
getName():          [IgetCanonicalName(): 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$NestedClassgetCanonicalName(): com.example.TestClassNames.NestedClassgetSimpleName():    NestedClasstoString():         class com.example.TestClassNames$NestedClass
getName():          com.example.TestClassNames$InnerClassgetCanonicalName(): com.example.TestClassNames.InnerClassgetSimpleName():    InnerClasstoString():         class com.example.TestClassNames$InnerClass
getName():          com.example.TestClassNames$1LocalClassgetCanonicalName(): nullgetSimpleName():    LocalClasstoString():         class com.example.TestClassNames$1LocalClass
getName():          [Lcom.example.TestClassNames$1LocalClass;getCanonicalName(): nullgetSimpleName():    LocalClass[]toString():         class [Lcom.example.TestClassNames$1LocalClass;
getName():          com.example.TestClassNames$1getCanonicalName(): nullgetSimpleName():toString():         class com.example.TestClassNames$1
getName():          [Lcom.example.TestClassNames$1;getCanonicalName(): nullgetSimpleName():    []toString():         class [Lcom.example.TestClassNames$1;
getName():          com.example.TestClassNames$$Lambda$1/1175962212getCanonicalName(): com.example.TestClassNames$$Lambda$1/1175962212getSimpleName():    TestClassNames$$Lambda$1/1175962212toString():         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对于intboolean0对于boolean1,boolean2对于boolean3,boolean4对于boolean5。对于非数组类和接口,类描述符是boolean6 + boolean7 + boolean8。对于数组类,类描述符是boolean9,后面跟着组件类型的类描述符(它本身可能是另一个数组类)。
  4. 对于数组类,getName()方法返回它的类描述符。该规则似乎只适用于组件类型为lambda的数组类(这可能是一个错误),但希望这无关紧要,因为甚至不存在组件类型为lambda的数组类。

现在,toString()方法:

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

getCanonicalName()方法:

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

getSimpleName()方法:

  1. 对于顶层的、嵌套的、内部的和局部的类,getSimpleName()返回写在源文件中的类名。
  2. 对于匿名类,getSimpleName()返回空的String
  3. 对于lambda类,getSimpleName()只返回getName()不带包名返回的结果。这对我来说没有多大意义,看起来像一个bug,但是在lambda类上调用getSimpleName()是没有意义的。
  4. 对于数组类,getSimpleName()方法返回组件类的简单名称,后面跟着[]。这有一个有趣/奇怪的副作用,组件类型为匿名类的数组类只有[]作为它们的简单名称。

我也对各种不同的命名方案感到困惑,当我在这里发现这个问题时,我正准备提出并回答我自己的问题。我认为我的发现很好地契合了它,并补充了现有的东西。我的重点是在各种术语中寻找文档,并添加一些可能在其他地方出现的更相关的术语。

考虑下面的例子:

package a.b;class C {static class D extends C {}D d;D[] ds;}
  • D简单名称D。这只是在声明类时编写的部分。匿名类没有简单的名字。Class.getSimpleName()返回此名称或空字符串。如果您这样写,那么简单的名称可能包含$,因为$是标识符的有效部分,即JLS section 3.8(即使它有点不鼓励)。

  • 根据, JLS章节6.7a.b.C.Da.b.C.D.D.D都是完全限定名,但只有a.b.C.DD规范名。因此,每个规范名都是完全限定名,但反过来并不总是正确的。Class.getCanonicalName()将返回规范名称或null

  • Class.getName()被记录为返回二进制名称,如JLS章节13.1所述。在本例中,它为D返回a.b.C$D,为D[]返回[La.b.C$D;

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

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

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

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

  • 典型的基于文件名的类加载器中的类的文件名是将二进制名称的内部形式的/解释为目录分隔符,并将文件扩展名.class附加到它之后得到的结果。它是相对于相关类装入器使用的类路径进行解析的。

这是我找到的描述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 typeint.class.getName();          // -> intint.class.getCanonicalName(); // -> intint.class.getSimpleName();    // -> int
// Standard classInteger.class.getName();          // -> java.lang.IntegerInteger.class.getCanonicalName(); // -> java.lang.IntegerInteger.class.getSimpleName();    // -> Integer
// Inner classMap.Entry.class.getName();          // -> java.util.Map$EntryMap.Entry.class.getCanonicalName(); // -> java.util.Map.EntryMap.Entry.class.getSimpleName();    // -> Entry
// Anonymous inner classClass<?> anonymousInnerClass = new Cloneable() {}.getClass();anonymousInnerClass.getName();          // -> somepackage.SomeClass$1anonymousInnerClass.getCanonicalName(); // -> nullanonymousInnerClass.getSimpleName();    // -> // An empty string
// Array of primitivesClass<?> primitiveArrayClass = new int[0].getClass();primitiveArrayClass.getName();          // -> [IprimitiveArrayClass.getCanonicalName(); // -> int[]primitiveArrayClass.getSimpleName();    // -> int[]
// Array of objectsClass<?> objectArrayClass = new Integer[0].getClass();objectArrayClass.getName();          // -> [Ljava.lang.Integer;objectArrayClass.getCanonicalName(); // -> java.lang.Integer[]objectArrayClass.getSimpleName();    // -> Integer[]
    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 ClassName: java.lang.StringBufferSimple Name: StringBufferCanonical Name: java.lang.StringBufferType Name: java.lang.StringBuffer

有趣的是,当类名格式不正确时,getCanonicalName()getSimpleName()可以引发InternalError。这种情况发生在一些非java JVM语言上,例如Scala。

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

scala> case class C()defined class C
scala> val c = C()c: C = C()
scala> c.getClass.getSimpleNamejava.lang.InternalError: Malformed class nameat java.lang.Class.getSimpleName(Class.java:1330)... 32 elided
scala> c.getClass.getCanonicalNamejava.lang.InternalError: Malformed class nameat java.lang.Class.getSimpleName(Class.java:1330)at java.lang.Class.getCanonicalName(Class.java:1399)... 32 elided
scala> c.getClass.getNameres2: String = C

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

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 existsSystem.out.println("Class = " + cls.getCanonicalName());    //Class = com.practice.ClassNameSystem.out.println("Class = " + cls.getName());             //Class = com.practice.ClassNameSystem.out.println("Class = " + cls.getSimpleName());       //Class = ClassNameSystem.out.println("Class = " + Map.Entry.class.getName());             // -> Class = java.util.Map$EntrySystem.out.println("Class = " + Map.Entry.class.getCanonicalName());    // -> Class = java.util.Map.EntrySystem.out.println("Class = " + Map.Entry.class.getSimpleName());       // -> Class = Entry}}

一个不同之处在于,如果您使用匿名类,则在尝试使用getCanonicalName()获取类名时可以获得空值

另一个事实是getName()方法对于内部类的行为与getCanonicalName()方法不同。getName()使用美元作为外围类规范名和内部类简单名之间的分隔符。

想知道更多关于在Java中检索类名的事情。