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

2022-08-31 03:58:03

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

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

我已经多次检查了Javadoc,但这从来没有很好地解释过它。我还运行了一个测试,这并没有反映出这些方法的调用方式背后的任何真正含义。


答案 1

如果你不确定某件事,试着先写一个测试。

我这样做了:

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.forNameClassLoaderClassLoader
  • 规范名称是将在导入语句中使用的名称。它可能在操作或日志记录操作期间有用。当编译器具有类路径的完整视图时,它会通过在编译时冲突完全限定的类名和包名来强制其中规范名称的唯一性。但是,JVM 必须接受此类名称冲突,因此规范名称不会唯一标识 .(事后看来,这个 getter 的更好名称应该是 ;但是这种方法可以追溯到 JVM 仅用于运行 Java 程序的时代。toStringjavacClassLoadergetJavaName
  • 简单名称松散地标识类,在或日志记录操作期间可能很有用,但不保证是唯一的。toString
  • 类型名称返回“此类型名称的信息性字符串”,“它就像:它纯粹是信息性的,没有合约值”。(由 sir4ur0n 撰写)toString

此外,您通常可以参考 Java 语言规范文档,了解以下类型的 Java API 技术详细信息:

Example 6.7-2.并分别通过和Example 6.7-2.Fully Qualified NamesFully Qualified Names v. Canonical Name


答案 2

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

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. 基元的类描述符是 for 、 for 、 for 、 for 、 for 、 for 和 for 。对于非数组类和接口,类描述符后跟给出的内容,后跟 。对于数组类,类描述符后跟组件类型的类描述符(其本身可能是另一个数组类)。ZbooleanBbyteSshortCcharIintJlongFfloatDdoubleLgetName();[
  4. 对于数组类,该方法返回其类描述符。此规则似乎仅对组件类型为 lambda 的数组类失败(这可能是一个错误),但希望这无论如何都无关紧要,因为即使组件类型为 lambda 的数组类的存在也没有意义。getName()

现在,方法:toString()

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

方法:getCanonicalName()

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

方法:getSimpleName()

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

推荐