Получение имени класса из статического метода в Java

244

Как можно получить имя класса из статического метода в этом классе. Например

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

Чтобы поместить это в контекст, я на самом деле хочу вернуть имя класса как часть сообщения в исключении.

Майлз Д
источник
try{ throw new RuntimeEsception();} catch(RuntimeEcxeption e){return e.getstackTrace()[1].getClassName();}
arminvanbuuren

Ответы:

231

Чтобы правильно поддерживать рефакторинг (переименовать класс), вы должны использовать либо:

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

или (спасибо @James Van Huis ):

 MyClass.class.getSimpleName(); // class name and no more
Инструментарий
источник
136
Если вы собираетесь жестко программировать в знании MyClass, то вы можете просто сделать String name = "MyClass"; !
Джон Топли
111
Но тогда рефакторинг имени класса в вашей IDE не будет работать должным образом.
Джеймс Ван Хьюис
9
Правда. Хотя MyClass.class гарантирует, что эта строка не будет забыта с помощью рефакторинга «изменить имя класса»
инструментарий
19
Я хотел бы, чтобы «this» работал в статическом контексте для обозначения текущего класса в Java, чтобы этот «class.xxx» был разрешен в экземпляре или статическом коде для обозначения этого класса! Проблема в том, что MyClass является многословным и избыточным в контексте. Но в то же время, насколько мне нравится Java, она склоняется к многословности.
Лоуренс Дол
51
Что если я вызываю статический метод в подклассе и мне нужно имя подкласса?
Эдвард Фальк
120

Делайте то, что говорит инструментарий. Не делайте ничего подобного

return new Object() { }.getClass().getEnclosingClass();
Том Хотин - Tackline
источник
7
Если класс расширяет другой, это не возвращает фактический класс, только базовый класс.
Луис Сойро
1
@ LuisSoeiro Я верю, что он возвращает класс, в котором определен метод. Я не уверен, как базовый класс учитывает статический контекст.
Том Хотин - tackline
8
Я не понимаю, почему getClass () не может быть статическим. Эта «идиома» тогда не понадобится.
Ммирвальд
1
@mmirwaldt getClassвозвращает тип среды выполнения, поэтому не может быть статическим.
Том Хотин - Tackline
5
Это настоящий ответ. Потому что вам не нужно писать имя вашего класса в коде самостоятельно.
Боб
99

В Java 7+ вы можете сделать это в статическом методе / полях:

MethodHandles.lookup().lookupClass()
Rein
источник
Я собирался сказать Reflection.getCallerClass(). Но это предупреждает о нахождении в «солнечных» пакетах. Так что это может быть лучшим решением.
Foumpie
1
@Foumpie: Java 9 собирается представить официальный API, который заменит эту неофициальную Reflection.getCallerClass()вещь. Это немного сложно для его тривиальной операции, т. Е. Optional<Class<?>> myself = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) .walk(s -> s.map(StackWalker.StackFrame::getDeclaringClass) .findFirst());, Конечно, это связано с тем, что он будет гораздо более мощным.
Хольгер
6
Это легко лучшее решение. Это избавляет от необходимости указывать фактическое имя класса, оно не скрыто, это не хак, и, согласно сообщению Артема Криволапова ниже, это также самый быстрый подход.
скомиса
@Rein Есть ли способ получить класс времени выполнения, если он был вызван в базовом классе?
Glide
59

Итак, у нас есть ситуация, когда нам нужно статически получить объект класса или полное / простое имя класса без явного использования MyClass.classсинтаксиса.

В некоторых случаях это может быть очень удобно, например, для функции верхнего уровня (в этом случае kotlin создает статический класс Java, недоступный из кода kotlin).

У нас есть несколько вариантов получения этой информации:

  1. new Object(){}.getClass().getEnclosingClass();
    отметил Том Хотин - tackline

  2. getClassContext()[0].getName();от SecurityManager
    отмеченного Кристоффером

  3. new Throwable().getStackTrace()[0].getClassName();
    по подсчетам людвиг

  4. Thread.currentThread().getStackTrace()[1].getClassName();
    из Кекси

  5. и, наконец, круто
    MethodHandles.lookup().lookupClass();
    от Рейна


Я подготовил Эталоном для всех вариантов и результатов являются:

# 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


Выводы

  1. Лучший вариант для использования , довольно чистый и чудовищно быстрый.
    Доступно только с Java 7 и Android API 26!
 MethodHandles.lookup().lookupClass();
  1. Если вам нужна эта функциональность для Android или Java 6, вы можете использовать второй лучший вариант. Это тоже довольно быстро, но создает анонимный класс в каждом месте использования :(
 new Object(){}.getClass().getEnclosingClass();
  1. Если вам это нужно во многих местах и ​​вы не хотите, чтобы ваш байт-код раздулся из-за множества анонимных классов - SecurityManagerэто ваш друг (третий лучший вариант).

    Но вы не можете просто позвонить getClassContext()- он защищен в SecurityManagerклассе. Вам понадобится некоторый вспомогательный класс, подобный этому:

 // 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. Вам, вероятно, никогда не нужно использовать последние два варианта, основанные на getStackTrace()исключении from или Thread.currentThread(). Очень неэффективно и может возвращать только имя класса как, а Stringне как Class<*>экземпляр.


PS

Если вы хотите создать экземпляр logger для статических утилит kotlin (как я :), вы можете использовать этот помощник:

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

Пример использования:

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

Эта инструкция отлично работает:

Thread.currentThread().getStackTrace()[1].getClassName();
Keksi
источник
5
Позаботьтесь, чтобы это могло быть очень медленным. Однако вы можете скопировать вставить его.
Габор Липтак
1
Это дает дополнительное преимущество: нет необходимости создавать объект или поток каждый раз, когда вы его используете.
Эрл Сегал-Халеви
5
@ErelSegalHalevi Это создает много StackTraceElement s в фоновом режиме, хотя :(
Navin
4
Если вы посмотрите на исходный код, Thread.getStackTrace()то увидите, что он ничего не делает, кроме как return (new Exception()).getStackTrace();в случае вызова на currentThread(). Таким образом, решение @count ludwig - более прямой способ добиться того же.
T-Bull
34

Вы можете сделать что-то действительно приятное, используя JNI, например:

MyObject.java:

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

    public static native String getClassName();

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

затем:

javac MyObject.java
javah -jni MyObject

затем:

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

Затем скомпилируйте C в общую библиотеку с именем libclassname.soи запустите Java!

* смешок

lifelongcoug
источник
Почему это не встроено?
ggb667
Умно, и я ценю юмор. Муха в мази заключается в том, что имя по умолчанию для функции C Java_MyObject_getClassNameимеет встроенное имя. Обходной путь - использовать JNI RegisterNatives. Конечно, вам придется кормить это с JNI FindClass(env, 'com/example/MyObject'), так что там тоже нет победы.
Renate
2
@Renate, так что весь этот ответ на самом деле шутка? Если это так, пожалуйста, сделайте это очень явно, потому что вы знаете, мы должны помогать другим людям, поэтому давайте не будем загонять невинных в ловушки.
Стефан Гурихон
Ну, это была не моя шутка, и я указал, что это была шутка. Вариант использования для этого сценария обычно предназначен для определения класса в журналах. Избавляясь от всей сложности, она обычно сводится к тому, чтобы выполнить либо: private static final String TAG = "MyClass"либо private static final String TAG = MyClass.class.getSimpleName();второй вариант более удобен для глобального переименования классов с использованием IDE.
Renate
20

Я использую это, чтобы инициировать Log4j Logger в верхней части моих классов (или аннотировать).

PRO: Throwable уже загружен, и вы можете сэкономить ресурсы, не используя «IO heavy» SecurityManager.

CON: Некоторые вопросы относительно того, будет ли это работать для всех JVM.

// 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()); 
граф Людвиг
источник
Создайте свой собственный класс исключений, чтобы jvm не беспокоился: jhocr.googlecode.com/svn/trunk/src/main/java/com/googlecode/…
4F2E4A2E
1
Если вы собираетесь предложить что-то столь же ужасное, как это решение, пожалуйста, по крайней мере, отнесите своих профессионалов к естественному решению использования MyClass.class.getName () вместо другого ужасного решения, например, злоупотребления SecurityManager.
Сорен Бойзен
Больше минусов: слишком многословно; медленный (это на самом деле второстепенная точка, потому что он запускается только один раз, когда класс загружается).
инструментальный инструмент
13

Злоупотребление SecurityManager

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

Или, если не установлен, используйте внутренний класс, который расширяет его (пример ниже постыдно скопирован из HowTo Real ):

public static class CurrentClassGetter extends SecurityManager {
    public String getClassName() {
        return getClassContext()[1].getName(); 
    }
}
Кристоффер
источник
9

Если вам нужно полное имя пакета с ним, позвоните:

String name = MyClass.class.getCanonicalName();

Если вы хотите только последний элемент, позвоните:

String name = MyClass.class.getSimpleName();
Джеймс Ван Хьюис
источник
5

Дословное использование класса вызывающего, как на MyClass.class.getName()самом деле, делает свою работу, но склонен к ошибкам копирования / вставки, если вы распространяете этот код среди многочисленных классов / подклассов, где вам нужно это имя класса.

И рецепт Тома Хоутина на самом деле неплохой, нужно просто правильно его приготовить :)

Если у вас есть базовый класс со статическим методом, который можно вызывать из подклассов, и этот статический метод должен знать фактический класс вызывающего, это может быть достигнуто следующим образом:

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

Безопасное для рефакторинга, вырезанное и вставленное решение, которое позволяет избежать определения специальных классов ниже.

Напишите статический метод, который восстанавливает имя класса, стараясь включить имя класса в имя метода:

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

затем вспомните это в вашем статическом методе:

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

Безопасность рефакторинга обеспечивается за счет исключения использования строк, безопасность «вырезать и вставить» предоставляется, потому что если вы вырезаете и вставляете метод вызывающей стороны, вы не найдете getMyClassName () в целевом классе «MyClass2», поэтому вам придется переопределить и обновить его.

avalori
источник
3

Поскольку вопрос Что-то вроде `this.class` вместо` ClassName.class`? помечен как дубликат для этого (что спорно , потому что речь идет о классе , а не имя класса), я отправляю ответ здесь:

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

Важно определить thisClassкак частное, потому что:
1) оно не должно быть унаследовано: производные классы должны либо определять свои собственные, thisClassлибо выдавать сообщение об ошибке
2) ссылки на другие классы должны быть сделаны, ClassName.classа не ClassName.thisClass.

С thisClassопределением, доступ к имени класса становится:

thisClass.getName()
18446744073709551615
источник
1

Мне нужно было имя класса в статических методах нескольких классов, поэтому я реализовал JavaUtil Class следующим методом:

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

Надеюсь, это поможет!

phaderer
источник
1
Мало того, что это плохо использовать из-за магического числа 2 (которое может легко привести к исключению NullPointerException), но вы сильно полагаетесь на точность виртуальной машины. Из javadoc метода: * Некоторые виртуальные машины могут, при некоторых обстоятельствах, пропустить один или несколько стековых фреймов из трассировки стека. В крайнем случае виртуальной машине, у которой нет информации трассировки стека относительно этого потока, разрешено возвращать массив нулевой длины из этого метода. *
Дробовик
0

Я использовал эти два подхода для обоих static и non staticсценария:

Основной класс:

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

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

Как предоставить имя класса:

нестатический способ:

private AndroidLogger mLogger = new AndroidLogger(this);

Статический способ:

private static AndroidLogger mLogger = new AndroidLogger(Myclass.class.getSimpleName());
0xAliHn
источник
-1

Если вы используете отражение, вы можете получить объект Method и затем:

method.getDeclaringClass().getName()

Чтобы получить сам метод, вы, вероятно, можете использовать:

Class<?> c = Class.forName("class name");
Method  method = c.getDeclaredMethod ("method name", parameterTypes)
Франце младший
источник
3
И как вы узнаете, что такое «имя класса»? :)
alfasin
1
Имея Class.forName("class name")уже дать вам класс. Почему вы хотите получить его с помощью метода?
Сергей Ирисов