Obtendo o nome da classe de um método estático em Java


244

Como se pode obter o nome da classe a partir de um método estático nessa classe. Por exemplo

public class MyClass {
    public static String getClassName() {
        String name = ????; // what goes here so the string "MyClass" is returned
        return name;
    }
}

Para contextualizar, na verdade, quero retornar o nome da classe como parte de uma mensagem em uma exceção.


try{ throw new RuntimeEsception();} catch(RuntimeEcxeption e){return e.getstackTrace()[1].getClassName();}
arminvanbuuren 11/09/18

Respostas:


231

Para oferecer suporte à refatoração corretamente (renomear classe), você deve usar:

 MyClass.class.getName(); // full name with package

ou (graças a @James Van Huis ):

 MyClass.class.getSimpleName(); // class name and no more

136
Se você deseja codificar com conhecimento sobre o MyClass assim, pode também fazer String name = "MyClass"; !
31420 John Topley

111
Porém, refatorar o nome da classe no seu IDE não funcionará corretamente.
James Van Huis

9
Verdade. Embora MyClass.class irá garantir esta linha não são esquecidos com um 'nome de classe mudança' refatoração
kit de ferramentas

19
Eu gostaria que "this" funcionasse em um contexto estático para significar a classe atual em Java, que essa "class.xxx" fosse permitida na instância ou no código estático para significar essa classe! O problema é que o MyClass é detalhado e redundante, no contexto. Mas, por mais que eu goste de Java, parece inclinar-se para a verbosidade.
Lawrence Dol

51
E se eu estiver chamando o método estático em uma subclasse e desejar o nome da subclasse?
Edward Falk

120

Faça o que o kit de ferramentas diz. Não faça nada parecido com isto:

return new Object() { }.getClass().getEnclosingClass();

7
Se a classe estender outra, isso não retornará a classe real, apenas a classe base.
Luis Soeiro

1
@LuisSoeiro Acredito que ele retorne a classe em que o método está definido. Não tenho certeza de como a classe base é fatorada no contexto estático.
Tom Hawtin - defina

8
Eu não entendo por que getClass () não pode ser estático. Esse "idioma" não seria necessário.
precisa saber é o seguinte

1
@mmirwaldt getClassretorna o tipo de tempo de execução, portanto, não pode ser estático.
Tom Hawtin - defina

5
Esta é a verdadeira resposta. Porque você não precisa escrever o nome da sua classe no seu código.
Bobs

99

No Java 7+, você pode fazer isso nos métodos / campos estáticos:

MethodHandles.lookup().lookupClass()

Eu ia dizer Reflection.getCallerClass(). Mas dá um aviso sobre estar nos pacotes 'sol'. Portanto, essa pode ser uma solução melhor.
Foumpie

1
@Foumpie: O Java 9 vai apresentar uma API oficial que substituirá essa Reflection.getCallerClass()coisa não oficial . É um pouco complicado para sua operação trivial, ou seja Optional<Class<?>> myself = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) .walk(s -> s.map(StackWalker.StackFrame::getDeclaringClass) .findFirst());, mas é claro, isso está relacionado ao fato de que será muito mais poderoso.
21917 Holger

6
Esta é facilmente a melhor solução. Evita a necessidade de especificar o nome da classe real, não é obscuro, não é um hack e, de acordo com o post de Artyom Krivolapov abaixo, também é de longe a abordagem mais rápida.
Skomisa

@ Rein Existe uma maneira de obter a classe runtime, se isso foi chamado na classe base?
Glide

59

Portanto, temos uma situação em que precisamos obter estaticamente um objeto de classe ou um nome completo / simples de classe sem um uso explícito de MyClass.classsintaxe.

Pode ser realmente útil em alguns casos, por exemplo, instância de logger para o funções de nível superior (neste caso, o kotlin cria uma classe Java estática não acessível a partir do código kotlin).

Temos algumas variantes diferentes para obter essas informações:

  1. new Object(){}.getClass().getEnclosingClass();
    observado por Tom Hawtin - tackline

  2. getClassContext()[0].getName();do SecurityManager
    observado por Christoffer

  3. new Throwable().getStackTrace()[0].getClassName();
    por count ludwig

  4. Thread.currentThread().getStackTrace()[1].getClassName();
    de Keksi

  5. e finalmente incrível
    MethodHandles.lookup().lookupClass();
    de Rein


Eu preparei um referência para todas as variantes e resultados são:

# Run complete. Total time: 00:04:18

Benchmark                                                      Mode  Cnt      Score     Error  Units
StaticClassLookup.MethodHandles_lookup_lookupClass             avgt   30      3.630 ±   0.024  ns/op
StaticClassLookup.AnonymousObject_getClass_enclosingClass      avgt   30    282.486 ±   1.980  ns/op
StaticClassLookup.SecurityManager_classContext_1               avgt   30    680.385 ±  21.665  ns/op
StaticClassLookup.Thread_currentThread_stackTrace_1_className  avgt   30  11179.460 ± 286.293  ns/op
StaticClassLookup.Throwable_stackTrace_0_className             avgt   30  10221.209 ± 176.847  ns/op


Conclusões

  1. Melhor variante de usar , bastante limpa e monstruosamente rápida.
    Disponível apenas desde Java 7 e Android API 26!
 MethodHandles.lookup().lookupClass();
  1. Caso você precise dessa funcionalidade para Android ou Java 6, poderá usar a segunda melhor variante. Também é rápido, mas cria uma classe anônima em cada local de uso :(
 new Object(){}.getClass().getEnclosingClass();
  1. Se você precisar dele em muitos lugares e não quiser que seu bytecode inche devido a inúmeras classes anônimas - SecurityManageré seu amigo (terceira melhor opção).

    Mas você não pode simplesmente ligar getClassContext()- está protegido na SecurityManagerclasse. Você precisará de alguma classe auxiliar como esta:

 // Helper class
 public final class CallerClassGetter extends SecurityManager
 {
    private static final CallerClassGetter INSTANCE = new CallerClassGetter();
    private CallerClassGetter() {}

    public static Class<?> getCallerClass() {
        return INSTANCE.getClassContext()[1];
    }
 }

 // Usage example:
 class FooBar
 {
    static final Logger LOGGER = LoggerFactory.getLogger(CallerClassGetter.getCallerClass())
 }
  1. Você provavelmente nunca precisará usar as duas últimas variantes com base na getStackTrace()exceção from ou no Thread.currentThread(). Muito ineficiente e pode retornar apenas o nome da classe como a String, não a Class<*>instância.


PS

Se você deseja criar uma instância do logger para utilitários estáticos do kotlin (como eu :), você pode usar este auxiliar:

import org.slf4j.Logger
import org.slf4j.LoggerFactory

// Should be inlined to get an actual class instead of the one where this helper declared
// Will work only since Java 7 and Android API 26!
@Suppress("NOTHING_TO_INLINE")
inline fun loggerFactoryStatic(): Logger
    = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass())

Exemplo de uso:

private val LOGGER = loggerFactoryStatic()

/**
 * Returns a pseudo-random, uniformly distributed value between the
 * given least value (inclusive) and bound (exclusive).
 *
 * @param min the least value returned
 * @param max the upper bound (exclusive)
 *
 * @return the next value
 * @throws IllegalArgumentException if least greater than or equal to bound
 * @see java.util.concurrent.ThreadLocalRandom.nextDouble(double, double)
 */
fun Random.nextDouble(min: Double = .0, max: Double = 1.0): Double {
    if (min >= max) {
        if (min == max) return max
        LOGGER.warn("nextDouble: min $min > max $max")
        return min
    }
    return nextDouble() * (max - min) + min
}

38

Esta instrução funciona bem:

Thread.currentThread().getStackTrace()[1].getClassName();

5
Tome cuidado, pois isso pode ser muito lento. No entanto, você pode copiar e colar.
Gábor Lipták

1
Isso tem o benefício adicional de não ter que criar um objeto ou um thread cada vez que você o usa.
Erel Segal-Halevi 01/01

5
@ErelSegalHalevi No entanto, ele cria muitos StackTraceElement s em segundo plano :(
Navin

4
Se você olhar para o código-fonte, Thread.getStackTrace()verá que ele não faz nada além return (new Exception()).getStackTrace();de ser chamado no currentThread(). Portanto, a solução do @count ludwig é a maneira mais direta de conseguir o mesmo.
T-Bull

34

Você poderia fazer algo realmente agradável usando JNI assim:

MyObject.java:

public class MyObject
{
    static
    {
        System.loadLibrary( "classname" );
    }

    public static native String getClassName();

    public static void main( String[] args )
    {
        System.out.println( getClassName() );
    }
}

então:

javac MyObject.java
javah -jni MyObject

então:

MyObject.c:

#include "MyObject.h"

JNIEXPORT jstring JNICALL Java_MyObject_getClassName( JNIEnv *env, jclass cls )
{
    jclass javaLangClass = (*env)->FindClass( env, "java/lang/Class" );
    jmethodID getName = (*env)->GetMethodID( env, javaLangClass, "getName",
        "()Ljava/lang/String;" );
    return (*env)->CallObjectMethod( env, cls, getName );
}

Em seguida, compile o C em uma biblioteca compartilhada chamada libclassname.so e execute o java!

* risada


Por que isso não está embutido?
precisa saber é o seguinte

Inteligente, e eu aprecio o humor. A mosca na pomada é que o nome padrão da função C Java_MyObject_getClassNametem o nome incorporado. A maneira de contornar isso é usar o JNI RegisterNatives. Claro que você teria que alimentar isso com o JNI FindClass(env, 'com/example/MyObject'), então também não há vitória.
Renate

2
@ Renate, então toda essa resposta é realmente uma piada? Nesse caso, por favor, deixe bem explícito, porque você sabe que devemos ajudar outras pessoas, então não vamos colocar inocentes em armadilhas.
Stéphane Gourichon

Bem, não foi minha piada e eu apontei que era uma piada. O caso de uso desse cenário é geralmente para identificar a classe nos logs. Tirando toda a complexidade, geralmente se resume a fazer: private static final String TAG = "MyClass"ou private static final String TAG = MyClass.class.getSimpleName();O segundo é mais amigável para renomear classe global usando um IDE.
Renove

20

Eu uso isso para iniciar o Log4j Logger na parte superior das minhas classes (ou fazer anotações).

PRO: Throwable já está carregado e você pode economizar recursos ao não usar o SecurityManager "IO pesado".

CON: Alguma pergunta sobre se isso funcionará para todas as JVMs.

// Log4j . Logger --- Get class name in static context by creating an anonymous Throwable and 
// getting the top of its stack-trace. 
// NOTE you must use: getClassName() because getClass() just returns StackTraceElement.class 
static final Logger logger = Logger.getLogger(new Throwable() .getStackTrace()[0].getClassName()); 

Crie sua própria classe de exceção para que a jvm não se incomode: jhocr.googlecode.com/svn/trunk/src/main/java/com/googlecode/…
4F2E4A2E

1
Se você sugerir algo tão terrível quanto esta solução, relacione pelo menos seus profissionais à solução natural de usar MyClass.class.getName (), em vez de outra solução horrível, como abusar do SecurityManager.
Søren Boisen

Mais contras: muito detalhado; lento (na verdade, este é um ponto secundário, porque é executado apenas uma vez, quando a classe é carregada).
toolforger

13

Abuse o SecurityManager

System.getSecurityManager().getClassContext()[0].getName();

Ou, se não estiver definido, use uma classe interna que a estenda (exemplo abaixo vergonhosamente copiado do HowTo do Real ):

public static class CurrentClassGetter extends SecurityManager {
    public String getClassName() {
        return getClassContext()[1].getName(); 
    }
}

9

Se você quiser o nome completo do pacote, chame:

String name = MyClass.class.getCanonicalName();

Se você deseja apenas o último elemento, chame:

String name = MyClass.class.getSimpleName();

5

O uso verboso da classe de quem chama MyClass.class.getName()realmente faz o trabalho, mas é propenso a copiar / colar erros se você propagar esse código para várias classes / subclasses onde você precisa desse nome de classe.

E a receita de Tom Hawtin na verdade não é ruim, basta cozinhá-lo da maneira certa :)

Caso você tenha uma classe base com um método estático que pode ser chamado de subclasses e esse método estático precise conhecer a classe do chamador real, isso pode ser alcançado da seguinte maneira:

class BaseClass {
  static sharedStaticMethod (String callerClassName, Object... otherArgs) {
    useCallerClassNameAsYouWish (callerClassName);
    // and direct use of 'new Object() { }.getClass().getEnclosingClass().getName()'
    // instead of 'callerClassName' is not going to help here,
    // as it returns "BaseClass"
  }
}

class SubClass1 extends BaseClass {
  static someSubclassStaticMethod () {
    // this call of the shared method is prone to copy/paste errors
    sharedStaticMethod (SubClass1.class.getName(),
                        other_arguments);
    // and this call is safe to copy/paste
    sharedStaticMethod (new Object() { }.getClass().getEnclosingClass().getName(),
                        other_arguments);
  }
}

4

Uma solução segura para refatoração, recortar e colar que evita a definição de classes ad-hoc abaixo.

Escreva um método estático que recupere o nome da classe, tendo o cuidado de incluir o nome da classe no nome do método:

private static String getMyClassName(){
  return MyClass.class.getName();
}

depois, lembre-o no seu método estático:

public static void myMethod(){
  Tracer.debug(getMyClassName(), "message");
}

A segurança da refatoração é dada ao evitar o uso de strings, a segurança de recortar e colar é concedida porque, se você recortar e colar o método de chamada, não encontrará o getMyClassName () na classe "MyClass2" de destino, sendo forçado a redefini-lo e atualizá-lo.


3

Desde a pergunta Algo como `this.class` em vez de` ClassName.class`? está marcado como duplicado para este (que é discutível porque essa pergunta é sobre a classe e não o nome da classe), estou postando a resposta aqui:

class MyService {
    private static Class thisClass = MyService.class;
    // or:
    //private static Class thisClass = new Object() { }.getClass().getEnclosingClass();
    ...
    static void startService(Context context) {
        Intent i = new Intent(context, thisClass);
        context.startService(i);
    }
}

É importante definir thisClasscomo privado porque:
1) não deve ser herdado: as classes derivadas devem definir suas próprias thisClassou produzir uma mensagem de erro
2) as referências de outras classes devem ser feitas como ClassName.classantes ClassName.thisClass.

Com thisClassdefinido, o acesso ao nome da classe se torna:

thisClass.getName()

1

Eu precisava do nome da classe nos métodos estáticos de várias classes, então implementei uma Classe JavaUtil com o seguinte método:

public static String getClassName() {
    String className = Thread.currentThread().getStackTrace()[2].getClassName();
    int lastIndex = className.lastIndexOf('.');
    return className.substring(lastIndex + 1);
}

Espero que ajude!


1
Não é apenas ruim usar isso por causa do número mágico 2 (que pode facilmente resultar em uma NullPointerException), mas você depende muito da precisão da máquina virtual. No javadoc do método: * Algumas máquinas virtuais podem, em algumas circunstâncias, omitir um ou mais quadros de pilha do rastreamento de pilha. No caso extremo, uma máquina virtual que não possui informações de rastreamento de pilha relacionadas a esse encadeamento tem permissão para retornar uma matriz de comprimento zero desse método. *
Espingarda

0

Eu usei essas duas abordagens para ambos statice o non staticcenário:

Classe principal:

//For non static approach
public AndroidLogger(Object classObject) {
    mClassName = classObject.getClass().getSimpleName();
}

//For static approach
public AndroidLogger(String className) {
    mClassName = className;
}

Como fornecer o nome da classe:

maneira não estática:

private AndroidLogger mLogger = new AndroidLogger(this);

Maneira estática:

private static AndroidLogger mLogger = new AndroidLogger(Myclass.class.getSimpleName());

-1

Se você estiver usando reflexão, poderá obter o objeto Method e, em seguida:

method.getDeclaringClass().getName()

Para obter o próprio método, você provavelmente pode usar:

Class<?> c = Class.forName("class name");
Method  method = c.getDeclaredMethod ("method name", parameterTypes)

3
E como você saberá o que é: "nome da classe"? :)
alfasin

1
Class.forName("class name")Já tendo lhe dado uma aula. Por que você deseja recuperá-lo via Method?
Sergey Irisov 14/09/16
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.