协慌网

登录 贡献 社区

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

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

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

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

答案

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

我这样做了:

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返回一个空字符串。

看到这个的结果是:

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

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

添加本地类,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 ,则所有四种方法都只返回其名称。

现在getName()方法的规则:

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

现在, toString()方法:

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

getCanonicalName()方法:

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

getSimpleName()方法:

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