Итак, у нас есть ситуация, когда нам нужно статически получить объект класса или полное / простое имя класса без явного использования синтаксиса MyClass.class
.
В некоторых случаях это может быть очень удобно, например, Экземпляр журнала для функций верхнего уровня kotlin (в этом случае kotlin создает статический класс Java, недоступный из кода kotlin).
У нас есть несколько вариантов получения этой информации:
new Object(){}.getClass().getEnclosingClass();
замечено Томом Хотином - tackline
getClassContext()[0].getName();
от SecurityManager
отмеченный Christoffer
new Throwable().getStackTrace()[0].getClassName();
граф Людвиг
Thread.currentThread().getStackTrace()[1].getClassName();
от Кекси
и, наконец, потрясающе
MethodHandles.lookup().lookupClass();
от Рейн
Я подготовил бенчмарк jmh для всех вариантов, и результаты:
# 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
Выводы
- Лучший вариант для использования , довольно чистый и чудовищно быстрый.
Доступно только с Java 7 и Android API 26!
MethodHandles.lookup().lookupClass();
- Если вам нужна эта функциональность для Android или Java 6, вы можете использовать второй лучший вариант. Это также довольно быстро, , но создает анонимный класс в каждом месте использования : (
new Object(){}.getClass().getEnclosingClass();
Если вам это нужно во многих местах и вы не хотите, чтобы ваш байт-код раздулся из-за множества анонимных классов - 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())
}
- Вероятно, вам никогда не понадобятся последние два варианта, основанные на
getStackTrace()
из исключения или Thread.currentThread()
. Очень неэффективно и может возвращать только имя класса как String
, а не Class<*>
экземпляр.
приписка
Если вы хотите создать экземпляр логгера для статических утилит 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
}