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


972

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

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

我已经多次检查过Javadoc,但这从来不能很好地解释它。我也进行了测试,但没有反映出调用这些方法背后的任何实际含义。



218
我认为这是一个合理的问题。Javadoc不能很好地解释这两者之间的区别。
Graham Borland


7
@GrahamBorland Javadoc说“由Java语言规范定义” -因此您可以在该文档中查找它。仅仅因为它不是可单击的链接,人们仍然可以花最少的精力并单击第一个搜索引擎结果。
vbence 2014年

66
@vbence:大多数人宁愿把事情做好,也不愿为类似的琐事查找JLS。因此,这是Google的第一个结果:)
pathikrit 2014年

Answers:


1129

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

我这样做:

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(原始):
    getName():int
    getCanonicalName():int
    getSimpleName():整数
    getTypeName():int

String.class(普通类):
    getName():java.lang.String
    getCanonicalName():java.lang.String
    getSimpleName():字符串
    getTypeName():java.lang.String

java.util.HashMap.SimpleEntry.class(嵌套类):
    getName():java.util.AbstractMap $ SimpleEntry
    getCanonicalName():java.util.AbstractMap.SimpleEntry
    getSimpleName():SimpleEntry
    getTypeName():java.util.AbstractMap $ SimpleEntry

新的java.io.Serializable(){}。getClass()(匿名内部类):
    getName():ClassNameTest $ 1
    getCanonicalName():空
    getSimpleName():    
    getTypeName():ClassNameTest $ 1

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

结果是:

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

5
您认为还需要什么?
尼克·霍尔特

2
@AnupamSaini是的。在实际的应用程序中拥有这样的程序包名称会很疯狂。
Jayen 2014年

3
IT会发疯,但是,这种假设会让恶意的参与者起作用。有人说“哦,我们知道类永远不会以小写字母/包开始,而永远不会以大写字母开头”。当然,可以访问您的类加载器的恶意参与者已经可以做可怕的事情,因此这可能不是绝对可怕的假设。
2014年

2
@PieterDeBie怎么办?您只需要知道要测试的方法名称即可。
fool4jesus 2015年

20
Java 8还添加了getTypeName()...是否愿意为此进行更新?
西奥多·默多克

90

添加本地类,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. 原语的类描述符是Zfor booleanBfor byteSfor shortCfor charIfor intJfor longFfor floatDfor 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()方法返回。否则,它返回组件类型的规范名称,后跟。nullnull[]

getSimpleName()方法:

  1. 对于顶级,嵌套,内部和局部类,getSimpleName()该类返回源文件中编写的类的名称。
  2. 对于匿名类,getSimpleName()返回空String
  3. 对于lambda类,getSimpleName()just返回getName()不带包名称的返回值。这没有多大意义,对我来说似乎像个错误,但是getSimpleName()从一开始就调用lambda类是没有意义的。
  4. 对于数组类,该getSimpleName()方法返回组件类的简单名称,后跟[]。这具有有趣/怪异的副作用,即组件类型为匿名类的数组类具有[]与它们的简单名称相同的名称。

2
… replacing the dollar-signs by dots:仅替换定界符中引入的美元符号。您可以将美元作为简单名称的一部分,而这些将保持不变。
MvG

不好了!作为班级名称的一部分!我正在开发一个一流的变压器,我还以为“/”将是类和包名之间的分隔符安全:/
何塞·罗伯托·阿劳霍儒尼奥尔

81

除了尼克·霍尔特(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[]

28
建议对以上答案进行编辑不会更好。
LoKi 2015年

17

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

考虑以下示例:

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

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

  • Class.getName()根据JLS第13.1节的规定,该文件被证明可以返回二进制名称。在这种情况下,它返回的和为。a.b.C$DD[La.b.C$D;D[]

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

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

  • 运行javap -v -privatea/b/C.class显示,字节码指的类型dLa/b/C$D;与该阵列的ds作为[La/b/C$D;。这些被称为描述符,它们在JVMS 4.3节中指定。

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

  • 文件名在典型基于文件名的类加载器的一个一类的是,如果你解释你所得到的/二进制名称作为目录分隔符的内部形式,文件扩展名追加.class到它。相对于所讨论的类加载器使用的类路径来解决。


3
这是应该接受的答案,因为它是引用JLS并使用适当术语的唯一答案。
约翰

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

3

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

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


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

1

getName() –以String形式返回此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中检索类名的更多信息。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.