Tip Java 124: Lacak langkah Anda di Java 1.4

Saya tidak tahu tentang Anda, tapi saya sangat ingin tahu keberadaan saya. Sebagai seorang pria, saya tidak pernah tersesat, tetapi terkadang saya tidak tahu di mana saya berada. Beberapa tempat, seperti mall, memiliki peta dengan indikator "You Are Here". Demikian pula, Java sekarang memungkinkan kami mencari tahu lokasi kami dengan bantuan sistem. Dalam tip ini, saya akan menunjukkan kepada Anda cara mengekstrak informasi lokasi ini dari sistem secara konsisten dan dapat diandalkan.

Saya sangat yakin sistem runtime harus menyediakan metadata yang cukup tentang sistem itu sendiri sehingga program dapat membuat keputusan yang lebih baik dan menyelesaikan tugas. Java telah mampu untuk introspeksi dan refleksi kelas untuk beberapa waktu, tetapi sampai sekarang ia tidak memiliki kemampuan sederhana untuk memetakan kode runtime kembali ke posisinya di file kode sumber. Solusi pra-Java 1.4 adalah mengurai pelacakan tumpukan pengecualian secara manual. Sekarang, dengan Java 1.4, kami memiliki solusi yang lebih baik.

Pisahkan jejak tumpukan?

Solusi pra-Java 1.4 untuk mengumpulkan informasi lokasi adalah dengan mengurai printStackTrace()keluaran pengecualian secara manual . Berikut adalah contoh pelacakan tumpukan sederhana:

java.lang.Throwable di boo.hoo.StackTrace.bar (StackTrace.java:223) di boo.hoo.StackTrace.foo (StackTrace.java:218) di boo.hoo.StackTrace.main (StackTrace.java:54) 

Memisahkan kode di atas bukanlah masalah penguraian utama. Tapi bagaimana dengan ini?

java.lang.Throwable di boo.hoo.StackTrace $ FirstNested $ SecondNested. (StackTrace.java:267) di boo.hoo.StackTrace $ FirstNested. (StackTrace.java:256) di boo.hoo.StackTrace. (StackTrace.java : 246) di boo.hoo.StackTrace.main (StackTrace.java:70) 

Ugh. Apa sebenarnya arti semua goobley-guk aneh itu, dan mengapa saya harus menguraikannya? Jelas, sistem sudah melacak informasi lokasi tersebut, karena ia dapat membangun pelacakan tumpukan tersebut. Jadi mengapa informasi lokasi itu tidak tersedia secara langsung? Nah, dengan Java 1.4, akhirnya.

Selain itu, perlu diingat bahwa dalam menghadapi kompiler JIT (just-in-time) dan dinamis, pengoptimalan kompiler seperti HotSpot Sun Microsystems, informasi file dan nomor baris mungkin tidak ada. Tujuan "performance or bust" tentu bisa mengganggu.

Java 1.4 Dapat dilempar untuk menyelamatkan!

Setelah menoleransi keluhan selama bertahun-tahun, Sun Microsystems akhirnya memperluas java.lang.Throwablekelas dengan getStackTrace()metode tersebut. getStackTrace()mengembalikan larik StackTraceElements, di mana setiap StackTraceElementobjek menyediakan sarana untuk mengekstrak informasi lokasi secara lebih atau kurang secara langsung.

Untuk memperoleh informasi pemetaan tersebut, Anda masih membuat Throwableinstance di lokasi menarik di kode Anda:

// ... public static void main (String [] args) {Throwable ex = new Throwable (); // ...

Kode ini memposisikan titik itu di awal main().

Tentu saja, tidak ada gunanya mengumpulkan informasi itu tanpa melakukan sesuatu dengannya. Untuk tip ini, kita akan menggunakan setiap metode yang mendasari StackTraceElements untuk mengekstrak dan menampilkan semua informasi yang kita bisa.

Program StackTrace.javacontoh`` menunjukkan cara mengekstrak informasi lokasi dengan beberapa contoh. Anda akan membutuhkan J2SE (Java 2 Platform, Standard Edition) 1.4 SDK untuk mengkompilasi dan menjalankan program contoh.

Untuk mengekstrak dan menampilkan informasi pemetaan, kode contoh menggunakan metode helper displayStackTraceInformation(), dengan idiom penggunaan dasar berikut:

// ... public void crashAndBurnout () {// ... displayStackTraceInformation (new Throwable ()); // ...} // ...

The displayStackTraceInformation()kode adalah cukup sederhana:

public static boolean displayStackTraceInformation (Throwable ex, boolean displayAll) {if (null == ex) {System.out.println ("Referensi jejak tumpukan nol! Bailing ..."); return false; } System.out.println ("Tumpukan menurut printStackTrace (): \ n"); ex.printStackTrace (); System.out.println (""); StackTraceElement [] stackElements = ex.getStackTrace (); if (displayAll) {System.out.println ("The" + stackElements.length + "element" + ((stackElements.length == 1)? "": "s") + "dari jejak tumpukan: \ n" ); } else {System.out.println ("Elemen teratas dari" + stackElements.length + "elemen stack trace: \ n"); } untuk (int lcv = 0; lcv <stackElements.length; lcv ++) {System.out.println ("Filename:" + stackElements [lcv] .getFileName ());System.out.println ("Nomor baris:" + stackElements [lcv] .getLineNumber ()); String className = stackElements [lcv] .getClassName (); String packageName = extractPackageName (className); String simpleClassName = extractSimpleClassName (className); System.out.println ("Nama paket:" + ("" .equals (nama paket)? "[Paket default]": nama paket)); System.out.println ("Nama kelas lengkap:" + nama kelas); System.out.println ("Nama kelas sederhana:" + simpleClassName); System.out.println ("Nama kelas yang tidak dihapus:" + unmungeSimpleClassName (simpleClassName)); System.out.println ("Nama kelas langsung:" + extractDirectClassName (simpleClassName)); System.out.println ("Nama metode:" + stackElements [lcv] .getMethodName ()); System.out.println ("Metode asli ?:"+ stackElements [lcv] .isNativeMethod ()); System.out.println ("toString ():" + stackElements [lcv] .toString ()); System.out.println (""); if (! displayAll) return true; } System.out.println (""); kembali benar; } // Akhir displayStackTraceInformation ().

Pada dasarnya, kami memanggil getStackTrace()pass-in Throwable, dan kemudian melakukan loop melalui individu StackTraceElement, mengekstraksi informasi pemetaan sebanyak mungkin.

Perhatikan bagian kecil dari displayAllparameter yang diperkenalkan. displayAllmemungkinkan situs pemanggil memutuskan apakah akan menampilkan semua StackTraceElementatau hanya elemen paling atas tumpukan atau tidak . Program contoh menggunakan displayAllparameter untuk membatasi keluaran ke jumlah yang wajar.

Sebagian besar informasi pelacakan tumpukan secara langsung berguna. Misalnya, StackTraceElement.getMethodName()mengembalikan string yang berisi nama metode, sedangkan StackTraceElement.getFileName()mengembalikan string dengan nama file sumber asli. Baca StackTraceElementJavadoc untuk daftar lengkap metode.

Nama kelas berlimpah!

Seperti yang mungkin Anda perhatikan, displayStackTraceInformation()kode tersebut menggunakan beberapa metode pembantu tambahan untuk memisahkan nilai yang dikembalikan StackTraceElement.getClassName(). Metode helper tersebut diperlukan karena StackTraceElement.getClassName()mengembalikan nama kelas yang sepenuhnya memenuhi syarat, dan StackTraceElementtidak memiliki metode lain untuk menyediakan bagian dasar dari nama kelas yang sepenuhnya memenuhi syarat. Kita akan belajar tentang setiap metode pembantu tambahan dengan mengerjakan berbagai contoh penggunaan displayStackTraceInformation().

Paket default vs. bernama

Diberikan nama kelas yang sepenuhnya memenuhi syarat, extractPackageName()berikan nama paket tempat kelas tersebut berada:

publik String extractPackageName statis (String fullClassName) ("" .equals (fullClassName))) return ""; int lastDot = fullClassName.lastIndexOf ('.'); if (0> = lastDot) return ""; return fullClassName.substring (0, lastDot);  

Pada dasarnya, extractPackageNamemengekstrak semua yang mendahului titik terakhir dalam nama kelas yang sepenuhnya memenuhi syarat. Informasi sebelumnya kebetulan adalah nama paket.

Catatan: Anda dapat mengomentari / menghapus komentar pernyataan paket di bagian atas StackTrace.javauntuk mempelajari perbedaan antara menjalankan program contoh dalam paket default tanpa nama versus menjalankannya dalam boo.hoopaket. Misalnya, ketika tanda komentar, tampilan dari elemen tumpukan paling atas untuk panggilan ke bar()dari foo()dari main()akan terlihat seperti ini:

Nama file: StackTrace.java Nomor baris: 227 Nama paket: boo.hoo Nama kelas lengkap: boo.hoo.StackTrace Nama kelas sederhana: StackTrace Nama kelas yang tidak dimatikan: StackTrace Nama kelas langsung: StackTrace Nama metode: bar Metode asli ?: false toString ( ): boo.hoo.StackTrace.bar (StackTrace.java:227) 

Alternatifnya, jika Anda mengomentari pernyataan paket, elemen tumpukan di atas akan menyerupai ini:

Nama file: StackTrace.java Nomor baris: 227 Nama paket: [paket default] Nama kelas lengkap: StackTrace Nama kelas sederhana: StackTrace Nama kelas yang tidak dirubah: StackTrace Nama kelas langsung: StackTrace Nama metode: bar Metode asli ?: false toString (): StackTrace .bar (StackTrace.java:227) 

Bisakah nama kelas menjadi sederhana?

Metode pembantu berikutnya yang kami gunakan adalah extractSimpleClassName(). Seperti yang akan Anda lihat, hasil metode tersebut tidak selalu sederhana, tetapi saya ingin membedakan dengan jelas nama kelas yang disederhanakan ini dari nama kelas yang sepenuhnya memenuhi syarat.

Pada dasarnya, extractSimpleClassName()pelengkap extractPackageName():

publik String extractSimpleClassName statis (String fullClassName) ("" .equals (fullClassName))) return ""; int lastDot = fullClassName.lastIndexOf ('.'); if (0> lastDot) mengembalikan fullClassName; return fullClassName.substring (++ lastDot);  

Dengan kata lain, extractSimpleClassName()mengembalikan semuanya setelah titik terakhir ( .) dari nama kelas yang sepenuhnya memenuhi syarat. Sebagai contoh, dari pemanggilan yang sama ke bar()atas, kita melihat bahwa nama kelas sederhana itu saja StackTrace, apakah kode tersebut merupakan bagian dari paket default atau paket bernama.

Kami mendapatkan hasil yang lebih menarik saat kami mengalihkan perhatian ke kelas bersarang. Dalam program contoh, saya membuat dua level bersarang, kelas bernama ( FirstNesteddan FirstNested.SecondNested) bersama dengan tambahan, kelas dalam anonim (di dalam FirstNested.SecondNested).

Seluruh penggunaan bersarang dimulai dengan:

publik StackTrace (boolean na) {StackTrace.FirstNested nested = new StackTrace.FirstNested (); }

Perhatikan bahwa parameter boolean ( na) tidak berarti apa-apa. Saya baru saja menambahkannya karena konstruktor lain perlu dibedakan.

Berikut adalah kelas bersarang:

kelas publik FirstNested {public FirstNested () {StackTrace.displayStackTraceInformation (new Throwable ()); StackTrace.FirstNested.SecondNested yan = new StackTrace.FirstNested.SecondNested (); System.out.println ("Membuang dari dalam hogwash ():"); yan.hogwash (); } kelas publik SecondNested {public SecondNested () {StackTrace.displayStackTraceInformation (new Throwable ()); } public void hogwash () {StackTrace.displayStackTraceInformation (new Throwable ()); Whackable whacked = new Whackable () {public void whack () {StackTrace.displayStackTraceInformation (new Throwable ()); }}; // Akhir dari kelas anggota anonim. whacked.whack (); } // Akhir dari omong kosong (). } // Akhir dari kelas anggota FirstNested.SecondNexted. } // Akhir dari kelas anggota FirstNested.

Elemen tumpukan paling atas untuk SecondNestedkonstruktor terlihat seperti ini:

Nama file: StackTrace.java Nomor baris: 267 Nama paket: boo.hoo Nama kelas lengkap: boo.hoo.StackTrace $ FirstNested $ SecondNested Nama kelas sederhana: StackTrace $ FirstNested $ SecondNested Nama kelas Unmunged: StackTrace.FirstNested.SecondNested Nama kelas Direct: Nama Metode SecondNested: Metode Native ?: false toString (): boo.hoo.StackTrace $ FirstNested $ SecondNested. (StackTrace.java:267) 

Anda dapat melihat bahwa nama kelas yang sederhana tidak sesederhana itu dalam kasus ini. Kelas bertingkat dibedakan dari kelas bertingkat tingkat yang lebih tinggi dan dari kelas tingkat atas dengan menggunakan karakter tanda dolar ( $). Jadi, secara teknis, nama "sederhana" dari kelas bertingkat kedua adalah StackTrace$FirstNested$SecondNested.

Saya telah memberikan unmungeSimpleClassName()metode untuk mengganti tanda dolar dengan periode kelengkapan.

Karena saya keras kepala, saya masih ingin mendapatkan nama kelas yang benar-benar sederhana, jadi saya membuat extractDirectClassName():