Metode Sintetis Java

Dalam posting blog ini, saya melihat konsep metode sintetik Java. Postingan tersebut merangkum apa itu metode sintetik Java, bagaimana seseorang dapat dibuat dan diidentifikasi, dan implikasi metode sintetis Java pada pengembangan Java.

Spesifikasi Bahasa Java (bagian 13.1) menyatakan "Setiap konstruksi yang diperkenalkan oleh compiler yang tidak memiliki konstruksi yang sesuai dalam kode sumber harus ditandai sebagai sintetik, kecuali untuk konstruktor default dan metode inisialisasi kelas." Petunjuk lebih lanjut tentang arti sintetik di Jawa dapat ditemukan di dokumentasi Javadoc untuk Member.isSynthetic (). Dokumentasi metode tersebut menyatakan bahwa ia mengembalikan "true jika dan hanya jika anggota ini diperkenalkan oleh kompilator." Saya suka definisi yang sangat singkat dari "sintetik": konstruksi Java yang diperkenalkan oleh kompilator.

Compiler Java harus membuat metode sintetik pada kelas bersarang ketika atributnya yang ditentukan dengan pengubah privat diakses oleh kelas yang melingkupinya. Contoh kode berikutnya menunjukkan situasi ini.

DemonstrateSyntheticMethods.java (Kelas yang Melampirkan Memanggil Atribut Pribadi Satu Kelas Bersarang)

package dustin.examples; import java.util.Calendar; import static java.lang.System.out; public final class DemonstrateSyntheticMethods { public static void main(final String[] arguments) { DemonstrateSyntheticMethods.NestedClass nested = new DemonstrateSyntheticMethods.NestedClass(); out.println("String: " + nested.highlyConfidential); } private static final class NestedClass { private String highlyConfidential = "Don't tell anyone about me"; private int highlyConfidentialInt = 42; private Calendar highlyConfidentialCalendar = Calendar.getInstance(); private boolean highlyConfidentialBoolean = true; } } 

Kode di atas dikompilasi tanpa insiden. Saat javap dijalankan terhadap .classfile yang dikompilasi , hasilnya seperti yang ditunjukkan pada cuplikan layar berikut.

Seperti yang ditunjukkan oleh cuplikan layar di atas, metode sintetis dengan nama access$100tersebut telah dibuat pada kelas bersarang NestedClassuntuk memberikan String pribadinya ke kelas yang melingkupi. Perhatikan bahwa metode sintetik hanya ditambahkan untuk satu atribut privat dari NestedClass yang diakses oleh kelas yang melingkupinya. Jika saya mengubah kelas terlampir untuk mengakses semua atribut pribadi NestedClass, metode sintetis tambahan akan dibuat. Contoh kode berikutnya mendemonstrasikan melakukan hal ini dan cuplikan layar yang mengikutinya membuktikan bahwa empat metode sintetis dibuat dalam kasus tersebut.

DemonstrateSyntheticMethods.java (Kelas Penutup Memanggil Empat Atribut Pribadi Kelas Bersarang)

package dustin.examples; import java.util.Calendar; import static java.lang.System.out; public final class DemonstrateSyntheticMethods { public static void main(final String[] arguments) { DemonstrateSyntheticMethods.NestedClass nested = new DemonstrateSyntheticMethods.NestedClass(); out.println("String: " + nested.highlyConfidential); out.println("Int: " + nested.highlyConfidentialInt); out.println("Calendar: " + nested.highlyConfidentialCalendar); out.println("Boolean: " + nested.highlyConfidentialBoolean); } private static final class NestedClass { private String highlyConfidential = "Don't tell anyone about me"; private int highlyConfidentialInt = 42; private Calendar highlyConfidentialCalendar = Calendar.getInstance(); private boolean highlyConfidentialBoolean = true; } } 

Seperti yang ditunjukkan oleh dua cuplikan kode sebelumnya di atas dan gambar terkait, compiler Java memperkenalkan metode sintetis sesuai kebutuhan. Ketika hanya satu dari atribut privat kelas bertingkat yang diakses oleh kelas yang melingkupi, hanya satu metode sintetik ( access$100) yang dibuat oleh kompilator. Namun, ketika semua empat atribut pribadi dari kelas bersarang yang diakses oleh kelas melampirkan, empat metode sintetik yang dihasilkan oleh compiler ( access$100, access$200, access$300, dan access$400).

Dalam semua kasus kelas penutup mengakses data pribadi kelas bertingkatnya, metode sintetis dibuat untuk memungkinkan akses tersebut terjadi. Apa yang terjadi jika kelas bersarang menyediakan aksesor untuk data pribadinya yang dapat digunakan oleh kelas yang melingkupi? Itu ditunjukkan dalam daftar kode berikutnya dan dalam keluarannya seperti yang ditunjukkan pada cuplikan layar berikutnya.

DemonstrateSyntheticMethods.java dengan Pengakses Publik Kelas Bersarang untuk Data Pribadi

package dustin.examples; import java.util.Calendar; import java.util.Date; import static java.lang.System.out; public final class DemonstrateSyntheticMethods { public static void main(final String[] arguments) { DemonstrateSyntheticMethods.NestedClass nested = new DemonstrateSyntheticMethods.NestedClass(); out.println("String: " + nested.highlyConfidential); out.println("Int: " + nested.highlyConfidentialInt); out.println("Calendar: " + nested.highlyConfidentialCalendar); out.println("Boolean: " + nested.highlyConfidentialBoolean); out.println("Date: " + nested.getDate()); } private static final class NestedClass { private String highlyConfidential = "Don't tell anyone about me"; private int highlyConfidentialInt = 42; private Calendar highlyConfidentialCalendar = Calendar.getInstance(); private boolean highlyConfidentialBoolean = true; private Date date = new Date(); public Date getDate() { return this.date; } } } 

Cuplikan layar di atas menunjukkan bahwa kompilator tidak perlu membuat metode sintetik untuk mengakses atribut Tanggal pribadi di kelas bersarang karena kelas yang melingkupinya mengakses atribut itu melalui getDate()metode yang disediakan . Bahkan dengan getDate()disediakan, kompilator akan menghasilkan metode sintetik untuk mengakses jika datekode pelingkup telah ditulis untuk mengakses dateatribut secara langsung (sebagai properti) daripada melalui metode pengakses .

Cuplikan layar terakhir menampilkan pengamatan lain. Seperti yang getDate()ditunjukkan metode yang baru ditambahkan dalam snapshot layar itu, pengubah seperti publictermasuk dalam keluaran javap. Karena tidak ada pengubah yang ditampilkan untuk metode sintetik yang dibuat oleh kompilator, kita tahu bahwa itu adalah tingkat paket (atau paket-pribadi). Singkatnya, kompilator telah membuat metode paket-privat untuk mengakses atribut privat.

API refleksi Java menyediakan pendekatan lain untuk menentukan metode sintetik. Daftar kode berikutnya adalah untuk skrip Groovy yang akan menggunakan API refleksi Java untuk memberikan detail yang mudah tentang metode kelas bersarang yang ditunjukkan di atas.

reflectOnMethods.groovy

#!/usr/bin/env groovy import java.lang.reflect.Method import java.lang.reflect.Modifier if (args == null || args.size() < 2) { println "Outer and nested class names must be provided." println "\nUsage #1: reflectOnMethods qualifiedOuterClassName nestedClassName\n" println "\nUsage #2: groovy -cp classpath reflectOnMethods.groovy qualifiedOuterClassName nestedClassName\n" println "\t1. Include outer and nested classes on classpath if necessary" println "\t2. Do NOT include \$ on front of nested class name.\n" System.exit(-1) } def enclosingClassName = args[0] def nestedClassName = args[1] def fullNestedClassName = enclosingClassName + '$' + nestedClassName def enclosingClass = Class.forName(enclosingClassName) Class nestedClass = null enclosingClass.declaredClasses.each { if (!nestedClass && fullNestedClassName.equals(it.name)) { nestedClass = it } } if (nestedClass == null) { println "Unable to find nested class ${fullNestedClassName}" System.exit(-2) } // Use declaredMethods because don't care about inherited methods nestedClass.declaredMethods.each { print "\nMethod '${it.name}' " print "is ${getScopeModifier(it)} scope, " print "${it.synthetic ? 'is synthetic' : 'is NOT synthetic'}, and " println "${it.bridge ? 'is bridge' : 'is NOT bridge'}." } def String getScopeModifier(Method method) { def modifiers = method.modifiers def isPrivate = Modifier.isPrivate(modifiers) def isPublic = Modifier.isPublic(modifiers) def isProtected = Modifier.isProtected(modifiers) String scopeString = "package-private" // default if (isPublic) { scopeString = "public" } else if (isProtected) { scopeString = "protected" } else if (isPrivate) { scopeString = "private" } return scopeString } 

Ketika skrip Groovy di atas dijalankan terhadap kelas dan kelas bertingkat yang ditunjukkan di atas, hasilnya adalah yang ditampilkan di snapshot layar berikutnya.

Hasil skrip Groovy yang ditunjukkan pada gambar sebelumnya memverifikasi apa yang telah diberitahukan oleh javap kepada kita: ada empat metode sintetik dan satu metode non-sintetik yang ditentukan pada kelas bersarang NestedClass. Skrip ini juga memberi tahu kita bahwa metode sintetik yang dihasilkan kompiler adalah cakupan pribadi paket.

Penambahan metode sintetik ke kelas bersarang pada tingkat cakupan paket-privat bukanlah satu-satunya hal yang dilakukan kompilator dalam contoh di atas. Itu juga mengubah ruang lingkup kelas bersarang itu sendiri dari pengaturan pribadi dalam kode menjadi paket-pribadi dalam .classfile. Memang, meskipun metode sintetik hanya ditambahkan dalam kasus di mana kelas penutup mengakses atribut privat, kompilator selalu menjadikan paket kelas bersarang-privat meskipun itu ditentukan sebagai privat dalam kode. Kabar baiknya adalah ini adalah artefak yang dihasilkan dari proses kompilasi, yang berarti bahwa kode tidak dapat dikompilasi apa adanya terhadap tingkat cakupan kelas bersarang yang berubah atau metode sintetiknya. Runtime adalah saat hal-hal bisa menjadi tidak pasti.

Kelas, Rogue, mencoba mengakses beberapa metode sintetis NestedClass. Kode sumbernya ditampilkan berikutnya, diikuti oleh kesalahan penyusun yang terlihat saat mencoba mengompilasi kode sumber Rogue ini.

Rogue.java mencoba mengakses metode sintetis pada waktu kompilasi

package dustin.examples; import static java.lang.System.out; public class Rogue { public static void main(final String[] arguments) { out.println(DemonstrateSyntheticMethods.NestedClass.getDate()); } } 

Kode di atas tidak akan dikompilasi, bahkan untuk metode non-sintetik getDate(), dan melaporkan kesalahan ini:

Buildfile: C:\java\examples\synthetic\build.xml -init: compile: [javac] Compiling 1 source file to C:\java\examples\synthetic\classes [javac] C:\java\examples\synthetic\src\dustin\examples\Rogue.java:9: dustin.examples.DemonstrateSyntheticMethods.NestedClass has private access in dustin.examples.DemonstrateSyntheticMethods [javac] out.println(DemonstrateSyntheticMethods.NestedClass.getDate()); [javac] ^ [javac] 1 error BUILD FAILED C:\java\examples\synthetic\build.xml:29: Compile failed; see the compiler error output for details. Total time: 1 second 

Seperti yang ditunjukkan oleh pesan kesalahan kompilasi di atas, bahkan metode non-sintetik pada kelas bersarang tidak dapat diakses pada waktu kompilasi karena kelas bertingkat memiliki cakupan privat. Dalam artikelnya Java Insecurity: Accounting for Subtleties That Can Compromise Code, Charlie Lai membahas situasi potensial di mana perubahan yang diperkenalkan oleh compiler ini merupakan kerentanan keamanan. Faisal Feroz melangkah lebih jauh dan menyatakan, dalam posting Bagaimana Menulis Kode Java Aman, "Jangan gunakan Kelas Dalam" (lihat Kelas Bersarang, Dalam, Anggota, dan Tingkat Atas untuk rincian tentang kelas dalam sebagai bagian dari kelas bersarang) .

Banyak dari kita dapat bertahan lama dalam pengembangan Java tanpa memerlukan pemahaman yang signifikan tentang metode sintetik. Namun, ada situasi ketika kesadaran akan hal ini penting. Selain masalah keamanan yang terkait dengan ini, juga harus diperhatikan apa itu saat membaca pelacakan tumpukan. Metode nama-nama seperti access$100, access$200, access$300, access$400, access$500, access$600, dan access$1000dalam jejak stack mencerminkan metode sintetik yang dihasilkan oleh compiler.

Posting Asli Tersedia di //marxsoftware.blogspot.com/

.

Cerita ini, "Metode Sintetis Java", awalnya diterbitkan oleh JavaWorld.