IT story

Java의 정적 메소드에서 클래스 이름 가져 오기

hot-time 2020. 4. 15. 12:36
반응형

Java의 정적 메소드에서 클래스 이름 가져 오기


해당 클래스의 정적 메소드에서 클래스 이름을 얻는 방법은 무엇입니까? 예를 들어

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

문맥에 맞추기 위해 실제로 예외의 메시지의 일부로 클래스 이름을 반환하려고합니다.


리팩토링을 올바르게 지원하려면 (클래스 이름 바꾸기) 다음 중 하나를 사용해야합니다.

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

또는 ( @James Van Huis 덕분에 ) :

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

툴킷의 기능을 수행하십시오. 다음과 같이하지 마십시오 :

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

Java 7 이상에서는 정적 메소드 / 필드에서이를 수행 할 수 있습니다.

MethodHandles.lookup().lookupClass()

따라서 MyClass.class구문을 명시 적으로 사용하지 않고 클래스 객체 또는 클래스 전체 / 간단한 이름을 정적으로 가져와야하는 상황이 있습니다.

예를 들어, 상위 레벨 함수에 대한 로거 인스턴스 (이 경우 kotlin은 kotlin 코드에서 액세스 할 수없는 정적 Java 클래스를 작성 함) 와 같은 일부 경우에 매우 유용 할 수 있습니다 .

이 정보를 얻기위한 몇 가지 변형이 있습니다.

  1. new Object(){}.getClass().getEnclosingClass();
    에 의해 지적 톰 Hawtin의 - tackline

  2. getClassContext()[0].getName();ChristofferSecurityManager
    언급 에서

  3. new Throwable().getStackTrace()[0].getClassName();
    에 의해 계산 루드비히

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

  5. 그리고 마침내 Rein
    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<*>.


추신

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

이 지침은 잘 작동합니다.

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

다음과 같이 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.sojava!

*킬킬 웃음


나는 이것을 사용하여 내 클래스 상단 (또는 주석)에서 Log4j 로거를 초기화합니다.

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

SecurityManager 남용

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

또는 설정되지 않은 경우 확장하는 내부 클래스를 사용하십시오 (아래 예제는 Real의 HowTo 에서 부끄럽게 복사되었습니다 ).

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

전체 패키지 이름을 사용하려면 다음을 호출하십시오.

String name = MyClass.class.getCanonicalName();

마지막 요소 만 원하면 다음을 호출하십시오.

String name = MyClass.class.getSimpleName();

호출자의 클래스를 그대로 사용하면 MyClass.class.getName()실제로 작업을 수행하지만이 코드를이 클래스 이름이 필요한 수많은 클래스 / 하위 클래스에 전파하면 복사 / 붙여 넣기 오류가 발생하기 쉽습니다.

그리고 Tom Hawtin의 요리법 은 실제로 나쁘지 않습니다. 단지 올바른 방법으로 요리해야합니다. :)

서브 클래스에서 호출 될 수있는 정적 메소드가있는 기본 클래스가 있고이 정적 메소드가 실제 호출자의 클래스를 알아야하는 경우 다음과 같이 수행 될 수 있습니다.

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

아래의 애드혹 클래스의 정의를 피하는 리팩토링, 안전 및 잘라 내기 방지 솔루션.

메소드 이름에 클래스 이름을 포함시켜야하는 클래스 이름을 복구하는 정적 메소드를 작성하십시오.

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

그런 다음 정적 메소드에서 호출하십시오.

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

문자열 사용을 피함으로써 리팩토링 안전이 제공되며, 잘라 내기 및 붙여 넣기 안전이 부여됩니다. 호출자 메소드를 잘라내어 붙여 넣으면 대상 "MyClass2"클래스에서 getMyClassName ()을 찾을 수 없으므로 다시 정의하고 업데이트해야합니다.


`ClassName.class` 대신`this.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()

여러 클래스의 정적 메소드에 클래스 이름이 필요하므로 다음 메소드로 JavaUtil 클래스를 구현했습니다.

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

그것이 도움이되기를 바랍니다!


나는이 두 가지 접근법을 시나리오 staticnon 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());

리플렉션을 사용하는 경우 Method 객체를 얻을 수 있습니다.

method.getDeclaringClass().getName()

메소드 자체를 얻으려면 다음을 사용할 수 있습니다.

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

참고 URL : https://stackoverflow.com/questions/936684/getting-the-class-name-from-a-static-method-in-java

반응형