Отримання назви класу від статичного методу на 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
Я хотів би, щоб "це" працювало в статичному контексті, щоб означати поточний Клас на Java, щоб "class.xxx" було дозволено в будь-якому випадку або статичному коді, щоб означати цей клас! Проблема в цьому полягає в тому, що MyClass є багатослівним та надмірним у контексті. Але тоді наскільки мені подобається Java, схоже, схиляється до багатослівності.
Лоуренс Дол

51
Що робити, якщо я викликаю статичний метод в підкласі, і мені потрібно ім'я підкласу?
Едвард Фолк

120

Робіть те, що говорить інструментарій. Не робіть нічого подібного:

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

7
Якщо клас розширює ще один, це не повертає фактичний клас, а лише базовий клас.
Луїс Сейро

1
@LuisSoeiro Я вважаю, що він повертає клас, у якому визначений метод. Я не впевнений, як базовий клас впливає на статичний контекст.
Том Хотін - тайклін

8
Я не розумію, чому getClass () не може бути статичним. Ця "ідіома" тоді б не була потрібна.
mmirwaldt

1
@mmirwaldt getClassповертає тип виконання, тому не може бути статичним.
Том Хотін - тайклін

5
Це справжня відповідь. Тому що вам не потрібно самостійно писати ім’я свого класу у своєму коді.
Бобс

99

У Java 7+ ви можете це зробити в статичному методі / полях:

MethodHandles.lookup().lookupClass()

Я збирався сказати Reflection.getCallerClass(). Але це попереджає про те, що перебуваєте в упаковці "сонце". Тож це може бути кращим рішенням.
Фумпі

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();
    зауважив Том Хоутін - таклін

  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()виключенні або Thread.currentThread(). Дуже неефективно і може повертати лише ім'я класу як, а Stringне Class<*>екземпляр.


PS

Якщо ви хочете створити екземпляр реєстратора для статичних утилітів 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();

5
Слідкуйте за тим, щоб це було справді повільно. Однак ви можете скопіювати його.
Gábor Lipták

1
Це має додаткову перевагу у тому, що не потрібно створювати Об'єкт чи Нитку щоразу, коли ви його використовуєте.
Ерел Сегал-Халеві

5
@ErelSegalHalevi Це створює безліч StackTraceElement у фоновому режимі, хоча :(
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!

* посміятися


Чому це не вбудовано?
ggb667

Розумна, і я ціную гумор. Муха в мазі полягає в тому, що в типову назву функції С 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.

КОН: Деякі запитання щодо того, чи буде це спрацьовувати для всіх спільних інструментів.

// 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.
Søren Boisen

Більше мінусів: занадто багатослівний; повільний (це насправді другорядний момент, оскільки він працює лише один раз, коли клас завантажений).
toolforger

13

Зловживайте SecurityManager

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

Або, якщо його не встановлено, використовуйте внутрішній клас, який розширює його (приклад нижче ганебно скопійований з HowTo Реала ):

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, тому ви змушені будете переосмислити та оновити його.


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

1

Мені потрібно було ім'я класу в статичних методах декількох класів, тому я реалізував клас JavaUtil наступним методом:

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

Сподіваюся, це допоможе!


1
Це не тільки погано використовувати це через магічне число 2 (що може легко призвести до NullPointerException), але ви сильно покладаєтесь на точність віртуальної машини. З явадока методу: * Деякі віртуальні машини можуть за певних обставин опускати один або кілька фреймів стека із сліду стека. У крайньому випадку віртуальній машині, яка не має інформації про сліди стека щодо цього потоку, дозволяється повертати масив нульової довжини з цього методу. *
Стрілець

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

-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")вже дати вам клас. Чому ви хочете отримати його за допомогою методу?
Сергій Ірисов
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.