В чем разница между каноническим именем, простым именем и именем класса в Java Class?

974

В Java, в чем разница между ними:

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

Я проверял Javadoc несколько раз, но это никогда не объясняет это хорошо. Я также провел тест, и это не отражало никакого реального смысла в том, как эти методы вызываются.

Мохамед Тахер Alrefaie
источник
218
Я думаю, что это разумный вопрос. Javadoc не очень хорошо объясняет разницу между ними.
Грэм Борланд
1
Смотрите - docs.oracle.com/javase/6/docs/api/java/lang/Class.html или, может быть, просто напишите тест.
Ник Холт
7
@GrahamBorland Javadoc говорит «как определено в спецификации языка Java» - так что вы можете посмотреть его в этом документе. Просто потому, что это не кликабельная ссылка, люди могут приложить минимальные усилия и нажать на первый результат поисковой системы.
vbence
66
@vbence: Большинство людей предпочитают делать что-то, а не искать JLS для таких простых вещей, как это. Следовательно, это первый результат Google :)
pathikrit

Ответы:

1130

Если вы не уверены в чем-то, попробуйте сначала написать тест.

Я сделал это:

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 (примитив):
    getName (): int
    getCanonicalName (): int
    getSimpleName (): int
    getTypeName (): int

String.class (обычный класс):
    getName (): java.lang.String
    getCanonicalName (): java.lang.String
    getSimpleName (): String
    getTypeName (): java.lang.String

java.util.HashMap.SimpleEntry.class (вложенный класс):
    getName (): java.util.AbstractMap $ SimpleEntry
    getCanonicalName (): java.util.AbstractMap.SimpleEntry
    getSimpleName (): SimpleEntry
    getTypeName (): java.util.AbstractMap $ SimpleEntry

new java.io.Serializable () {}. getClass () (анонимный внутренний класс):
    getName (): ClassNameTest $ 1
    getCanonicalName (): null
    getSimpleName ():    
    getTypeName (): ClassNameTest $ 1

В последнем блоке есть пустая запись, которая getSimpleNameвозвращает пустую строку.

Результат, глядя на это:

  • имя это имя , которое вы будете использовать , чтобы динамически загружать класс с, например, вызов Class.forNameс по умолчанию ClassLoader. В рамках определенного ClassLoader, все классы имеют уникальные имена.
  • каноническое имя это имя , которое будет использоваться в операторе импорта. Это может быть полезно во время toStringили при ведении журнала. Когда javacкомпилятор имеет полное представление о пути к классам, он обеспечивает уникальность канонических имен в нем, конфликтуя полностью определенные имена классов и пакетов во время компиляции. Однако JVM должны принимать такие конфликты имен, и, таким образом, канонические имена не уникально идентифицируют классы в ClassLoader. (Оглядываясь назад, можно было бы назвать это имя лучше, getJavaNameно этот метод датируется тем временем, когда JVM использовалась исключительно для запуска программ на Java.)
  • название просто свободно идентифицирует класс, снова может оказаться полезным при toStringили лесозаготовительных операций , но не гарантировано быть уникальным.
  • то имя типа возвращает «информативная строка для названия этого типа», «Это походит ToString (): это чисто информативное и не имеет никакого значения контракта» (как написано sir4ur0n)
Ник Холт
источник
5
Какие дополнительные, как вы думаете, нужны?
Ник Холт
2
@ AnupamSaini да. Наличие такого имени пакета в реальном приложении было бы сумасшествием.
Jayen
3
Однако, это было бы сумасшествием, это предположение, которое позволило бы злоумышленнику действовать. Кто-то говорит: «О, хорошо, мы знаем, что классы никогда не начнутся со строчных букв / пакеты никогда не начнутся с прописных букв». Конечно, злоумышленник, имеющий доступ к вашему загрузчику классов, уже может делать ужасные вещи, так что это, вероятно, не совсем ужасное предположение.
CorsiKa
2
@PieterDeBie Как так? Все, что вам нужно знать, это имя метода, который вы хотите проверить.
fool4jesus
20
Java 8 также добавила getTypeName () ... хотите обновить для этого?
Теодор Мердок
90

Добавление локальных классов, лямбд и 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. Каждый не лямбда и не массив массив или интерфейс (т. Е. Верхнего уровня, вложенный, внутренний, локальный и анонимный) имеет имя (которое возвращается getName()), которое является именем пакета, за которым следует точка (если есть пакет) ), за которым следует имя его файла классов, сгенерированного компилятором (без суффикса .class). Если пакета нет, это просто имя файла класса. Если класс является внутренним, вложенным, локальным или анонимным классом, компилятор должен сгенерировать хотя бы один $в своем имени файла класса. Обратите внимание, что для анонимных классов имя класса заканчивается знаком доллара, за которым следует число.
  2. Имена лямбда-классов, как правило, непредсказуемы, и вы все равно не должны заботиться о них. Точно, их имя - это имя окружающего класса, за которым $$Lambda$следует число, за которым следует косая черта, а затем еще одно число.
  3. Класс дескриптора примитивов Zдля boolean, Bдля byte, Sдля short, Cдля char, Iдля int, Jдля long, Fдля floatи Dдля double. Для классов и интерфейсов, не являющихся массивами, за дескриптором класса Lследует то, что дано, getName()а затем ;. Для классов массива за дескриптором класса следует дескриптор [класса типа компонента (который сам может быть другим классом массива).
  4. Для классов массива getName()метод возвращает свой дескриптор класса. Это правило не работает только для классов массива, чей тип компонента является лямбда-выражением (что, возможно, является ошибкой), но, надеюсь, это не должно иметь значения в любом случае, потому что нет никакого смысла даже в существовании классов-массивов, чей тип компонента является лямбда-выражением.

Теперь toString()метод:

  1. Если экземпляр класса представляет интерфейс (или аннотацию, которая является особым типом интерфейса), toString()возвращается "interface " + getName(). Если это примитив, он просто возвращается getName(). Если это что-то еще (тип класса, даже если он довольно странный), он возвращается "class " + getName().

getCanonicalName()Метод:

  1. Для классов и интерфейсов верхнего уровня getCanonicalName()метод возвращает только то, что getName()возвращает метод.
  2. В getCanonicalName()методе возвращает nullдля анонимных или локальных классов и для классов массива из них.
  3. Для внутренних и вложенных классов и интерфейсов getCanonicalName()метод возвращает то, что getName()метод заменял бы введенные компилятором знаки доллара точками.
  4. Для классов массива getCanonicalName()метод возвращает, nullесли каноническое имя типа компонента равно null. В противном случае он возвращает каноническое имя типа компонента, за которым следует [].

getSimpleName()Метод:

  1. Для вложенных, внутренних и локальных классов верхнего уровня getSimpleName()возвращает имя класса, записанное в исходном файле.
  2. Для анонимных классов getSimpleName()возвращается пустой String.
  3. Для лямбда-классов getSimpleName()просто возвращает то, getName()что возвращает без имени пакета. Это не имеет особого смысла и выглядит для меня как ошибка, но нет смысла вызывать getSimpleName()лямбда-класс для начала.
  4. Для классов массива getSimpleName()метод возвращает простое имя класса компонента, за которым следует []. У этого есть забавный / странный побочный эффект, что классы массива, тип компонента которых является анонимным классом, имеют так же, []как их простые имена.
Виктор Стафуса
источник
2
… replacing the dollar-signs by dots: Заменяются только знаки доллара, которые были введены в качестве разделителей. Вы можете иметь доллары как часть простого имени, и они останутся на месте.
MvG
о нет! Как часть имени класса! Я разрабатываю преобразователь класса, и я подумал, что «/» будет безопасным разделителем между классом и именем пакета: /
Хосе Роберто Araújo Júnior
81

В дополнение к наблюдениям Ника Холта, я провел несколько случаев для 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[]
gerardw
источник
28
Я не был бы намного лучше предложить изменить ответ выше.
LoKi
17

Я также был озадачен широким спектром различных схем именования и собирался задать свой вопрос и ответить на него, когда нашел этот вопрос здесь. Я думаю, что мои выводы соответствуют этому достаточно хорошо, и дополняют то, что уже здесь. Я сосредоточен на поиске документации по различным терминам и добавлении некоторых связанных терминов, которые могут возникнуть в других местах.

Рассмотрим следующий пример:

package a.b;
class C {
  static class D extends C {
  }
  D d;
  D[] ds;
}
  • Простое имя в Dэто D. Это та часть, которую вы написали, когда объявляли класс. У анонимных классов нет простого имени. Class.getSimpleName()возвращает это имя или пустую строку. Простое имя может содержать a, $если вы напишите его так, поскольку $оно является допустимой частью идентификатора согласно разделу 3.8 JLS (даже если оно несколько не рекомендуется).

  • Согласно разделу JLS 6.7 , как a.b.C.Dи a.b.C.D.D.Dбыло бы полностью квалифицированные имена , но только a.b.C.Dбы быть каноническим именем из D. Таким образом, каждое каноническое имя является полностью определенным именем, но обратное не всегда верно. Class.getCanonicalName()вернет каноническое имя или null.

  • Class.getName()задокументировано возвращение двоичного имени , как указано в разделе 13.1 JLS . В этом случае он возвращается a.b.C$Dдля Dи [La.b.C$D;для D[].

  • Этот ответ демонстрирует, что два класса, загруженные одним и тем же загрузчиком классов, могут иметь одинаковое каноническое имя, но разные двоичные имена . Ни одно из имен не достаточно для надежного вывода другого: если у вас есть каноническое имя, вы не знаете, какие части имени являются пакетами, а какие содержат классы. Если у вас есть двоичное имя, вы не знаете, какие $были введены как разделители, а какие были частью какого-то простого имени. (Файл класса хранит двоичное имя самого класса и включающего его класса , что позволяет среде выполнения сделать это различие .)

  • Анонимные классы и локальные классы не имеют полностью определенных имен, но все еще имеют двоичное имя . То же самое относится к классам, вложенным в такие классы. Каждый класс имеет двоичное имя.

  • Запуск javap -v -privateна a/b/C.classпоказывает, что байт-код ссылается на тип das La/b/C$D;и тип массива dsas [La/b/C$D;. Это так называемые дескрипторы , и они указаны в разделе 4.3 JVMS .

  • Имя класса a/b/C$Dиспользуется в обоих этих дескрипторов, что вы получите, заменив .на /в двоичном имени. Спецификация JVM, очевидно, называет это внутренней формой двоичного имени . Раздел 4.2.1 JVMS описывает это и утверждает, что отличие от двоичного имени было по историческим причинам.

  • Имя файла класса в одном из типичных загрузчиков классов на основе имени файла - это то, что вы получаете, если вы интерпретируете /внутреннюю форму двоичного имени как разделитель каталогов и добавляете .classк нему расширение имени файла . Это решено относительно пути к классу, используемого рассматриваемым загрузчиком классов.

MVG
источник
3
Это должен быть принятый ответ, поскольку это единственный ответ, который ссылается на JLS и использует правильные термины.
Джон
10

это лучший документ, который я нашел, описывающий 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[]
Киран
источник
3

Интересно отметить, что 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

Это может быть проблемой для смешанных языковых сред или сред, которые динамически загружают байт-код, например, для серверов приложений и другого программного обеспечения платформы.

Сим
источник
2

getName () - возвращает имя объекта (класс, интерфейс, класс массива, тип примитива или 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 .

Абдул Алим Шакир
источник
1
    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
Шириш Сингх
источник
1
Первые две строки внутри метода могут быть уменьшены доClass<StringBuffer> clazz = StringBuffer.class
ThePyroEagle