รับชื่อคลาสจากวิธีการคงที่ใน 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 = "MyClass"; !
John Topley

111
แต่จากนั้นการปรับโครงสร้างชื่อคลาสใน IDE ของคุณจะไม่ทำงานอย่างถูกต้อง
James Van Huis

9
จริง แม้ว่า MyClass.class จะทำให้แน่ใจว่าบรรทัดนี้จะไม่ถูกลืมด้วยการเปลี่ยนชื่อ 'เปลี่ยนชื่อคลาส'
toolkit

19
ฉันหวังว่า "นี่" ทำงานในบริบทที่คงที่หมายถึงคลาสปัจจุบันใน Java ว่าอนุญาตให้ "class.xxx" ในอินสแตนซ์หรือรหัสคงที่เพื่อหมายถึงคลาสนี้! ปัญหานี้คือว่า MyClass verbose และซ้ำซ้อนในบริบท แต่เท่าที่ฉันชอบ Java ดูเหมือนว่าจะเอนเอียงไปทาง verbosity
Lawrence Dol

51
ถ้าฉันเรียกใช้เมธอดสแตติกในคลาสย่อยและฉันต้องการชื่อคลาสย่อย
Edward Falk

120

ทำในสิ่งที่ชุดเครื่องมือพูด อย่าทำอะไรแบบนี้:

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

7
ถ้าคลาสขยายอีกคลาสหนึ่งสิ่งนี้จะไม่ส่งคืนคลาสที่แท้จริง แต่เป็นคลาสพื้นฐานเท่านั้น
Luis Soeiro

1
@ LuisSoeiro ฉันเชื่อว่ามันคืนคลาสที่มีการกำหนดเมธอดไว้ฉันไม่แน่ใจว่าปัจจัยคลาสพื้นฐานในบริบทแบบสแตติก
Tom Hawtin - tackline

8
ฉันไม่เข้าใจว่าทำไม getClass () ไม่สามารถคงที่ได้ "สำนวน" นี้จะไม่จำเป็นต้องใช้
mmirwaldt

1
@mmirwaldt getClassส่งคืนชนิดรันไทม์ดังนั้นจึงไม่สามารถเป็นแบบคงที่ได้
Tom Hawtin - tackline

5
นี่คือคำตอบที่แท้จริง เพราะคุณไม่จำเป็นต้องเขียนชื่อคลาสของคุณในรหัสของคุณเอง
บ็อบ

99

ใน Java 7+ คุณสามารถทำได้ในเมธอด / ฟิลด์แบบสแตติก:

MethodHandles.lookup().lookupClass()

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());แต่แน่นอนว่ามันเกี่ยวข้องกับความจริงที่ว่ามันจะมีพลังมากกว่านี้
Holger

6
นี่เป็นทางออกที่ดีที่สุดได้อย่างง่ายดาย มันหลีกเลี่ยงความจำเป็นในการระบุชื่อคลาสที่แท้จริงไม่ชัดเจนไม่ใช่แฮ็คและจากการโพสต์ของ Artyom Krivolapov ด้านล่างก็เป็นวิธีที่เร็วที่สุด
skomisa

@Rein มีวิธีรับ class runtime หากมีการเรียกในชั้นฐานหรือไม่
Glide

59

ดังนั้นเราจึงมีสถานการณ์เมื่อเราจำเป็นต้องรับคลาสอ็อบเจ็กต์หรือชื่อเต็ม / คลาสแบบง่ายโดยไม่มีการใช้MyClass.classไวยากรณ์อย่างชัดเจน

มันมีประโยชน์จริงๆในบางกรณีเช่นอินสแตนซ์คนตัดไม้ของ ฟังก์ชั่นระดับบน (ในกรณีนี้ kotlin สร้างคลาส Java แบบคงที่ไม่สามารถเข้าถึงได้จากรหัส kotlin)

เรามีตัวแปรที่แตกต่างกันเล็กน้อยในการรับข้อมูลนี้:

  1. new Object(){}.getClass().getEnclosingClass();
    บันทึกโดยTom Hawtin - tackline

  2. getClassContext()[0].getName();จากSecurityManager
    บันทึกของChristoffer

  3. new Throwable().getStackTrace()[0].getClassName();
    โดยนับลุดวิก

  4. Thread.currentThread().getStackTrace()[1].getClassName();
    จากKeksi

  5. และในที่สุดก็ยอดเยี่ยม
    MethodHandles.lookup().lookupClass();
    จากRein


ฉันได้เตรียม มาตรฐานสำหรับตัวแปรและผลลัพธ์ทั้งหมดคือ:

# 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. ถ้าคุณต้องการมันในหลาย ๆ ที่และไม่ต้องการให้ bytecode ของคุณขยายตัวเนื่องจากมีคลาสนิรนามมากมาย - 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() ไม่มีประสิทธิภาพมากและสามารถส่งกลับเฉพาะชื่อคลาสเป็น a ไม่ใช่อินสแตนซ์Thread.currentThread()StringClass<*>


PS

หากคุณต้องการสร้างอินสแตนซ์คนตัดไม้สำหรับ utlet 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áborLipták

1
สิ่งนี้มีประโยชน์เพิ่มเติมโดยไม่ต้องสร้างวัตถุหรือเธรดทุกครั้งที่คุณใช้งาน
Erel Segal-Halevi

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และเรียกใช้จาวา!

*ซิกซี้


ทำไมสิ่งนี้ถึงไม่สร้างขึ้น?
ggb667

ฉลาดและฉันซาบซึ้งกับอารมณ์ขัน แมลงวันในขี้ผึ้งคือชื่อเริ่มต้นของฟังก์ชั่น C Java_MyObject_getClassNameมีชื่อฝังอยู่ วิธีรอบที่ใช้ RegisterNativesJNI แน่นอนว่าคุณต้องให้อาหารกับ JNI FindClass(env, 'com/example/MyObject')ดังนั้นจึงไม่ชนะที่นั่น
Renate

2
@ จัดสรรดังนั้นคำตอบทั้งหมดนี้เป็นเรื่องตลกจริงหรือ ถ้าเป็นเช่นนั้นโปรดอธิบายให้ชัดเจนเพราะคุณรู้ว่าเราควรจะช่วยเหลือผู้อื่นดังนั้นอย่าผลักผู้บริสุทธิ์เข้ามาในกับดัก
Stéphane Gourichon

ไม่ใช่เรื่องตลกของฉันและฉันชี้ให้เห็นว่ามันเป็นเรื่องตลก กรณีใช้งานสำหรับสถานการณ์นี้โดยปกติจะระบุคลาสในบันทึก การขจัดความซับซ้อนออกไปทั้งหมดโดยปกติแล้วการทำเช่นนี้จะลดลงprivate static final String TAG = "MyClass"หรืออย่างprivate static final String TAG = MyClass.class.getSimpleName();ที่สองนั้นเป็นมิตรกับการเปลี่ยนชื่อระดับโลกโดยใช้ IDE
ต่ออายุ

20

ฉันใช้สิ่งนี้เพื่อเริ่ม Log4j Logger ที่ด้านบนของชั้นเรียนของฉัน (หรือคำอธิบายประกอบ)

PRO: Throwable ได้ถูกโหลดแล้วและคุณอาจบันทึกทรัพยากรโดยไม่ใช้ SecurityManager "IO หนา"

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 ที่ไม่เหมาะสม
Søren Boisen

ข้อเสียเพิ่มเติม: verbose เกินไป ช้า (นี่คือจุดรองจริงเพราะมันทำงานเพียงครั้งเดียวเท่านั้นเมื่อโหลดชั้นเรียน)
toolforger

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()จริง แต่มีแนวโน้มที่จะคัดลอก / วางข้อผิดพลาดหากคุณเผยแพร่รหัสนี้ไปยังคลาส / คลาสย่อยจำนวนมากที่คุณต้องการชื่อคลาสนี้

และสูตรของ 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);
  }
}

4

โซลูชันการปรับโครงสร้างที่ปลอดภัยตัดและวางที่หลีกเลี่ยงคำจำกัดความของคลาส ad-hoc ด้านล่าง

เขียนวิธีสแตติกที่กู้คืนชื่อคลาสที่มีความระมัดระวังเพื่อรวมชื่อคลาสในชื่อเมธอด:

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

จากนั้นเรียกคืนด้วยวิธีการคงที่ของคุณ:

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

ความปลอดภัย Refactoring จะได้รับจากการหลีกเลี่ยงการใช้สตริงการตัดและวางเพื่อความปลอดภัยจะได้รับเพราะถ้าคุณตัดและวางเมธอดผู้โทรคุณจะไม่พบ 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.classClassName.thisClass

ด้วยการthisClassกำหนดสิทธิ์การเข้าถึงชื่อคลาสจะกลายเป็น:

thisClass.getName()

1

ฉันต้องการชื่อคลาสในวิธีสแตติกของหลายคลาสดังนั้นฉันจึงใช้ JavaUtil Class ด้วยวิธีการต่อไปนี้:

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

หวังว่ามันจะช่วย!


1
ไม่เพียงเป็นการดีที่จะใช้สิ่งนี้เพราะเวทมนต์หมายเลข 2 (ซึ่งอาจส่งผลให้ NullPointerException) ได้อย่างง่ายดาย แต่คุณต้องพึ่งพาความแม่นยำของเครื่องเสมือนเป็นอย่างมาก จาก javadoc ของเมธอด: * เครื่องเสมือนบางเครื่องอาจยกเว้นสแต็กเฟรมหนึ่งเฟรมหรือมากกว่าจากการติดตามสแต็ก ในกรณีที่รุนแรงเครื่องเสมือนที่ไม่มีข้อมูลการติดตามสแต็กที่เกี่ยวข้องกับเธรดนี้ได้รับอนุญาตให้ส่งคืนอาร์เรย์ที่มีความยาวเป็นศูนย์จากวิธีนี้ *
Shotgun

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.getDeclaringClass().getName()

เพื่อให้ได้เมธอดเองคุณสามารถใช้:

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

3
และคุณจะรู้ได้อย่างไรว่า: "ชื่อชั้น"? :)
alfasin

1
มีClass.forName("class name")ชั้นเรียนให้คุณแล้ว ทำไมคุณต้องการดึงข้อมูลด้วยวิธีการ
Sergey Irisov
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.