Pada dasarnya, refleksi berarti menggunakan kode program Anda sebagai data.
Oleh karena itu, menggunakan refleksi mungkin merupakan ide yang baik ketika kode program Anda merupakan sumber data yang berguna. (Tapi ada kompromi, jadi itu mungkin bukan ide yang bagus.)
Sebagai contoh, pertimbangkan kelas sederhana:
public class Foo {
public int value;
public string anotherValue;
}
dan Anda ingin menghasilkan XML darinya. Anda dapat menulis kode untuk menghasilkan XML:
public XmlNode generateXml(Foo foo) {
XmlElement root = new XmlElement("Foo");
XmlElement valueElement = new XmlElement("value");
valueElement.add(new XmlText(Integer.toString(foo.value)));
root.add(valueElement);
XmlElement anotherValueElement = new XmlElement("anotherValue");
anotherValueElement.add(new XmlText(foo.anotherValue));
root.add(anotherValueElement);
return root;
}
Tapi ini banyak kode boilerplate, dan setiap kali Anda mengubah kelas, Anda harus memperbarui kode. Sungguh, Anda bisa menggambarkan seperti apa kode ini
- buat elemen XML dengan nama kelas
- untuk setiap properti kelas
- buat elemen XML dengan nama properti
- masukkan nilai properti ke dalam elemen XML
- tambahkan elemen XML ke root
Ini adalah suatu algoritma, dan input algoritma adalah kelas: kita perlu namanya, dan nama, tipe dan nilai propertinya. Di sinilah refleksi masuk: itu memberi Anda akses ke informasi ini. Java memungkinkan Anda untuk memeriksa tipe menggunakan metode Class
kelas.
Beberapa kasus penggunaan lainnya:
- mendefinisikan URL di server web berdasarkan pada nama metode kelas, dan parameter URL berdasarkan argumen metode
- mengubah struktur kelas menjadi definisi tipe GraphQL
- panggil setiap metode kelas yang namanya dimulai dengan "test" sebagai case test unit
Namun, refleksi penuh berarti tidak hanya melihat kode yang ada (yang dengan sendirinya dikenal sebagai "introspeksi"), tetapi juga memodifikasi atau menghasilkan kode. Ada dua kasus penggunaan yang menonjol di Jawa untuk ini: proksi dan ejekan.
Katakanlah Anda memiliki antarmuka:
public interface Froobnicator {
void froobnicateFruits(List<Fruit> fruits);
void froobnicateFuel(Fuel fuel);
// lots of other things to froobnicate
}
dan Anda memiliki implementasi yang melakukan sesuatu yang menarik:
public class PowerFroobnicator implements Froobnicator {
// awesome implementations
}
Dan sebenarnya Anda juga memiliki implementasi kedua:
public class EnergySaverFroobnicator implements Froobnicator {
// efficient implementations
}
Sekarang Anda juga ingin beberapa keluaran log; Anda hanya ingin pesan log setiap kali suatu metode dipanggil. Anda dapat menambahkan output log ke setiap metode secara eksplisit, tetapi itu akan mengganggu, dan Anda harus melakukannya dua kali; satu kali untuk setiap implementasi. (Bahkan lebih ketika Anda menambahkan lebih banyak implementasi.)
Sebagai gantinya, Anda dapat menulis proxy:
public class LoggingFroobnicator implements Froobnicator {
private Logger logger;
private Froobnicator inner;
// constructor that sets those two
public void froobnicateFruits(List<Fruit> fruits) {
logger.logDebug("froobnicateFruits called");
inner.froobnicateFruits(fruits);
}
public void froobnicateFuel(Fuel fuel) {
logger.logDebug("froobnicateFuel( called");
inner.froobnicateFuel(fuel);
}
// lots of other things to froobnicate
}
Namun, sekali lagi, ada pola berulang yang dapat dijelaskan oleh suatu algoritma:
- proksi logger adalah kelas yang mengimplementasikan antarmuka
- ini memiliki konstruktor yang membutuhkan implementasi lain dari antarmuka dan logger
- untuk setiap metode di antarmuka
- implementasi mencatat pesan "$ methodname disebut"
- dan kemudian memanggil metode yang sama pada antarmuka bagian dalam, meneruskan semua argumen
dan input dari algoritma ini adalah definisi antarmuka.
Refleksi memungkinkan Anda untuk menentukan kelas baru menggunakan algoritma ini. Java memungkinkan Anda melakukan ini menggunakan metode java.lang.reflect.Proxy
kelas, dan ada perpustakaan yang memberi Anda lebih banyak kekuatan.
Jadi apa saja kelemahan refleksi?
- Kode Anda menjadi lebih sulit untuk dipahami. Satu tingkat abstraksi Anda dihapus lebih jauh dari efek konkret kode Anda.
- Kode Anda menjadi lebih sulit untuk di-debug. Terutama dengan pustaka yang menghasilkan kode, kode yang dieksekusi mungkin bukan kode yang Anda tulis, tetapi kode yang Anda hasilkan, dan debugger mungkin tidak dapat menunjukkan kode itu kepada Anda (atau membiarkan Anda menempatkan breakpoints).
- Kode Anda menjadi lebih lambat. Membaca informasi jenis secara dinamis dan mengakses bidang dengan gagang runtime alih-alih akses pengodean lebih lambat. Pembuatan kode dinamis dapat mengurangi efek ini, dengan biaya lebih sulit untuk di-debug.
- Kode Anda mungkin menjadi lebih rapuh. Akses refleksi dinamis bukan tipe-diperiksa oleh kompiler, tetapi melemparkan kesalahan saat runtime.