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

时间:2013-03-04 13:45:29

标签: java

在Java中,它们之间有什么区别:

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

我已多次检查过Javadoc,但这从未解释过。 我也进行了测试,并没有反映出调用这些方法背后的任何真正意义。

8 个答案:

答案 0 :(得分:1013)

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

我这样做了:

import java.io.Serializable;
import java.util.HashMap;

class ClassNameTest {
    public static void main(String[] arguments) {
        //primitive
        System.out.println(int.class.getName());
        System.out.println(int.class.getCanonicalName());
        System.out.println(int.class.getSimpleName());
        System.out.println(int.class.getTypeName()); // Added in Java 8

        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(String.class.getTypeName());

        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(HashMap.SimpleEntry.class.getTypeName());

        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(new Serializable(){}.getClass().getTypeName());
    }
}

打印:

int
int
int
int

java.lang.String
java.lang.String
String
java.lang.String

java.util.AbstractMap$SimpleEntry
java.util.AbstractMap.SimpleEntry
SimpleEntry
java.util.AbstractMap$SimpleEntry

ClassNameTest$1
null

ClassNameTest$4

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

看到这个的结果是:

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

答案 1 :(得分:77)

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

答案 2 :(得分:74)

添加本地类,lambdas和toString()方法以完成前两个答案。此外,我添加了lambdas数组和匿名类数组(虽然在实践中没有任何意义):

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,则所有四种方法都只返回其名称。
  2. 现在getName()方法的规则:

    1. 每个非lambda和非数组类或接口(即顶级,嵌套,内部,本地和匿名)都有一个名称(由getName()返回),后面是包名称一个点(如果有一个包),后跟由编译器生成的类文件的名称(不带后缀.class)。如果没有包,它只是类文件的名称。如果类是内部类,嵌套类,本地类或匿名类,则编译器应在其类文件名中生成至少一个$。请注意,对于匿名类,类名将以美元符号后跟数字结尾。
    2. Lambda类名通常是不可预测的,无论如何你都不应该关心它们。确切地说,他们的名字是封闭类的名称,后跟$$Lambda$,后跟一个数字,后跟一个斜杠,后跟另一个数字。
    3. 基元的类描述符为Z booleanBbyteSshort,{{1} } CcharIintJlongFfloatD double。对于非数组类和接口,类描述符为L,后跟getName()后跟;给出的内容。对于数组类,类描述符为[,后跟组件类型的类描述符(可能本身是另一个数组类)。
    4. 对于数组类,getName()方法返回其类描述符。这个规则似乎只对组件类型为lambda(可能是一个bug)的数组类失败,但希望这无论如何都不重要,因为即使存在组件类型为lambda的数组类也没有意义。
    5. 现在,toString()方法:

      1. 如果类实例表示接口(或注释,这是一种特殊类型的接口),toString()将返回"interface " + getName()。如果它是原始的,它只返回getName()。如果它是其他东西(类类型,即使它是一个非常奇怪的类),它返回"class " + getName()
      2. getCanonicalName()方法:

        1. 对于顶级类和接口,getCanonicalName()方法返回getName()方法返回的内容。
        2. 对于匿名或本地类以及那些类的数组类,getCanonicalName()方法返回null
        3. 对于内部和嵌套类和接口,getCanonicalName()方法返回getName()方法将用点替换编译器引入的美元符号的内容。
        4. 对于数组类,如果组件类型的规范名称为getCanonicalName(),则null方法返回null。否则,它返回组件类型的规范名称,后跟[]
        5. getSimpleName()方法:

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

答案 3 :(得分:10)

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

考虑以下示例:

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才是<{1}} D的强>规范名称。因此,每个规范名称都是完全限定的名称,但并不总是如此。 Class.getCanonicalName()将返回规范名称或null

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

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

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

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

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

  • 如果您在二进制名称的内部形式中解释/,则会在一个典型的基于文件名的类加载器中的类的文件名。作为目录分隔符,并将文件扩展名/附加到其中。它是相对于有问题的类加载器使用的类路径解决的。

答案 4 :(得分:9)

这是我发现描述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[]

答案 5 :(得分: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

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

答案 6 :(得分:1)

[ * ]

答案 7 :(得分:0)

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