Java Class中的规范名称,简单名称和类名称之间的区别是什么?
在Java中:有什么区别:
Object o1= .... o1.getClass().getSimpleName(); o1.getClass().getName(); o1.getClass().getCanonicalName();
?
UPDATE
我已经多次检查过Javadoc,但是这从来没有解释得很好。 我也做了一个testing,并没有反映这些方法被调用的方式背后的真正含义。
所以,请不要责怪我问这个问题,而是指导我到javadoc,试着做一些努力来解释背后的真正含义。 感谢您的支持。
如果您不确定某些事情,请先尝试写一个testing。
我做到了这一点:
//primitive System.out.println(int.class.getName()); System.out.println(int.class.getCanonicalName()); System.out.println(int.class.getSimpleName()); 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(); //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(); //anonymous inner class System.out.println(new Serializable(){}.getClass().getName()); System.out.println(new Serializable(){}.getClass().getCanonicalName()); System.out.println(new Serializable(){}.getClass().getSimpleName());
打印:
INT INT INT java.lang.String中 java.lang.String中 串 java.util.AbstractMap中的$ SimpleEntry java.util.AbstractMap.SimpleEntry SimpleEntry ClassnameTest $ 1 空值
getSimpleName
返回空string的最后一个块中有空行。
结果看这是:
- 该名称是您用来dynamic加载类的名称,例如,使用默认的
ClassLoader
调用Class.forName
。 - 规范名称是将在导入语句中使用的名称,并唯一标识该类。 在
toString
或日志logging操作中可能有用。 - 简单的名字可以松散地标识这个类,在
toString
或者日志操作过程中也可能有用,但不能保证是唯一的。
添加数组:
//primitive System.out.println(int.class.getName()); System.out.println(int.class.getCanonicalName()); System.out.println(int.class.getSimpleName()); 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(); //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(); //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(); { //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()); }
添加到Nick Holt的答案:
[I int[] int[] [Ljava.lang.Integer; java.lang.Integer[] Integer[]
添加本地类,lambdas和toString()
方法来完成前面的两个答案。 此外,我添加了lambda数组和匿名类数组(尽pipe在实践中没有任何意义):
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;
所以,这是规则。 首先,让我们从原始types开始,然后void
:
- 如果类对象表示一个原始types或
void
,则所有四个方法都只是简单地返回它的名字。
现在, getName()
方法的规则如下:
- 每个非lambdaexpression式和非数组类或接口(即顶级,嵌套,内部,本地和匿名)都有一个名称(由
getName()
返回),即包名称后跟一个点(如果存在是一个包),接着是由编译器生成的类文件的名称(不带后缀.class
)。 如果没有包,则只是类文件的名称。 如果这个类是一个内部的,嵌套的,本地的或者匿名的类,编译器应该在它的类文件名中至less生成一个$
。 请注意,对于匿名类,类名将以一个美元符号和一个数字结尾。 - Lambda类的名字通常是不可预知的,你不应该关心它们。 确切地说,他们的名字是封闭类的名字,后面是
$$Lambda$
,后面跟着一个数字,后面跟着一个斜线,后面跟着另一个数字。 - 基元的类描述符是:
boolean
B
为Z
,byte
B
为B
,short
为S
,char
为C
,I
为int
,J
为long
,F
为float
,D
为double
float
。 对于非数组类和接口,类描述符是L
后跟getName()
后跟的是什么;
。 对于数组类,类描述符是[
后面是组件types的类描述符(可能本身就是另一个数组类)。 - 对于数组类,
getName()
方法返回它的类描述符。 这个规则似乎只对组件types是lambda(可能是一个bug)的数组类是失败的,但希望这也不重要,因为即使存在数组types是lambda的数组类也没有意义。
现在, toString()
方法:
- 如果类实例表示一个接口(或一个注解,这是一种特殊types的接口),
toString()
返回"interface " + getName()
。 如果它是一个基元,它只返回getName()
。 如果它是别的东西(一个类的types,即使它是一个非常奇怪的),它返回"class " + getName()
。
getCanonicalName()
方法:
- 对于顶级类和接口,
getCanonicalName()
方法只返回getName()
方法返回的内容。 -
getCanonicalName()
方法为匿名或本地类以及这些类的数组类返回null
。 - 对于内部和嵌套的类和接口,
getCanonicalName()
方法返回getName()
方法将用点代替编译器引入的美元符号。 - 对于数组类,如果组件types的规范名称为
null
,则getCanonicalName()
方法返回null
。 否则,它将返回组件types的规范名称,后跟[]
。
getSimpleName()
方法:
- 对于顶层,嵌套,内部和本地类,
getSimpleName()
返回源文件中写入的类的名称。 - 对于匿名类,
getSimpleName()
返回一个空String
。 - 对于lambda类,
getSimpleName()
只返回getName()
将返回的包名称。 这没什么意义,看起来像是一个bug,但是在lambda类上调用getSimpleName()
是没有意义的。 - 对于数组类,
getSimpleName()
方法返回组件类的简单名称,后跟[]
。 这有一个有趣/奇怪的副作用,其中组件types是匿名类的数组类只有[]
作为它们的简单名称。
这是我发现最好的文档描述getName(),getSimpleName(),getCanonicalName()
// 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[]
我也对各种各样的命名scheme感到困惑,当我在这里发现这个问题时,我正要问和回答我自己的问题。 我认为我的发现非常适合,并补充已经在这里。 我的重点是查找各种条款的文档 ,并添加一些可能在其他地方出现的相关术语。
考虑下面的例子:
package ab; class C { static class D extends C { } D d; D[] ds; }
-
D
的简单名字是D
这只是你在声明类时写的部分。 匿名类没有简单的名字。Class.getSimpleName()
返回这个名字或者空string。 如果你这样写,简单的名字可以包含一个$
,因为$
是一个标识符的有效部分。 -
根据JLS第6.7节 ,
abCDDD
和abCDDD
都是完全合格的名称 ,但只有abCDDD
D
的正规名称 。 所以每一个规范的名字都是一个完全合格的名字,但是传达并不总是正确的。Class.getCanonicalName()
将返回规范名称或null
。 -
Class.getName()
被logging为返回二进制名称 ,如JLS第13.1节中所述 。 在这种情况下,它为D
返回abC$D
D
和[La.bC$D;
为D[]
。 -
这个答案表明,可能由同一个类加载器加载的两个类具有相同的规范名称但不同的二进制名称 。 这两个名称都不足以可靠地推断出另一个名称:如果您有规范名称,则不知道名称的哪些部分是包,哪些包含类。 如果你有二进制名称,你不知道哪个
$
作为分隔符被引入,哪些是简单名字的一部分。 -
匿名类和本地类没有完全限定的名字,但仍然有一个二进制名称 。 嵌套在这些类中的类也是如此。 每个class级都有一个二进制名称。
-
在
a/b/C.class
上运行javap -v -private
显示字节码指的是d
的types为La/b/C$D;
和arraysds
[La/b/C$D;
。 这些被称为描述符 ,它们在JVMS第4.3节中被指定。 -
在这两个描述符中使用的类名称
a/b/C$D
是通过replace而得到的.
由/
以二进制名称。 JVM规范显然将这称为二进制名称的内部forms 。 JVMS第4.2.1节对此进行了描述,并指出与二进制名称的区别是出于历史原因。 -
如果将二进制名称的内部forms中的
/
解释为目录分隔符,并将扩展名为.class
的文件扩展名,那么在典型的基于文件名的类加载器中的某个类的文件名就是您所得到的。 它是相对于有问题的类加载器所使用的类path来parsing的。
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
有趣的是, getCanonicalName()
和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
对于dynamic加载字节码的混合语言环境或环境,例如应用程序服务器和其他平台软件,这可能是个问题。