Mengapa saya harus menggunakan refleksi?

29

Saya baru ke Jawa; melalui studi saya, saya membaca bahwa refleksi digunakan untuk memanggil kelas dan metode, dan untuk mengetahui metode mana yang diterapkan atau tidak.

Kapan saya harus menggunakan refleksi, dan apa perbedaan antara menggunakan refleksi dan instantiating objek dan memanggil metode dengan cara tradisional?

Hamzah khammash
sumber
10
Silakan lakukan bagian riset Anda sebelum memposting. Ada banyak materi di StackExchange (seperti yang dicatat @Jalayn) dan web secara umum tentang refleksi. Saya sarankan Anda membaca misalnya Tutorial Java tentang Refleksi dan kembali setelah itu jika Anda memiliki pertanyaan yang lebih konkret.
Péter Török
1
Pasti ada sejuta dupes.
DeadMG
3
Lebih dari beberapa programmer profesional akan menjawab "sesering mungkin, bahkan mungkin tidak pernah."
Ross Patterson

Jawaban:

38
  • Refleksi jauh lebih lambat daripada hanya memanggil metode dengan nama mereka, karena harus memeriksa metadata dalam bytecode daripada hanya menggunakan alamat dan konstanta yang telah dikompilasi sebelumnya.

  • Refleksi juga lebih kuat: Anda dapat mengambil definisi dari protectedatau finalanggota, menghapus perlindungan dan memanipulasi seolah-olah telah dinyatakan bisa berubah! Jelas ini merongrong banyak jaminan bahasa yang biasanya dibuat untuk program Anda dan bisa sangat, sangat berbahaya.

Dan ini cukup banyak menjelaskan kapan menggunakannya. Biasanya, jangan. Jika Anda ingin memanggil metode, panggil saja. Jika Anda ingin bermutasi anggota, hanya menyatakan itu bisa berubah daripada pergi di belakang kompilasi.

Salah satu penggunaan refleksi dunia nyata yang berguna adalah ketika menulis sebuah kerangka kerja yang harus beroperasi dengan kelas-kelas yang ditentukan pengguna, di mana penulis kerangka kerja tidak tahu apa yang akan menjadi anggota (atau bahkan kelas) akan. Refleksi memungkinkan mereka untuk berurusan dengan kelas apa pun tanpa menyadarinya terlebih dahulu. Sebagai contoh, saya rasa tidak mungkin untuk menulis perpustakaan berorientasi aspek yang kompleks tanpa refleksi.

Sebagai contoh lain, JUnit digunakan untuk menggunakan sedikit refleksi sepele: ia menyebutkan semua metode di kelas Anda, mengasumsikan bahwa semua yang dipanggil testXXXadalah metode pengujian, dan mengeksekusi hanya itu. Tapi sekarang ini bisa dilakukan lebih baik dengan anotasi, dan pada kenyataannya JUnit 4 sebagian besar telah pindah ke anotasi.

Kilian Foth
sumber
6
"Lebih kuat" perlu perawatan. Anda tidak perlu refleksi untuk mendapatkan kelengkapan Turing, jadi tidak ada perhitungan yang membutuhkan refleksi. Tentu saja Turing lengkap tidak mengatakan apa pun tentang jenis kekuatan lain, seperti kemampuan I / O dan, tentu saja, refleksi.
Steve314
1
Refleksi belum tentu "jauh lebih lambat". Anda dapat menggunakan refleksi sekali untuk menghasilkan bytecode pembungkus panggilan langsung.
SK-logika
2
Anda akan tahu kapan Anda membutuhkannya. Saya sering bertanya-tanya mengapa (di luar generasi bahasa) seseorang membutuhkannya. Kemudian, tiba-tiba, saya lakukan ... Harus berjalan naik turun rantai orang tua / anak untuk mengintip / menyodok data ketika saya mendapat panel dari pengembang lain untuk masuk ke salah satu sistem yang saya pelihara.
Brian Knoblauch
@ SK-logic: sebenarnya untuk menghasilkan bytecode Anda tidak perlu refleksi sama sekali (memang refleksi tidak mengandung API untuk manipulasi bytecode sama sekali!).
Joachim Sauer
1
@ JoachimSauer, tentu saja, tetapi Anda akan memerlukan API refleksi untuk memuat bytecode yang dihasilkan ini.
SK-logic
15

Saya pernah seperti Anda dulu, saya tidak tahu banyak tentang refleksi - masih belum - tetapi saya pernah menggunakannya sekali.

Saya memiliki kelas dengan dua kelas batin, dan setiap kelas memiliki banyak metode.

Saya perlu memohon semua metode di kelas batin, dan memakainya secara manual akan terlalu banyak pekerjaan.

Menggunakan refleksi, saya bisa memanggil semua metode ini hanya dalam 2-3 baris kode, bukan jumlah metode itu sendiri.

Mahmoud Hossam
sumber
4
Mengapa downvote?
Mahmoud Hossam
1
Mencairkan pembukaan
Dmitry Minkovsky
1
@MahmoudHossam Mungkin bukan praktik terbaik, tetapi jawaban Anda menggambarkan taktik penting yang bisa diterapkan.
ankush981
13

Saya akan mengelompokkan penggunaan refleksi ke dalam tiga kelompok:

  1. Instantiating kelas yang berubah-ubah. Misalnya, dalam kerangka kerja injeksi ketergantungan, Anda mungkin menyatakan bahwa antarmuka ThingDoer diimplementasikan oleh kelas NetworkThingDoer. Kerangka kerja kemudian akan menemukan konstruktor dari NetworkThingDoer dan instantiate.
  2. Marshalling dan unmarshalling ke beberapa format lain. Misalnya, memetakan objek dengan getter dan pengaturan yang mengikuti konvensi bean ke JSON dan kembali lagi. Kode sebenarnya tidak tahu nama-nama bidang atau metode, itu hanya memeriksa kelas.
  3. Membungkus kelas dalam lapisan pengalihan (mungkin Daftar itu tidak benar-benar dimuat, tetapi hanya sebuah penunjuk ke sesuatu yang tahu cara mengambilnya dari database) atau memalsukan seluruh kelas (jMock akan membuat kelas sintetis yang mengimplementasikan antarmuka) untuk tujuan pengujian).
Kevin Peterson
sumber
Ini adalah penjelasan refleksi terbaik yang saya temukan di StackExchange. Sebagian besar jawaban mengulangi apa yang dikatakan Java Trail (yaitu "Anda dapat mengakses semua properti ini", tetapi tidak mengapa Anda mau), memberikan contoh melakukan hal-hal dengan refleksi yang jauh lebih mudah dilakukan tanpa, atau memberikan jawaban samar tentang bagaimana Spring menggunakannya. Jawaban ini sebenarnya memberikan tiga contoh valid yang TIDAK BISA dengan mudah dikerjakan oleh JVM tanpa refleksi. Terima kasih!
ndm13
3

Refleksi memungkinkan suatu program bekerja dengan kode yang mungkin tidak ada dan melakukannya dengan cara yang andal.

"Kode normal" memiliki snippet seperti URLConnection c = nullyang dengan kehadirannya semata-mata menyebabkan pemuat kelas memuat kelas URLConnection sebagai bagian dari memuat kelas ini, melempar pengecualian dan keluar dari ClassNotFound.

Refleksi memungkinkan Anda memuat kelas berdasarkan namanya dalam bentuk string dan mengujinya untuk berbagai properti (berguna untuk beberapa versi di luar kendali Anda) sebelum meluncurkan kelas aktual yang bergantung padanya. Contoh khas adalah kode khusus OS X yang digunakan untuk membuat program Java terlihat asli di bawah OS X, yang tidak ada pada platform lain.


sumber
2

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 Classkelas.

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.Proxykelas, 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.
Sebastian Redl
sumber
1

Refleksi dapat secara otomatis menjaga bagian-bagian program Anda dalam sinkronisasi, di mana sebelumnya, Anda harus memperbarui program secara manual untuk menggunakan antarmuka baru.

DeadMG
sumber
5
Harga yang Anda bayar dalam kasus ini adalah Anda kehilangan pemeriksaan tipe oleh kompiler dan keselamatan refactor di IDE. Ini adalah trade-off yang tidak ingin saya lakukan.
Barend