JavaRush /Java Blog /Random-ID /Refleksi di Java - Contoh Penggunaan

Refleksi di Java - Contoh Penggunaan

Dipublikasikan di grup Random-ID
Anda mungkin pernah menjumpai konsep “refleksi” dalam kehidupan sehari-hari. Biasanya kata ini merujuk pada proses mempelajari diri sendiri. Dalam pemrograman, ini memiliki arti yang sama - ini adalah mekanisme untuk memeriksa data tentang suatu program, serta mengubah struktur dan perilaku program selama pelaksanaannya. Yang penting di sini adalah hal ini dilakukan pada saat runtime, bukan pada waktu kompilasi. Tapi mengapa memeriksa kode saat runtime? Anda sudah melihatnya :/ Contoh penggunaan Refleksi - 1Ide refleksi mungkin tidak langsung jelas karena satu alasan: sampai saat ini, Anda selalu mengetahui kelas yang Anda ikuti. Misalnya, Anda dapat menulis kelas Cat:
package learn.javarush;

public class Cat {

   private String name;
   private int age;

   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   public void sayMeow() {

       System.out.println("Meow!");
   }

   public void jump() {

       System.out.println("Jump!");
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public int getAge() {
       return age;
   }

   public void setAge(int age) {
       this.age = age;
   }

@Override
public String toString() {
   return "Cat{" +
           "name='" + name + '\'' +
           ", age=" + age +
           '}';
}

}
Anda tahu segalanya tentangnya, Anda melihat bidang dan metode apa yang dimilikinya. Tentunya Anda dapat membuat sistem pewarisan dengan kelas umum untuk kenyamanan Animal, jika tiba-tiba program tersebut membutuhkan kelas hewan lain. Sebelumnya, kami bahkan membuat kelas klinik hewan di mana Anda dapat meneruskan objek induk Animal, dan program akan merawat hewan tersebut tergantung apakah itu anjing atau kucing. Meskipun tugas-tugas ini tidak terlalu sederhana, program ini mempelajari semua informasi yang diperlukan tentang kelas-kelas pada waktu kompilasi. Oleh karena itu, ketika Anda main()meneruskan objek dalam suatu metode Catke metode kelas klinik hewan, program sudah mengetahui bahwa ini adalah kucing, bukan anjing. Sekarang mari kita bayangkan kita dihadapkan pada tugas lain. Tujuan kami adalah menulis penganalisis kode. Kita perlu membuat kelas CodeAnalyzerdengan satu metode - void analyzeClass(Object o). Metode ini harus:
  • menentukan kelas mana yang diteruskan ke objek tersebut dan menampilkan nama kelas di konsol;
  • menentukan nama semua bidang kelas ini, termasuk bidang pribadi, dan menampilkannya di konsol;
  • tentukan nama semua metode kelas ini, termasuk metode privat, dan tampilkan di konsol.
Ini akan terlihat seperti ini:
public class CodeAnalyzer {

   public static void analyzeClass(Object o) {

       //Вывести название класса, к которому принадлежит an object o
       //Вывести названия всех переменных этого класса
       //Вывести названия всех методов этого класса
   }

}
Sekarang perbedaan antara masalah ini dan masalah lain yang telah Anda selesaikan sebelumnya terlihat. Dalam hal ini, kesulitannya terletak pada kenyataan bahwa baik Anda maupun program tidak mengetahui apa sebenarnya yang akan diteruskan ke metode tersebut analyzeClass(). Anda menulis sebuah program, pemrogram lain akan mulai menggunakannya, yang dapat memasukkan apa saja ke dalam metode ini - kelas Java standar apa pun atau kelas apa pun yang mereka tulis. Kelas ini dapat memiliki sejumlah variabel dan metode. Dengan kata lain, dalam hal ini kami (dan program kami) tidak tahu kelas apa yang akan kami kerjakan. Namun, kita harus menyelesaikan masalah ini. Dan di sini perpustakaan Java standar membantu kami - Java Reflection API. Reflection API adalah fitur bahasa yang canggih. Dokumentasi resmi Oracle menyatakan bahwa mekanisme ini direkomendasikan untuk digunakan hanya oleh programmer berpengalaman yang memahami betul apa yang mereka lakukan. Anda akan segera memahami mengapa kami tiba-tiba diberikan peringatan seperti itu sebelumnya :) Berikut adalah daftar apa yang dapat dilakukan dengan menggunakan Reflection API:
  1. Mencari tahu/menentukan kelas suatu benda.
  2. Dapatkan informasi tentang pengubah kelas, bidang, metode, konstanta, konstruktor, dan superkelas.
  3. Cari tahu metode mana yang termasuk dalam antarmuka/antarmuka yang diimplementasikan.
  4. Buat sebuah instance kelas ketika nama kelas tidak diketahui sampai program dijalankan.
  5. Dapatkan dan tetapkan nilai bidang objek berdasarkan nama.
  6. Panggil metode objek berdasarkan nama.
Daftar yang mengesankan, ya? :) Perhatian:Mekanisme refleksi mampu melakukan semua ini “dengan cepat” terlepas dari objek kelas mana yang kita teruskan ke penganalisis kode kita! Mari kita lihat kemampuan Reflection API beserta contohnya.

Cara mengetahui/menentukan kelas suatu benda

Mari kita mulai dengan dasar-dasarnya. Titik masuk ke mekanisme refleksi Java adalah Class. Ya, ini terlihat sangat lucu, tapi itulah gunanya refleksi :) Dengan menggunakan class Class, pertama-tama kita menentukan kelas objek apa pun yang diteruskan ke metode kita. Mari kita coba ini:
import learn.javarush.Cat;

public class CodeAnalyzer {

   public static void analyzeClass(Object o) {
       Class clazz = o.getClass();
       System.out.println(clazz);
   }

   public static void main(String[] args) {

       analyzeClass(new Cat("Barsik", 6));
   }
}
Keluaran konsol:

class learn.javarush.Cat
Perhatikan dua hal. Pertama, kami sengaja menempatkan kelas tersebut Catdalam paket terpisah, learn.javarush;dan sekarang Anda dapat melihat bahwa kelas tersebut getClass()mengembalikan nama lengkap kelas tersebut. Kedua, kami memberi nama variabel kami clazz. Terlihat agak aneh. Tentu saja, ini harus disebut "kelas", tetapi "kelas" adalah kata khusus dalam bahasa Java, dan kompiler tidak akan mengizinkan variabel dipanggil seperti itu. Saya harus keluar dari situ :) Yah, bukan awal yang buruk! Apa lagi yang ada dalam daftar kemungkinannya?

Cara mendapatkan informasi tentang pengubah kelas, bidang, metode, konstanta, konstruktor, dan superkelas

Ini sudah lebih menarik! Di kelas saat ini kita tidak memiliki konstanta dan tidak ada kelas induk. Mari tambahkan untuk kelengkapan. Mari kita buat kelas induk paling sederhana Animal:
package learn.javarush;
public class Animal {

   private String name;
   private int age;
}
Dan mari tambahkan Catwarisan dari Animaldan satu konstanta ke kelas kita:
package learn.javarush;

public class Cat extends Animal {

   private static final String ANIMAL_FAMILY = "Семейство кошачьих";

   private String name;
   private int age;

   //...остальная часть класса
}
Sekarang kami memiliki satu set lengkap! Mari kita coba kemungkinan refleksi :)
import learn.javarush.Cat;

import java.util.Arrays;

public class CodeAnalyzer {

   public static void analyzeClass(Object o) {
       Class clazz = o.getClass();
       System.out.println("Name класса: " + clazz);
       System.out.println("Поля класса: " + Arrays.toString(clazz.getDeclaredFields()));
       System.out.println("Родительский класс: " + clazz.getSuperclass());
       System.out.println("Методы класса: " +  Arrays.toString(clazz.getDeclaredMethods()));
       System.out.println("Конструкторы класса: " + Arrays.toString(clazz.getConstructors()));
   }

   public static void main(String[] args) {

       analyzeClass(new Cat("Barsik", 6));
   }
}
Inilah yang kami dapatkan di konsol:
Name класса: class learn.javarush.Cat
Поля класса: [private static final java.lang.String learn.javarush.Cat.ANIMAL_FAMILY, private java.lang.String learn.javarush.Cat.name, private int learn.javarush.Cat.age]
Родительский класс: class learn.javarush.Animal
Методы класса: [public java.lang.String learn.javarush.Cat.getName(), public void learn.javarush.Cat.setName(java.lang.String), public void learn.javarush.Cat.sayMeow(), public void learn.javarush.Cat.setAge(int), public void learn.javarush.Cat.jump(), public int learn.javarush.Cat.getAge()]
Конструкторы класса: [public learn.javarush.Cat(java.lang.String,int)]
Kami menerima begitu banyak informasi mendetail tentang kelas tersebut! Dan tidak hanya tentang bagian publik, tetapi juga tentang bagian pribadi. Perhatian: private-variabel juga ditampilkan dalam daftar. Sebenarnya, “analisis” kelas dapat dianggap selesai pada saat ini: sekarang, dengan menggunakan metode ini, analyzeClass()kita akan mempelajari segala sesuatu yang mungkin. Namun bukan hanya itu saja kemungkinan yang kita miliki saat bekerja dengan refleksi. Jangan membatasi diri pada pengamatan sederhana dan beralih ke tindakan aktif! :)

Cara membuat instance kelas jika nama kelas tidak diketahui sebelum program dijalankan

Mari kita mulai dengan konstruktor default. Itu belum ada di kelas kita Cat, jadi mari kita tambahkan:
public Cat() {

}
Berikut tampilan kode untuk membuat objek Catmenggunakan refleksi (metode createCat()):
import learn.javarush.Cat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {

   public static Cat createCat() throws IOException, IllegalAccessException, InstantiationException, ClassNotFoundException {

       BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
       String className = reader.readLine();

       Class clazz = Class.forName(className);
       Cat cat = (Cat) clazz.newInstance();

       return cat;
   }

public static Object createObject() throws Exception {

   BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
   String className = reader.readLine();

   Class clazz = Class.forName(className);
   Object result = clazz.newInstance();

   return result;
}

   public static void main(String[] args) throws IOException, IllegalAccessException, ClassNotFoundException, InstantiationException {
       System.out.println(createCat());
   }
}
Masuk ke konsol:

learn.javarush.Cat
Keluaran konsol:

Cat{name='null', age=0}
Ini bukan kesalahan: nilai namedan ageditampilkan di konsol karena kami memprogram keluarannya dalam metode toString()kelas Cat. Di sini kita membaca nama kelas yang objeknya akan kita buat dari konsol. Program yang sedang berjalan mempelajari nama kelas yang objeknya akan dibuat. Contoh penggunaan Refleksi - 3Demi singkatnya, kami telah menghilangkan kode untuk penanganan pengecualian yang tepat sehingga tidak memakan lebih banyak ruang daripada contoh itu sendiri. Dalam program nyata, tentu saja, sangat bermanfaat untuk menangani situasi di mana nama yang dimasukkan salah, dll. Konstruktor default adalah hal yang cukup sederhana, jadi membuat instance kelas dengan menggunakannya, seperti yang Anda lihat, tidaklah sulit :) Dan dengan menggunakan metode ini, newInstance()kami membuat objek baru dari kelas ini. Lain halnya jika konstruktor kelas Catmengambil parameter sebagai masukan. Mari kita hapus konstruktor default dari kelas dan coba jalankan kode kita lagi.

null
java.lang.InstantiationException: learn.javarush.Cat
  at java.lang.Class.newInstance(Class.java:427)
Ada yang salah! Kami menerima kesalahan karena kami memanggil metode untuk membuat objek melalui konstruktor default. Namun sekarang kami tidak memiliki desainer seperti itu. Artinya ketika metode ini berfungsi, newInstance()mekanisme refleksi akan menggunakan konstruktor lama kita dengan dua parameter:
public Cat(String name, int age) {
   this.name = name;
   this.age = age;
}
Tapi kami tidak melakukan apa pun dengan parameternya, seolah-olah kami benar-benar melupakannya! Untuk meneruskannya ke konstruktor menggunakan refleksi, Anda harus mengubahnya sedikit:
import learn.javarush.Cat;

import java.lang.reflect.InvocationTargetException;

public class Main {

   public static Cat createCat()  {

       Class clazz = null;
       Cat cat = null;

       try {
           clazz = Class.forName("learn.javarush.Cat");
           Class[] catClassParams = {String.class, int.class};
           cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Barsik", 6);
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (InstantiationException e) {
           e.printStackTrace();
       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (NoSuchMethodException e) {
           e.printStackTrace();
       } catch (InvocationTargetException e) {
           e.printStackTrace();
       }

       return cat;
   }

   public static void main(String[] args) {
       System.out.println(createCat());
   }
}
Keluaran konsol:

Cat{name='Barsik', age=6}
Mari kita lihat lebih dekat apa yang terjadi dalam program kami. Kami telah membuat array objek Class.
Class[] catClassParams = {String.class, int.class};
Mereka sesuai dengan parameter konstruktor kami (kami hanya memiliki parameter Stringdan int). Kami meneruskannya ke metode clazz.getConstructor()dan mendapatkan akses ke konstruktor yang diperlukan. Setelah itu, yang tersisa hanyalah memanggil metode newInstance()dengan parameter yang diperlukan dan jangan lupa untuk secara eksplisit memasukkan objek ke kelas yang kita butuhkan - Cat.
cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Barsik", 6);
Hasilnya, objek kita akan berhasil dibuat! Keluaran konsol:

Cat{name='Barsik', age=6}
Mari kita lanjutkan :)

Cara mendapatkan dan menetapkan nilai bidang objek berdasarkan nama

Bayangkan Anda menggunakan kelas yang ditulis oleh programmer lain. Namun, Anda tidak mempunyai kesempatan untuk mengeditnya. Misalnya perpustakaan kelas siap pakai yang dikemas dalam JAR. Anda dapat membaca kode kelas, tetapi Anda tidak dapat mengubahnya. Pemrogram yang membuat kelas di perpustakaan ini (biarkan kelas lama kita Cat) tidak cukup tidur sebelum desain akhir dan menghapus getter dan setter untuk bidang tersebut age. Sekarang kelas ini telah hadir kepada Anda. Ini sepenuhnya memenuhi kebutuhan Anda, karena Anda hanya memerlukan objek dalam program Cat. Tapi Anda membutuhkannya dengan bidang yang sama age! Ini adalah masalah: kita tidak dapat mencapai lapangan, karena memiliki pengubah private, dan getter dan setter telah dihapus oleh calon pengembang kelas ini :/ Nah, refleksi dapat membantu kita dalam situasi ini juga! CatKami memiliki akses ke kode kelas : setidaknya kami dapat mengetahui bidang apa yang dimilikinya dan apa namanya. Berbekal informasi ini, kami memecahkan masalah kami:
import learn.javarush.Cat;

import java.lang.reflect.Field;

public class Main {

   public static Cat createCat()  {

       Class clazz = null;
       Cat cat = null;
       try {
           clazz = Class.forName("learn.javarush.Cat");
           cat = (Cat) clazz.newInstance();

           //с полем name нам повезло - для него в классе есть setter
           cat.setName("Barsik");

           Field age = clazz.getDeclaredField("age");

           age.setAccessible(true);

           age.set(cat, 6);

       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (InstantiationException e) {
           e.printStackTrace();
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (NoSuchFieldException e) {
           e.printStackTrace();
       }

       return cat;
   }

   public static void main(String[] args) {
       System.out.println(createCat());
   }
}
Seperti yang dinyatakan dalam komentar, namesemuanya sederhana dengan bidang ini: pengembang kelas menyediakan penyetel untuk itu. Anda juga sudah mengetahui cara membuat objek dari konstruktor default: ada metode untuk ini newInstance(). Tapi Anda harus bermain-main dengan bidang kedua. Mari kita cari tahu apa yang terjadi di sini :)
Field age = clazz.getDeclaredField("age");
Di sini kita, menggunakan objek kita Class clazz, mengakses bidang agemenggunakan getDeclaredField(). Ini memberi kita kemampuan untuk mendapatkan bidang usia sebagai objek Field age. Namun ini belum cukup, karena privatebidang tidak bisa diberi nilai begitu saja. Untuk melakukan ini, Anda perlu membuat bidang tersebut “tersedia” menggunakan metode setAccessible():
age.setAccessible(true);
Bidang-bidang yang melakukan hal ini dapat diberi nilai:
age.set(cat, 6);
Seperti yang Anda lihat, kami memiliki semacam penyetel yang terbalik: kami menetapkan Field agenilainya pada bidang, dan juga meneruskannya ke objek yang bidang ini harus ditetapkan. Mari jalankan metode kita main()dan lihat:

Cat{name='Barsik', age=6}
Hebat, kami melakukan semuanya! :) Mari kita lihat kemungkinan lain apa yang kita miliki...

Cara memanggil metode objek berdasarkan nama

Mari kita ubah sedikit situasinya dari contoh sebelumnya. Katakanlah pengembang kelas Catmembuat kesalahan dengan bidang - keduanya tersedia, ada pengambil dan penyetel untuk keduanya, semuanya baik-baik saja. Masalahnya berbeda: dia menjadikan metode private yang pasti kita perlukan:
private void sayMeow() {

   System.out.println("Meow!");
}
Akibatnya, kita akan membuat objek Catdalam program kita, namun tidak akan dapat memanggil metodenya sayMeow(). Apakah kita akan mempunyai kucing yang tidak mengeong? Cukup aneh :/ Bagaimana cara memperbaikinya? Sekali lagi, Reflection API hadir untuk menyelamatkan! Kami tahu nama metode yang diperlukan. Selebihnya adalah soal teknik:
import learn.javarush.Cat;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {

   public static void invokeSayMeowMethod()  {

       Class clazz = null;
       Cat cat = null;
       try {

           cat = new Cat("Barsik", 6);

           clazz = Class.forName(Cat.class.getName());

           Method sayMeow = clazz.getDeclaredMethod("sayMeow");

           sayMeow.setAccessible(true);

           sayMeow.invoke(cat);

       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (NoSuchMethodException e) {
           e.printStackTrace();
       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (InvocationTargetException e) {
           e.printStackTrace();
       }
   }

   public static void main(String[] args) {
       invokeSayMeowMethod();
   }
}
Di sini kita bertindak dengan cara yang hampir sama seperti dalam situasi dengan akses ke lahan pribadi. Pertama kita mendapatkan metode yang kita perlukan, yang dienkapsulasi dalam objek kelas Method:
Method sayMeow = clazz.getDeclaredMethod("sayMeow");
Dengan bantuan, getDeclaredMethod()Anda dapat “menjangkau” metode pribadi. Selanjutnya kita membuat metode ini bisa dipanggil:
sayMeow.setAccessible(true);
Dan terakhir, kita memanggil metode pada objek yang diinginkan:
sayMeow.invoke(cat);
Memanggil suatu metode juga terlihat seperti "panggilan terbalik": kita terbiasa mengarahkan objek ke metode yang diperlukan menggunakan titik ( cat.sayMeow()), dan ketika bekerja dengan refleksi, kita meneruskan ke metode objek yang ingin dipanggil. . Apa yang kita miliki di konsol?

Meow!
Semuanya berhasil! :) Sekarang Anda melihat kemungkinan luas yang diberikan mekanisme refleksi di Java kepada kita. Dalam situasi yang sulit dan tidak terduga (seperti pada contoh kelas dari perpustakaan tertutup), ini dapat sangat membantu kita. Namun, seperti kekuatan besar lainnya, hal ini juga menyiratkan tanggung jawab yang besar. Kerugian dari refleksi ditulis di bagian khusus di situs web Oracle. Ada tiga kelemahan utama:
  1. Produktivitas menurun. Metode yang dipanggil menggunakan refleksi mempunyai kinerja yang lebih rendah dibandingkan metode yang dipanggil secara normal.

  2. Ada batasan keamanan. Mekanisme refleksi memungkinkan Anda mengubah perilaku program selama runtime. Namun di lingkungan kerja Anda pada proyek nyata mungkin ada batasan yang tidak memungkinkan Anda melakukan hal ini.

  3. Risiko pengungkapan informasi orang dalam. Penting untuk dipahami bahwa penggunaan refleksi secara langsung melanggar prinsip enkapsulasi: ini memungkinkan kita mengakses bidang, metode, dll. Saya rasa tidak perlu dijelaskan bahwa pelanggaran langsung dan berat terhadap prinsip-prinsip OOP harus dilakukan hanya dalam kasus yang paling ekstrim, ketika tidak ada cara lain untuk menyelesaikan masalah karena alasan di luar kendali Anda.

Gunakan mekanisme refleksi dengan bijak dan hanya dalam situasi yang tidak dapat dihindari, dan jangan lupakan kekurangannya. Ini mengakhiri kuliah kita! Ternyata lumayan besar, tapi hari ini kamu belajar banyak hal baru :)
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION