Object o1 = ....
o1.getClass().getSimpleName();
o1.getClass().getName();
o1.getClass().getCanonicalName();
我已经多次查看了Javadoc,但它从未很好地解释过这个问题。我还进行了一些测试,但这些方法的调用方式背后的真正含义并没有得到反映。
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
返回一个空字符串。ClassLoader
调用Class.forName
。在特定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
示例6.7-2
和示例6.7-2
分别介绍了完全限定名称
和完全限定名称与规范名称
getName()
的基础上添加了一些差别,其中数组不像[[[Ljava.lang.Object;
那样表示,而是表示为java.lang.Object[][][]
。 - Adowrath添加本地类、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
开始:
void
,那么所有四个方法都只返回其名称。现在是 getName()
方法的规则:
getName()
返回),它是包名称后跟一个点(如果有包),后跟其类文件的名称(编译器生成的不带后缀 .class
)。如果没有包,则简单地使用类文件的名称。如果该类是内部、嵌套、本地或匿名类,则编译器应在其类文件名称中生成至少一个 $
。请注意,对于匿名类,类名将以美元符号结尾,后跟一个数字。$$Lambda$
,后跟一个数字,后跟斜杠,后跟另一个数字。Z
表示 boolean
,B
表示 byte
,S
表示 short
,C
表示 char
,I
表示 int
,J
表示 long
,F
表示 float
,D
表示 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()
方法针对匿名类返回一个空字符串。getSimpleName()
方法仅返回getName()
方法返回的不带包名的名称。虽然这看起来有些奇怪,但在lambda类上调用getSimpleName()
方法没有意义。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
。这只是在声明类时编写的部分。匿名类没有简单名称。{{link2:Class.getSimpleName()
}}返回此名称或空字符串。如果您像这样编写它,则简单名称可能包含$
,因为根据JLS第3.8节,$
是标识符的有效部分(即使有点不鼓励)。
根据JLS第6.7节,a.b.C.D
和a.b.C.D.D.D
都将是完全限定名称,但只有a.b.C.D
才是D
的规范名称。因此,每个规范名称都是完全限定名称,但反之则不总是如此。{{link5:Class.getCanonicalName()
}}将返回规范名称或null
。
{{link6: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;
。这些称为描述符,并在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)中发生。
考虑以下示例(Scala 2.11在Java 8上):
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
}
}
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<StringBuffer> clazz = StringBuffer.class
。 - ThePyroEaglegetClass()
方法用于获取与特定对象实例相关联的 Class 对象。该 Class 对象 表示有关对象所属类的信息。
getSimpleName()
、getName()
和getCanonicalName()
方法有以下不同:
getSimpleName()
是返回由 Class 对象表示的类的简单名称的方法,没有关于包的信息。objectOne
是 java.util.ArrayList 类
的一个实例,则objectOne.getClass().getSimpleName()
将返回 ArrayList
。
getName()
是返回由 Class 对象表示的类的完全限定名称的方法,包括包名。例如:如果objectOne
是java.util.ArrayList类
的一个实例,那么objectOne.getClass().getName()
将返回java.util.ArrayList
。
getCanonicalName()
是返回由Class对象表示的类的规范化名称的方法。规范名称是类的完全限定名称,但删除了任何通用类型信息。例如:如果objectOne
是具有String泛型类型参数
的java.util.ArrayList
类的实例,则o1.getClass().getCanonicalName()
将返回java.util.ArrayList
。