在Java中,这些有什么区别:
Object o1 = ....
o1.getClass().getSimpleName();
o1.getClass().getName();
o1.getClass().getCanonicalName();
我已经多次检查了 Javadoc,但这从来没有很好地解释它。我还进行了一个测试,这并没有反映出调用这些方法的方式背后的任何真正含义。
在Java中,这些有什么区别:
Object o1 = ....
o1.getClass().getSimpleName();
o1.getClass().getName();
o1.getClass().getCanonicalName();
我已经多次检查了 Javadoc,但这从来没有很好地解释它。我还进行了一个测试,这并没有反映出调用这些方法的方式背后的任何真正含义。
如果您不确定某事,请先尝试编写测试。
我这样做了:
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
default调用ClassLoader
。在一定的范围内ClassLoader
,所有的类都有唯一的名字。- 规范名称是在导入语句中使用的名称。
toString
在或记录操作期间它可能很有用。当javac
编译器拥有类路径的完整视图时,它通过在编译时冲突完全限定的类和包名称来强制其中规范名称的唯一性。但是 JVM 必须接受这种名称冲突,因此规范名称不能唯一标识ClassLoader
. (事后看来,这个 getter 的一个更好的名字应该是getJavaName
;但这个方法可以追溯到 JVM 仅用于运行 Java 程序的时候。)- 简单的名称松散地标识类,在或记录操作期间可能很有用,
toString
但不能保证是唯一的。- 类型名称返回“此类型名称的信息字符串”,“就像
toString
:它纯粹是信息性的,没有合同价值”。(由 sir4ur0n 撰写)
您还可以通常参考 Java 语言规范文档以了解这些类型的技术 Java API 详细信息:
- 这是有关此主题 的
Java 11
规范: https ://docs.oracle.com/javase/specs/jls/se11/html/jls-6.html#jls-6.7
Example 6.7-2.
并分别Example 6.7-2.
过去Fully Qualified Names
和Fully Qualified Names v. Canonical Name
添加本地类、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
:
void
,则所有四个方法都只返回其名称。现在该getName()
方法的规则:
getName()
由),后跟编译器生成的类文件的名称(没有后缀.class
)。如果没有包,它只是类文件的名称。$
如果该类是内部、嵌套、本地或匿名类,编译器应在其类文件名中至少生成一个。请注意,对于匿名类,类名将以美元符号后跟一个数字结尾。$$Lambda$
,后面是一个数字,后面是一个斜线,然后是另一个数字。Z
for boolean
, B
for byte
, S
for short
, C
for char
, I
for int
, J
for long
, F
forfloat
和D
for double
。对于非数组类和接口,类描述符L
后面是给定的,getName()
后面是;
. 对于数组类,类描述符[
后面是组件类型的类描述符(它本身可能是另一个数组类)。getName()
方法返回其类描述符。这条规则似乎只对组件类型为 lambda 的数组类(这可能是一个错误)无效,但希望这无论如何都不重要,因为即使存在组件类型为 lambda 的数组类也没有意义。现在,toString()
方法:
toString()
返回"interface " + getName()
. 如果它是一个原语,它会简单地返回getName()
。如果它是别的东西(一个类类型,即使它很奇怪),它会返回"class " + getName()
.getCanonicalName()
方法:
getCanonicalName()
方法只返回该getName()
方法返回的内容。getCanonicalName()
方法返回null
匿名或本地类以及它们的数组类。getCanonicalName()
方法返回该getName()
方法将用点替换编译器引入的美元符号的内容。getCanonicalName()
方法返回。否则,它返回组件类型的规范名称,后跟.null
null
[]
getSimpleName()
方法:
getSimpleName()
返回源文件中写入的类的名称。getSimpleName()
返回一个空的String
.getSimpleName()
它只getName()
返回没有包名的返回值。这没有多大意义,对我来说看起来像是一个错误,但是从调用getSimpleName()
lambda 类开始是没有意义的。getSimpleName()
方法返回组件类的简单名称,后跟[]
. 这具有有趣/奇怪的副作用,即组件类型为匿名类的数组类[]
就像它们的简单名称一样。除了 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[]
我也对各种不同的命名方案感到困惑,当我在这里发现这个问题时,我正要问和回答我自己的问题。我认为我的发现非常适合它,并补充了已经在这里的内容。我的重点是寻找有关各种术语的文档,并添加一些可能在其他地方出现的更相关的术语。
考虑以下示例:
package a.b;
class C {
static class D extends C {
}
D d;
D[] ds;
}
的简单D
名称是D
。这只是你在声明类时写的部分。匿名类没有简单的名称。Class.getSimpleName()
返回此名称或空字符串。$
如果您这样写,简单名称可能包含 a ,因为根据JLS 第 3.8 节$
,它是标识符的有效部分(即使有些不鼓励)。
根据JLS 第 6.7 节,两者a.b.C.D
和a.b.C.D.D.D
都是完全限定名称,但只是. 因此,每个规范名称都是完全限定名称,但反过来并不总是正确的。将返回规范名称或.a.b.C.D
D
Class.getCanonicalName()
null
Class.getName()
记录返回二进制名称,如JLS 13.1 节中所指定。在这种情况下,它返回a.b.C$D
forD
和[La.b.C$D;
for D[]
。
这个答案表明,由同一个类加载器加载的两个类可能具有相同的规范名称但不同的二进制名称。两个名称都不足以可靠地推断出另一个名称:如果您有规范名称,您不知道名称的哪些部分是包,哪些部分包含类。如果您有二进制名称,您不知道哪些$
是作为分隔符引入的,哪些是一些简单名称的一部分。(类文件存储类本身及其封闭类的二进制名称,这允许运行时进行区分。)
运行javap -v -private
ona/b/C.class
显示字节码引用 as 的类型d
和La/b/C$D;
数组ds
as的类型[La/b/C$D;
。这些被称为描述符,它们在JVMS 4.3 节中指定。
a/b/C$D
这两个描述符中使用的类名是.
通过/
在二进制名中替换为得到的。JVM 规范显然将此称为二进制名称的内部形式。JVMS 第 4.2.1 节对其进行了描述,并指出与二进制名称的差异是出于历史原因。
如果您将二进制名称的内部形式解释为目录分隔符,并将文件扩展名附加到它,则在典型的基于文件名的类加载器之一中的类的文件名是您得到的。它是相对于相关类加载器使用的类路径解析的。/
.class
这是我找到的描述 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[]
有趣的是,当类名格式错误时,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
对于混合语言环境或动态加载字节码的环境,例如应用服务器和其他平台软件,这可能是一个问题。
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 中检索类名的更多信息。
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