Qual é a diferença entre nome canônico, nome simples e nome da classe no Java Class?

973

Em Java, qual é a diferença entre estes:

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

Eu verifiquei o Javadoc várias vezes e, no entanto, isso nunca explica muito bem. Também fiz um teste e isso não refletia nenhum significado real por trás da maneira como esses métodos são chamados.

Mohamed Taher Alrefaie
fonte
218
Eu acho que essa é uma pergunta razoável. O javadoc não explica bem a diferença entre os três.
Graham Borland
1
Consulte - docs.oracle.com/javase/6/docs/api/java/lang/Class.html ou talvez apenas escreva um teste.
Nick Holt
7
@GrahamBorland O javadoc diz "conforme definido pela Java Language Specification" - para que você possa pesquisar nesse documento. Só porque não é um link clicável, as pessoas ainda podem fazer um esforço mínimo e clicar no primeiro resultado do mecanismo de pesquisa.
vbence
66
@vbence: A maioria das pessoas prefere fazer as coisas do que procurar no JLS coisas triviais como essa. Assim, este é o primeiro resultado do Google :)
pathikrit

Respostas:

1130

Se você não tiver certeza sobre algo, tente escrever um teste primeiro.

Eu fiz isso:

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();
    }
}

Impressões:

int.class (primitivo):
    getName (): int
    getCanonicalName (): int
    getSimpleName (): int
    getTypeName (): int

String.class (classe comum):
    getName (): java.lang.String
    getCanonicalName (): java.lang.String
    getSimpleName (): String
    getTypeName (): java.lang.String

java.util.HashMap.SimpleEntry.class (classe aninhada):
    getName (): java.util.AbstractMap $ SimpleEntry
    getCanonicalName (): java.util.AbstractMap.SimpleEntry
    getSimpleName (): SimpleEntry
    getTypeName (): java.util.AbstractMap $ SimpleEntry

nova java.io.Serializable () {}. getClass () (classe interna anônima):
    getName (): ClassNameTest $ 1
    getCanonicalName (): null
    getSimpleName ():    
    getTypeName (): ClassNameTest $ 1

Há uma entrada vazia no último bloco em que getSimpleNameretorna uma string vazia.

O resultado final é:

  • o nome é o nome que você usaria para carregar dinamicamente a classe com, por exemplo, uma chamada Class.forNamecom o padrão ClassLoader. Dentro do escopo de um certo ClassLoader, todas as classes têm nomes exclusivos.
  • o nome canônico é o nome que seria usado em uma declaração de importação. Pode ser útil durante toStringou operações de log. Quando o javaccompilador tem uma visão completa de um caminho de classe, ele impõe a exclusividade de nomes canônicos dentro dele, colidindo nomes de classes e pacotes totalmente qualificados no momento da compilação. No entanto, as JVMs devem aceitar conflitos de nome e, portanto, nomes canônicos não identificam exclusivamente classes dentro de a ClassLoader. (Em retrospectiva, teria sido um nome melhor para esse getter getJavaName; mas esse método data de uma época em que a JVM foi usada apenas para executar programas Java.)
  • o nome simples identifica livremente a classe, novamente pode ser útil durante toStringou operações de registro, mas não é garantido que seja exclusivo.
  • o nome do tipo retorna "uma sequência informativa para o nome desse tipo", "É como toString (): é meramente informativa e não possui valor de contrato" (conforme escrito por sir4ur0n)
Nick Holt
fonte
5
Que extra você acha que é necessário?
Nick Holt
2
@AnupamSaini yes. Ter esse nome de pacote em um aplicativo real seria uma loucura.
214 Jayen Jayen
3
A TI seria louca, no entanto, esse é o tipo de suposição que permitiria que um ator mal-intencionado funcionasse. Alguém dizendo "oh, bem, sabemos que as aulas nunca começarão com minúsculas / pacotes nunca começarão com maiúsculas". É verdade que um ator mal-intencionado que tem acesso ao seu carregador de classes já pode fazer coisas terríveis, por isso provavelmente não é uma suposição absolutamente terrível.
precisa saber é o seguinte
2
@PieterDeBie Como assim? Tudo o que você precisa saber é o nome do método que deseja testar.
Fool4jesus
20
Java 8 adicionou getTypeName () também ... gostaria de atualizar para isso?
Theodore Murdock
90

Adicionando classes locais, lambdas e o toString()método para concluir as duas respostas anteriores. Além disso, adiciono matrizes de lambdas e matrizes de classes anônimas (que, na prática, não fazem nenhum sentido):

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 {}

Esta é a saída completa:

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;

Então, aqui estão as regras. Primeiro, vamos começar com tipos primitivos e void:

  1. Se o objeto de classe representa um tipo primitivo ou void, todos os quatro métodos simplesmente retornam seu nome.

Agora as regras para o getName()método:

  1. Toda classe ou interface que não seja lambda e não array (ou seja, nível superior, aninhado, interno, local e anônimo) tem um nome (retornado por getName()) que é o nome do pacote seguido por um ponto (se houver um pacote ), seguido pelo nome do seu arquivo de classe, conforme gerado pelo compilador (sem o sufixo .class). Se não houver pacote, é simplesmente o nome do arquivo de classe. Se a classe for uma classe interna, aninhada, local ou anônima, o compilador deve gerar pelo menos um $em seu nome de arquivo de classe. Observe que, para classes anônimas, o nome da classe terminaria com um cifrão seguido de um número.
  2. Os nomes das classes Lambda geralmente são imprevisíveis e você não deve se preocupar com eles. Exatamente, o nome deles é o nome da classe envolvente, seguido por $$Lambda$, seguido por um número, seguido por uma barra, seguido por outro número.
  3. O descritor de classe das primitivas é Zpara boolean, Bpara byte, Spara short, Cpara char, Ipara int, Jpara long, Fpara floate Dpara double. Para classes e interfaces que não são de matriz, o descritor de classe é Lseguido pelo que é fornecido por getName()seguido por ;. Para classes de matriz, o descritor de classe é [seguido pelo descritor de classe do tipo de componente (que pode ser outra classe de matriz).
  4. Para classes de matriz, o getName()método retorna seu descritor de classe. Essa regra parece falhar apenas para classes de matriz cujo tipo de componente é um lambda (que possivelmente é um bug), mas espero que isso não importe de qualquer maneira, porque não faz sentido nem mesmo a existência de classes de matriz cujo tipo de componente é um lambda.

Agora, o toString()método:

  1. Se a instância da classe representar uma interface (ou uma anotação, que é um tipo especial de interface), os toString()retornos "interface " + getName(). Se é um primitivo, ele retorna simplesmente getName(). Se for outra coisa (um tipo de classe, mesmo que seja bem estranho), ele retornará "class " + getName().

O getCanonicalName()método:

  1. Para classes e interfaces de nível superior, o getCanonicalName()método retorna exatamente o que o getName()método retorna.
  2. O getCanonicalName()método retorna nullpara classes anônimas ou locais e para classes de matriz dessas.
  3. Para classes e interfaces internas e aninhadas, o getCanonicalName()método retorna o que o getName()método substituiria os cifrões introduzidos pelo compilador por pontos.
  4. Para as classes de matriz, o getCanonicalName()método retorna nullse o nome canónica do tipo de componente é null. Caso contrário, ele retornará o nome canônico do tipo de componente seguido por [].

O getSimpleName()método:

  1. Para classes de nível superior, aninhadas, internas e locais, getSimpleName()retorna o nome da classe conforme escrito no arquivo de origem.
  2. Para classes anônimas, o getSimpleName()retorno é vazio String.
  3. Para classes lambda, o getSimpleName()just retorna o getName()que retornaria sem o nome do pacote. Isso não faz muito sentido e parece um bug para mim, mas não faz sentido chamar getSimpleName()uma classe lambda para começar.
  4. Para classes de matriz, o getSimpleName()método retorna o nome simples da classe de componente seguido por []. Isso tem o efeito colateral engraçado / estranho de que as classes de matriz cujo tipo de componente é uma classe anônima têm apenas []seus nomes simples.
Victor Stafusa
fonte
2
… replacing the dollar-signs by dots: Somente os sinais de dólar introduzidos como delimitadores estão sendo substituídos. Você pode ter dólares como parte de um nome simples, e esses permanecerão no lugar.
MvG 2/16/16
Ah não! Como parte do nome da classe! Estou desenvolvendo um transformador de classe e pensei que '/' seria um delimitador seguro entre a classe e o nome do pacote: /
José Roberto Araújo Júnior
81

Além das observações de Nick Holt, executei alguns casos para o Arraytipo de dados:

//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());

Impressões acima do snippet de código:

[I
int[]
int[]

[Ljava.lang.Integer;
java.lang.Integer[]
Integer[]
gerardw
fonte
28
Não seria muito melhor propor uma edição para a resposta acima.
LoKi 27/11/2015
17

Também fiquei confuso com a ampla gama de esquemas de nomes diferentes e estava prestes a fazer e responder minha própria pergunta sobre isso quando a encontrei aqui. Acho que minhas descobertas se encaixam bem o suficiente e complementam o que já está aqui. Meu foco é procurar documentação sobre os vários termos e adicionar outros termos relacionados que podem surgir em outros lugares.

Considere o seguinte exemplo:

package a.b;
class C {
  static class D extends C {
  }
  D d;
  D[] ds;
}
  • O nome simples de Dé D. Essa é apenas a parte que você escreveu ao declarar a classe. Classes anônimas não têm um nome simples. Class.getSimpleName()retorna esse nome ou a sequência vazia. É possível que o nome simples contenha a $se você o escrever assim, pois $é uma parte válida de um identificador conforme a seção 3.8 do JLS (mesmo que seja um pouco desencorajado).

  • De acordo com a seção 6.7 do JLS , ambos a.b.C.De a.b.C.D.D.Dseriam nomes totalmente qualificados , mas somente a.b.C.Do nome canônico de D. Portanto, todo nome canônico é um nome totalmente qualificado, mas o inverso nem sempre é verdadeiro. Class.getCanonicalName()retornará o nome canônico ou null.

  • Class.getName()está documentado para retornar o nome binário , conforme especificado na seção 13.1 do JLS . Nesse caso, ele retorna a.b.C$Dpara De [La.b.C$D;para D[].

  • Esta resposta demonstra que é possível que duas classes carregadas pelo mesmo carregador de classes tenham o mesmo nome canônico, mas nomes binários distintos . Nenhum nome é suficiente para deduzir o outro com segurança: se você possui o nome canônico, não sabe quais partes do nome são pacotes e quais contêm classes. Se você possui o nome binário, não sabe quais $foram introduzidos como separadores e quais faziam parte de algum nome simples. (O arquivo de classe armazena o nome binário da própria classe e sua classe anexa , o que permite que o tempo de execução faça essa distinção .)

  • Classes anônimas e classes locais não têm nomes totalmente qualificados, mas ainda têm um nome binário . O mesmo vale para as classes aninhadas dentro dessas classes. Toda classe tem um nome binário.

  • Correndo javap -v -privatesobre a/b/C.classmostra que o código de bytes refere-se ao tipo de dcomo La/b/C$D;e que da matriz dscomo [La/b/C$D;. Eles são chamados de descritores e são especificados na seção 4.3 do JVMS .

  • O nome da classe a/b/C$Dusado em ambos os descritores é o que você obtém substituindo .por /no nome binário. A especificação da JVM aparentemente chama isso de forma interna do nome binário . A seção 4.2.1 da JVMS a descreve e afirma que a diferença do nome binário ocorreu por razões históricas.

  • O nome do arquivo de uma classe em um dos carregadores de classes típicos baseados em nome de arquivo é o que você obtém se interpretar o /formulário interno do nome binário como um separador de diretório e anexar a extensão do nome do arquivo .class. É resolvido em relação ao caminho da classe usado pelo carregador de classes em questão.

MvG
fonte
3
Essa deve ser a resposta aceita, já que é a única resposta que faz referência ao JLS e usa terminologias apropriadas.
John
10

este é o melhor documento que encontrei descrevendo 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[]
Kiran
fonte
3

É interessante notar que getCanonicalName()e getSimpleName()pode aumentarInternalError quando o nome da classe está incorreto. Isso acontece para algumas linguagens JVM não Java, por exemplo, Scala.

Considere o seguinte (Scala 2.11 no 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

Isso pode ser um problema para ambientes de linguagem mista ou ambientes que carregam dinamicamente bytecode, por exemplo, servidores de aplicativos e outro software de plataforma.

Sim
fonte
2

getName () - retorna o nome da entidade (classe, interface, classe de matriz, tipo primitivo ou nulo) representada por esse objeto de classe, como uma String.

getCanonicalName () - retorna o nome canônico da classe subjacente, conforme definido pela Java Language Specification.

getSimpleName () - retorna o nome simples da classe subjacente, que é o nome que ele recebeu no código-fonte.

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 
  }
}

Uma diferença é que, se você usar uma classe anônima, poderá obter um valor nulo ao tentar obter o nome da classe usando o métodogetCanonicalName()

Outro fato é que o getName()método se comporta de maneira diferente do getCanonicalName()método para classes internas . getName()usa um dólar como separador entre o nome canônico da classe envolvente e o nome simples da classe interna.

Para saber mais sobre como recuperar um nome de classe em Java .

Abdul Alim Shakir
fonte
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
Shirish Singh
fonte
1
As primeiras duas linhas dentro do método pode ser reduzido aClass<StringBuffer> clazz = StringBuffer.class
ThePyroEagle