Mendapatkan nama kelas dari metode statis di Jawa

244

Bagaimana seseorang bisa mendapatkan nama kelas dari metode statis di kelas itu. Sebagai contoh

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

Singkatnya, saya sebenarnya ingin mengembalikan nama kelas sebagai bagian dari pesan dalam pengecualian.

Miles D
sumber
try{ throw new RuntimeEsception();} catch(RuntimeEcxeption e){return e.getstackTrace()[1].getClassName();}
arminvanbuuren

Jawaban:

231

Untuk mendukung refactoring dengan benar (ganti nama kelas), maka Anda harus menggunakan:

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

atau (terima kasih kepada @James Van Huis ):

 MyClass.class.getSimpleName(); // class name and no more
toolkit
sumber
136
Jika Anda akan melakukan hard-code dalam pengetahuan tentang MyClass seperti itu, maka Anda sebaiknya melakukan String name = "MyClass"; !
John Topley
111
Namun, refactoring nama kelas di IDE Anda tidak akan berfungsi dengan baik.
James Van Huis
9
Benar. Meskipun MyClass.class akan memastikan baris ini tidak dilupakan dengan refactoring 'perubahan nama kelas'
toolkit
19
Saya berharap "ini" bekerja dalam konteks statis berarti Kelas saat ini di Jawa, bahwa "class.xxx" itu diizinkan dalam contoh atau kode statis berarti kelas ini! Masalah dengan ini adalah bahwa MyClass adalah verbose dan redundan, dalam konteksnya. Tapi sama halnya seperti saya suka Jawa, itu tampaknya condong ke arah verbositas.
Lawrence Dol
51
Bagaimana jika saya memanggil metode statis dalam subclass, dan saya ingin nama subclass?
Edward Falk
120

Lakukan apa yang dikatakan toolkit. Jangan melakukan hal seperti ini:

return new Object() { }.getClass().getEnclosingClass();
Tom Hawtin - tackline
sumber
7
Jika kelas meluas yang lain, ini tidak mengembalikan kelas yang sebenarnya, hanya kelas dasar.
Luis Soeiro
1
@LuisSoeiro Saya percaya ini mengembalikan kelas bahwa metode ini didefinisikan. Saya tidak yakin bagaimana faktor-faktor kelas dasar ke dalam konteks statis.
Tom Hawtin - tackline
8
Saya tidak mengerti mengapa getClass () tidak bisa statis. "Idiom" ini tidak dibutuhkan.
mmirwaldt
1
@mmirwaldt getClassmengembalikan tipe runtime, jadi tidak bisa statis.
Tom Hawtin - tackline
5
Inilah jawaban sebenarnya. Karena Anda tidak perlu menuliskan nama kelas Anda dalam kode Anda sendiri.
Bobs
99

Di Java 7+ Anda dapat melakukan ini dalam metode / bidang statis:

MethodHandles.lookup().lookupClass()
Kendali
sumber
Saya akan mengatakan Reflection.getCallerClass(). Tapi itu memberi peringatan tentang berada di paket 'matahari'. Jadi ini mungkin solusi yang lebih baik.
Foumpie
1
@ Foumpie: Java 9 akan memperkenalkan API resmi yang akan menggantikan hal tidak resmi ini Reflection.getCallerClass(). Ini agak rumit untuk operasi sepele nya, yaitu Optional<Class<?>> myself = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) .walk(s -> s.map(StackWalker.StackFrame::getDeclaringClass) .findFirst());, tetapi tentu saja, itu terhubung dengan fakta bahwa itu akan jauh lebih kuat.
Holger
6
Ini dengan mudah solusi terbaik. Itu menghindari kebutuhan untuk menentukan nama kelas yang sebenarnya, itu tidak jelas, itu bukan peretasan dan menurut posting Artyom Krivolapov di bawah ini juga sejauh ini merupakan pendekatan tercepat.
skomisa
@Rein Apakah ada cara mendapatkan kelas runtime jika ini dipanggil di kelas dasar?
Glide
59

Jadi, kita memiliki situasi ketika kita perlu secara statis mendapatkan objek kelas atau nama lengkap / kelas sederhana tanpa penggunaan MyClass.classsintaks yang eksplisit .

Ini bisa sangat berguna dalam beberapa kasus, misalnya contoh logger untuk fungsi tingkat atas (dalam hal ini kotlin membuat kelas Java statis yang tidak dapat diakses dari kode kotlin).

Kami memiliki beberapa varian berbeda untuk mendapatkan informasi ini:

  1. new Object(){}.getClass().getEnclosingClass();
    dicatat oleh Tom Hawtin - tackline

  2. getClassContext()[0].getName();dari yang SecurityManager
    dicatat oleh Christoffer

  3. new Throwable().getStackTrace()[0].getClassName();
    menurut hitungan ludwig

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

  5. dan akhirnya dahsyat
    MethodHandles.lookup().lookupClass();
    dari Rein


Saya sudah menyiapkan tolok ukur untuk semua varian dan hasil adalah:

# 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


Kesimpulan

  1. Varian terbaik untuk digunakan , agak bersih dan sangat cepat.
    Hanya tersedia sejak Java 7 dan Android API 26!
 MethodHandles.lookup().lookupClass();
  1. Jika Anda membutuhkan fungsi ini untuk Android atau Java 6, Anda dapat menggunakan varian terbaik kedua. Ini agak cepat juga, tetapi menciptakan kelas anonim di setiap tempat penggunaan :(
 new Object(){}.getClass().getEnclosingClass();
  1. Jika Anda membutuhkannya di banyak tempat dan tidak ingin kode byte Anda kembung karena banyak kelas anonim - SecurityManageradalah teman Anda (opsi terbaik ketiga).

    Tapi Anda tidak bisa begitu saja menelepon getClassContext()- itu dilindungi di SecurityManagerkelas. Anda membutuhkan beberapa kelas pembantu seperti ini:

 // 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. Anda mungkin tidak perlu menggunakan dua varian terakhir berdasarkan getStackTrace()dari atau dari Thread.currentThread(). Sangat tidak efisien dan hanya dapat mengembalikan nama kelas sebagai String, bukan Class<*>turunannya.


PS

Jika Anda ingin membuat instance logger untuk utilitas kotlin statis (seperti saya :), Anda dapat menggunakan bantuan ini:

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

Contoh penggunaan:

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
}
Artyom Krivolapov
sumber
38

Instruksi ini berfungsi dengan baik:

Thread.currentThread().getStackTrace()[1].getClassName();
Keksi
sumber
5
Hati-hati, itu bisa sangat lambat. Namun Anda dapat menyalinnya.
Gábor Lipták
1
Ini memiliki manfaat tambahan karena tidak harus membuat Obyek atau Thread setiap kali Anda menggunakannya.
Erel Segal-Halevi
5
@ErelSegalHalevi Ini menciptakan banyak sekali StackTraceElement di latar belakang :(
Navin
4
Jika Anda melihat kode sumber Thread.getStackTrace()Anda akan melihat bahwa itu tidak melakukan apa-apa selain return (new Exception()).getStackTrace();dalam kasus dipanggil pada currentThread(). Jadi solusi @count ludwig adalah cara yang lebih langsung untuk mencapai hal yang sama.
T-Bull
34

Anda dapat melakukan sesuatu yang sangat manis dengan menggunakan JNI seperti ini:

MyObject.java:

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

    public static native String getClassName();

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

kemudian:

javac MyObject.java
javah -jni MyObject

kemudian:

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

Kemudian kompilasi C menjadi perpustakaan bersama yang disebut libclassname.sodan jalankan java!

*tertawa kecil

seumur hidup
sumber
Mengapa ini tidak ada di dalamnya?
ggb667
Pintar, dan saya menghargai humornya. Lalat dalam salep adalah bahwa nama default fungsi C Java_MyObject_getClassNamememiliki nama yang disematkan. Cara mengatasinya adalah dengan menggunakan JNI RegisterNatives. Tentu saja Anda harus memberi makan itu dengan JNI FindClass(env, 'com/example/MyObject'), jadi tidak ada kemenangan di sana juga.
Renate
2
@Renate, jadi seluruh jawaban ini sebenarnya lelucon? Jika demikian, tolong buat ini sangat eksplisit karena Anda tahu, kami seharusnya membantu orang lain, jadi jangan masukkan orang yang tidak bersalah ke dalam perangkap.
Stéphane Gourichon
Yah, itu bukan lelucon saya dan saya menunjukkan bahwa itu adalah lelucon. Kasus penggunaan untuk skenario ini biasanya untuk mengidentifikasi kelas dalam log. Menghapus semua kerumitan, biasanya bermuara pada melakukan salah satu: private static final String TAG = "MyClass"atau yang private static final String TAG = MyClass.class.getSimpleName();kedua lebih ramah untuk penggantian nama kelas global menggunakan IDE.
Renate
20

Saya menggunakan ini untuk init Log4j Logger di bagian atas kelas saya (atau membubuhi keterangan).

PRO: Throwable sudah dimuat dan Anda dapat menghemat sumber daya dengan tidak menggunakan SecurityManager "IO heavy".

CON: Beberapa pertanyaan apakah ini akan bekerja untuk semua 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()); 
hitung ludwig
sumber
Buat kelas pengecualian Anda sendiri sehingga jvm tidak akan repot: jhocr.googlecode.com/svn/trunk/src/main/java/com/googlecode/…
4F2E4A2E
1
Jika Anda akan menyarankan sesuatu yang mengerikan seperti solusi ini, harap setidaknya kaitkan pro Anda dengan solusi alami menggunakan MyClass.class.getName (), alih-alih solusi mengerikan lainnya seperti menyalahgunakan SecurityManager.
Søren Boisen
Lebih kontra: terlalu bertele-tele; lambat (ini sebenarnya adalah titik minor karena hanya berjalan sekali, ketika kelas dimuat).
toolforger
13

Pelanggaran Manajer Keamanan

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

Atau, jika tidak disetel, gunakan kelas dalam yang memperluasnya (contoh di bawah ini disalin secara memalukan dari Real's HowTo ):

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

Jika Anda ingin seluruh nama paket bersamanya, hubungi:

String name = MyClass.class.getCanonicalName();

Jika Anda hanya menginginkan elemen terakhir, hubungi:

String name = MyClass.class.getSimpleName();
James Van Huis
sumber
5

Penggunaan kelas penelepon secara verbal seperti yang MyClass.class.getName()sebenarnya dilakukan, tetapi cenderung untuk menyalin / menempel kesalahan jika Anda menyebarkan kode ini ke banyak kelas / subkelas di mana Anda memerlukan nama kelas ini.

Dan resep Tom Hawtin sebenarnya tidak buruk, kita hanya perlu memasaknya dengan cara yang benar :)

Jika Anda memiliki kelas dasar dengan metode statis yang dapat dipanggil dari subclass, dan metode statis ini perlu mengetahui kelas penelepon yang sebenarnya, ini dapat dicapai seperti berikut:

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);
  }
}
Sergey Ushakov
sumber
4

Solusi yang aman untuk refactoring, cut & paste-safe yang menghindari definisi kelas ad-hoc di bawah ini.

Tulis metode statis yang memulihkan nama kelas dengan hati-hati untuk memasukkan nama kelas dalam nama metode:

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

lalu ingat dalam metode statis Anda:

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

Keamanan refactoring diberikan dengan menghindari penggunaan string, keamanan cut & paste diberikan karena jika Anda memotong & menempelkan metode pemanggil Anda tidak akan menemukan getMyClassName () di kelas "MyClass2" target, sehingga Anda akan dipaksa untuk mendefinisikan ulang dan memperbaruinya.

avalori
sumber
3

Karena pertanyaannya Sesuatu seperti `this.class` bukan` ClassName.class`? ditandai sebagai duplikat untuk yang ini (yang dapat diperdebatkan karena pertanyaan itu adalah tentang kelas daripada nama kelas), saya memposting jawabannya di sini:

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

Penting untuk didefinisikan thisClasssebagai pribadi karena:
1) itu tidak boleh diwarisi: kelas turunan harus mendefinisikan sendiri thisClassatau menghasilkan pesan kesalahan
2) referensi dari kelas lain harus dilakukan ClassName.classdaripada ClassName.thisClass.

Dengan thisClassditentukan, akses ke nama kelas menjadi:

thisClass.getName()
18446744073709551615
sumber
1

Saya membutuhkan nama kelas dalam metode statis beberapa kelas jadi saya menerapkan JavaUtil Class dengan metode berikut:

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

Semoga ini bisa membantu!

phaderer
sumber
1
Tidak hanya itu buruk untuk menggunakan ini karena angka ajaib 2 (yang dapat dengan mudah menghasilkan NullPointerException), tetapi Anda sangat bergantung pada keakuratan mesin virtual. Dari javadoc metode: * Beberapa mesin virtual mungkin, dalam beberapa keadaan, menghilangkan satu atau lebih tumpukan frame dari jejak stack. Dalam kasus ekstrem, mesin virtual yang tidak memiliki informasi jejak tumpukan mengenai utas ini diizinkan untuk mengembalikan array nol panjang dari metode ini. *
Shotgun
0

Saya telah menggunakan dua pendekatan ini untuk keduanya staticdan non staticskenario:

Kelas utama:

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

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

Cara memberikan nama kelas:

cara non statis:

private AndroidLogger mLogger = new AndroidLogger(this);

Cara statis:

private static AndroidLogger mLogger = new AndroidLogger(Myclass.class.getSimpleName());
0xAliHn
sumber
-1

Jika Anda menggunakan refleksi, Anda bisa mendapatkan objek Metode dan kemudian:

method.getDeclaringClass().getName()

Untuk mendapatkan Metode itu sendiri, Anda mungkin dapat menggunakan:

Class<?> c = Class.forName("class name");
Method  method = c.getDeclaredMethod ("method name", parameterTypes)
Franzé Jr.
sumber
3
Dan bagaimana Anda tahu apa itu: "nama kelas"? :)
alfasin
1
Setelah Class.forName("class name")memberi Anda kelas. Mengapa Anda ingin mengambilnya melalui Metode?
Sergey Irisov