【问题标题】:What is the difference between canonical name, simple name and class name in Java Class?Java Class 中的规范名、简单名和类名有什么区别?
【发布时间】:2013-02-18 15:01:33
【问题描述】:

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

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

我已多次检查 Javadoc,但始终无法很好地解释它。 我还进行了一个测试,结果并没有反映出这些方法的调用方式背后的任何真正含义。

【问题讨论】:

标签: java


【解决方案1】:

如果您不确定某事,请先尝试编写测试。

我这样做了:

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 返回一个空字符串。

看这个的结果是:

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

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

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

【讨论】:

  • 您认为还需要什么?
  • @AnupamSaini 是的。在真正的应用程序中使用这样的包名会很疯狂。
  • 这太疯狂了,但是,这种假设会让恶意行为者起作用。有人说“哦,我们知道类永远不会以小写字母开头/包永远不会以大写字母开头”。诚然,有权访问您的类加载器的恶意行为者已经可以做一些可怕的事情,所以这可能不是一个绝对可怕的假设。
  • @PieterDeBie 怎么样?您只需要知道要测试的方法名称即可。
  • Java 8 也添加了 getTypeName() ......要更新吗?
【解决方案2】:

添加本地类、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():          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 用于charI0,@9 J 代表 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. 对于数组类,如果组件类型的规范名称为nullgetCanonicalName() 方法将返回null。否则,它返回组件类型的规范名称,后跟[]

getSimpleName() 方法:

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

【讨论】:

  • … replacing the dollar-signs by dots: 只有作为分隔符引入的美元符号被替换。您可以将美元作为简单名称的一部分,并且这些将保留在原处。
  • 哦不!作为类名的一部分!我正在开发一个类转换器,我认为“/”将是类和包名之间的安全分隔符:/
【解决方案3】:

除了 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());

上面的代码sn-p打印:

[I
int[]
int[]

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

【讨论】:

  • 建议对上述答案进行修改再好不过了。
【解决方案4】:

我也对各种不同的命名方案感到困惑,当我在这里发现这个问题时,我正要就此提出并回答我自己的问题。我认为我的发现非常适合它,并补充了已经在这里的内容。我的重点是寻找有关各种术语的文档,并添加一些可能在其他地方出现的更多相关术语。

考虑以下示例:

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

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

  • Class.getName() 被记录为返回 二进制名称,如 JLS section 13.1 中所指定。在这种情况下,它为D 返回a.b.C$D,为D[] 返回[La.b.C$D;

  • This answer 证明由同一个类加载器加载的两个类可能具有相同的规范名称但不同的二进制名称。两个名称都不足以可靠地推断出另一个名称:如果您有规范名称,您不知道名称的哪些部分是包,哪些部分包含类。如果您有二进制名称,您不知道哪些$ 被引入作为分隔符,哪些是一些简单名称的一部分。 (class itself及其enclosing class的类文件stores the binary name,它允许运行时为make this distinction。)

  • Anonymous classeslocal classes 没有完全限定名称,但仍有二进制名称。嵌套在这些类中的类也是如此。 每个类都有一个二进制名称。

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

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

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

【讨论】:

  • 这应该是公认的答案,因为它是唯一引用 JLS 并使用正确术语的答案。
【解决方案5】:

这是我找到的描述 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[]

【讨论】:

    【解决方案6】:

    有趣的是,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.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
    

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

    【讨论】:

      【解决方案7】:

      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() 使用美元作为封闭类规范名称和内部类简单名称之间的分隔符。

      了解更多关于retrieving a class name in Java的信息。

      【讨论】:

        【解决方案8】:
            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
        

        【讨论】:

        • 方法里面的前两行可以简化为Class&lt;StringBuffer&gt; clazz = StringBuffer.class
        猜你喜欢
        • 2022-11-09
        • 2021-12-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-07-13
        • 2019-05-24
        • 2012-04-26
        • 2021-08-11
        相关资源
        最近更新 更多