CodeGym/Java Blog/Acak/Contoh refleksi
John Squirrels
Level 41
San Francisco

Contoh refleksi

Dipublikasikan di grup Acak
anggota
Mungkin Anda pernah menjumpai konsep "refleksi" dalam kehidupan sehari-hari. Kata ini biasanya mengacu pada proses mempelajari diri sendiri. Dalam pemrograman, ini memiliki arti yang sama — ini adalah mekanisme untuk menganalisis data tentang suatu program, dan bahkan mengubah struktur dan perilaku suatu program, saat program sedang berjalan. Contoh refleksi - 1 Yang penting di sini adalah kita melakukan ini saat runtime, bukan saat kompilasi. Tapi mengapa memeriksa kode saat runtime? Lagi pula, Anda sudah bisa membaca kodenya :/ Ada alasan mengapa gagasan refleksi mungkin tidak langsung jelas: sampai saat ini, Anda selalu tahu kelas mana yang Anda kerjakan. Misalnya, Anda dapat menulis Catkelas:
package learn.codegym;

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 tentang itu, dan Anda dapat melihat bidang dan metode yang dimilikinya. Misalkan Anda tiba-tiba perlu memperkenalkan kelas hewan lain ke dalam program. Anda mungkin bisa membuat struktur warisan kelas dengan Animalkelas induk untuk kenyamanan. Sebelumnya, kami bahkan membuat kelas yang mewakili klinik hewan, di mana kami dapat meneruskan objek Animal(instance dari kelas induk), dan program memperlakukan hewan tersebut dengan tepat berdasarkan apakah itu anjing atau kucing. Meskipun ini bukan tugas yang paling sederhana, program ini dapat mempelajari semua informasi yang diperlukan tentang kelas pada waktu kompilasi. Oleh karena itu, saat Anda meneruskan Catobjek ke metode kelas klinik hewan dimain()metode, program sudah mengetahui bahwa itu adalah kucing, bukan anjing. Sekarang bayangkan kita menghadapi tugas yang berbeda. Tujuan kami adalah menulis penganalisa kode. Kita perlu membuat CodeAnalyzerkelas dengan satu metode: void analyzeObject(Object o). Metode ini harus:
  • tentukan kelas objek yang diteruskan ke sana dan tampilkan nama kelas di konsol;
  • tentukan nama semua bidang dari kelas yang diteruskan, termasuk yang pribadi, dan tampilkan di konsol;
  • tentukan nama semua metode kelas yang diteruskan, termasuk yang pribadi, dan tampilkan di konsol.
Ini akan terlihat seperti ini:
public class CodeAnalyzer {

   public static void analyzeClass(Object o) {

       // Print the name of the class of object o
       // Print the names of all variables of this class
       // Print the names of all methods of this class
   }

}
Sekarang kita dapat melihat dengan jelas bagaimana tugas ini berbeda dari tugas lain yang telah Anda selesaikan sebelumnya. Dengan tujuan kami saat ini, kesulitannya terletak pada kenyataan bahwa baik kami maupun program tidak tahu persis apa yang akan diteruskan keanalyzeClass()metode. Jika Anda menulis program seperti itu, pemrogram lain akan mulai menggunakannya, dan mereka mungkin meneruskan apa pun ke metode ini — kelas Java standar apa pun atau kelas lain apa pun yang mereka tulis. Kelas yang lulus dapat memiliki sejumlah variabel dan metode. Dengan kata lain, kami (dan program kami) tidak tahu kelas apa yang akan kami kerjakan. Tapi tetap saja, kita harus menyelesaikan tugas ini. Dan di sinilah Java Reflection API standar membantu kami. Refleksi API adalah alat bahasa yang ampuh. Dokumentasi resmi Oracle merekomendasikan bahwa mekanisme ini hanya boleh digunakan oleh pemrogram berpengalaman yang tahu apa yang mereka lakukan. Anda akan segera mengerti mengapa kami memberikan peringatan semacam ini sebelumnya :) Berikut adalah daftar hal-hal yang dapat Anda lakukan dengan API Refleksi:
  1. Mengidentifikasi/menentukan kelas dari suatu objek.
  2. Dapatkan informasi tentang pengubah kelas, bidang, metode, konstanta, konstruktor, dan kelas super.
  3. Cari tahu metode mana yang termasuk dalam antarmuka yang diimplementasikan.
  4. Buat instance kelas yang nama kelasnya tidak diketahui hingga program dijalankan.
  5. Dapatkan dan tetapkan nilai bidang instance berdasarkan nama.
  6. Panggil metode instance dengan nama.
Daftar yang mengesankan, ya? :) Catatan:mekanisme refleksi dapat melakukan semua hal ini "dengan cepat", terlepas dari jenis objek yang kami berikan ke penganalisa kode kami! Mari jelajahi kemampuan Reflection API dengan melihat beberapa contoh.

Cara mengidentifikasi/menentukan kelas suatu objek

Mari kita mulai dengan dasar-dasarnya. Titik masuk ke mesin refleksi Java adalah kelasnya Class. Ya, ini terlihat sangat lucu, tapi itulah refleksi :) Dengan menggunakan Classkelas, pertama-tama kita menentukan kelas dari objek apa pun yang diteruskan ke metode kita. Mari kita coba lakukan ini:
import learn.codegym.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("Fluffy", 6));
   }
}
Keluaran konsol:
class learn.codegym.Cat
Perhatikan dua hal. Pertama, kami sengaja menempatkan Catkelas dalam paket terpisah learn.codegym. Sekarang Anda dapat melihat bahwa getClass()metode mengembalikan nama lengkap kelas. Kedua, kami menamai variabel kami clazz. Itu terlihat sedikit aneh. Masuk akal untuk menyebutnya "kelas", tetapi "kelas" adalah kata khusus di Jawa. Kompiler tidak akan mengizinkan variabel disebut demikian. Kami harus menyiasatinya entah bagaimana :) Tidak buruk untuk memulai! Apa lagi yang kami miliki dalam daftar kemampuan itu?

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

Sekarang segalanya menjadi lebih menarik! Di kelas saat ini, kami tidak memiliki konstanta atau kelas induk. Mari tambahkan mereka untuk membuat gambar yang lengkap. Buat Animalkelas induk paling sederhana:
package learn.codegym;
public class Animal {

   private String name;
   private int age;
}
Dan kita akan membuat Catkelas kita mewarisi Animaldan menambahkan satu konstanta:
package learn.codegym;

public class Cat extends Animal {

   private static final String ANIMAL_FAMILY = "Feline family";

   private String name;
   private int age;

   // ...the rest of the class
}
Sekarang kita memiliki gambaran lengkapnya! Mari kita lihat refleksi apa yang mampu dilakukan :)
import learn.codegym.Cat;

import java.util.Arrays;

public class CodeAnalyzer {

   public static void analyzeClass(Object o) {
       Class clazz = o.getClass();
       System.out.println("Class name: " + clazz);
       System.out.println("Class fields: " + Arrays.toString(clazz.getDeclaredFields()));
       System.out.println("Parent class: " + clazz.getSuperclass());
       System.out.println("Class methods: " + Arrays.toString(clazz.getDeclaredMethods()));
       System.out.println("Class constructors: " + Arrays.toString(clazz.getConstructors()));
   }

   public static void main(String[] args) {

       analyzeClass(new Cat("Fluffy", 6));
   }
}
Inilah yang kami lihat di konsol:
Class name:  class learn.codegym.Cat
Class fields: [private static final java.lang.String learn.codegym.Cat.ANIMAL_FAMILY, private java.lang.String learn.codegym.Cat.name, private int learn.codegym.Cat.age]
Parent class: class learn.codegym.Animal
Class methods: [public java.lang.String learn.codegym.Cat.getName(), public void learn.codegym.Cat.setName(java.lang.String), public void learn.codegym.Cat.sayMeow(), public void learn.codegym.Cat.setAge(int), public void learn.codegym.Cat.jump(), public int learn.codegym.Cat.getAge()]
Class constructors: [public learn.codegym.Cat(java.lang.String, int)]
Lihat semua informasi kelas mendetail yang bisa kami dapatkan! Dan bukan hanya informasi publik, tetapi juga informasi pribadi! Catatan: privatevariabel juga ditampilkan dalam daftar. "Analisis" kelas kami pada dasarnya dapat dianggap lengkap: kami menggunakan analyzeObject()metode untuk mempelajari semua yang kami bisa. Tapi ini bukan segalanya yang bisa kita lakukan dengan refleksi. Kami tidak terbatas pada pengamatan sederhana — kami akan melanjutkan untuk mengambil tindakan! :)

Cara membuat instance kelas yang nama kelasnya tidak diketahui hingga program dijalankan.

Mari kita mulai dengan konstruktor default. Kelas kita Catbelum memilikinya, jadi mari kita tambahkan:
public Cat() {

}
Berikut kode untuk membuat Catobjek menggunakan createCat()metode refleksi ():
import learn.codegym.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());
   }
}
Masukan konsol:
learn.codegym.Cat
Keluaran konsol:
Cat{name='null', age=0}
Ini bukan kesalahan: nilai namedan ageditampilkan di konsol karena kami menulis kode untuk menampilkannya dalam toString()metode kelas Cat. Di sini kita membaca nama kelas yang objeknya akan kita buat dari konsol. Program mengenali nama kelas yang objeknya akan dibuat. Contoh refleksi - 3Demi singkatnya, kami menghilangkan kode penanganan pengecualian yang tepat, yang akan memakan lebih banyak ruang daripada contoh itu sendiri. Dalam program nyata, tentu saja, Anda harus menangani situasi yang melibatkan nama yang salah dimasukkan, dll. Konstruktor default cukup sederhana, jadi seperti yang Anda lihat, mudah digunakan untuk membuat instance kelas :) Menggunakan newInstance()metode , kami membuat objek baru dari kelas ini. Ini masalah lain jikaCatkonstruktor mengambil argumen sebagai masukan. Mari hapus konstruktor default kelas dan coba jalankan kode kita lagi.
null
java.lang.InstantiationException: learn.codegym.Cat
at java.lang.Class.newInstance(Class.java:427)
Ada yang salah! Kami mendapat kesalahan karena kami memanggil metode untuk membuat objek menggunakan konstruktor default. Tapi kami tidak memiliki konstruktor seperti itu sekarang. Jadi saat newInstance()metode dijalankan, mekanisme refleksi 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! Menggunakan refleksi untuk meneruskan argumen ke konstruktor memerlukan sedikit "kreativitas":
import learn.codegym.Cat;

import java.lang.reflect.InvocationTargetException;

public class Main {

   public static Cat createCat()  {

       Class clazz = null;
       Cat cat = null;

       try {
           clazz = Class.forName("learn.codegym.Cat");
           Class[] catClassParams = {String.class, int.class};
           cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Fluffy", 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='Fluffy', age=6}
Mari kita lihat lebih dekat apa yang terjadi dalam program kita. Kami membuat array Classobjek.
Class[] catClassParams = {String.class, int.class};
Mereka sesuai dengan parameter konstruktor kami (yang baru saja memiliki Stringdan intparameter). Kami meneruskannya ke clazz.getConstructor()metode dan mendapatkan akses ke konstruktor yang diinginkan. Setelah itu, yang perlu kita lakukan hanyalah memanggil newInstance()metode dengan argumen yang diperlukan, dan jangan lupa untuk secara eksplisit mentransmisikan objek ke tipe yang diinginkan: Cat.
cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Fluffy", 6);
Sekarang objek kita berhasil dibuat! Keluaran konsol:
Cat{name='Fluffy', age=6}
Langsung bergerak :)

Cara mendapatkan dan menetapkan nilai bidang instance berdasarkan nama.

Bayangkan Anda menggunakan kelas yang ditulis oleh programmer lain. Selain itu, Anda tidak memiliki kemampuan untuk mengeditnya. Misalnya, pustaka kelas siap pakai yang dikemas dalam JAR. Anda dapat membaca kode kelas, tetapi Anda tidak dapat mengubahnya. Misalkan programmer yang membuat salah satu kelas di perpustakaan ini (biarlah itu menjadi Catkelas lama kita), gagal tidur cukup pada malam sebelum desain selesai, menghapus pengambil dan penyetel untuk lapangan age. Sekarang kelas ini telah hadir untuk Anda. Ini memenuhi semua kebutuhan Anda, karena Anda hanya memerlukan Catobjek dalam program Anda. Tetapi Anda membutuhkan mereka untuk memiliki agebidang! Ini adalah masalah: kita tidak dapat mencapai lapangan, karena memilikiprivatepengubah, dan pengambil dan penyetel telah dihapus oleh pengembang yang kurang tidur yang membuat kelas :/ Nah, refleksi dapat membantu kita dalam situasi ini! Kami memiliki akses ke kode untuk Catkelas, jadi setidaknya kami dapat mengetahui bidang apa yang dimilikinya dan apa namanya. Berbekal informasi ini, kita dapat memecahkan masalah kita:
import learn.codegym.Cat;

import java.lang.reflect.Field;

public class Main {

   public static Cat createCat()  {

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

           // We got lucky with the name field, since it has a setter
           cat.setName("Fluffy");

           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, semuanya dengan namebidang itu mudah, karena pengembang kelas menyediakan penyetel. Anda sudah tahu cara membuat objek dari konstruktor default: kami memilikinya newInstance()untuk ini. Tapi kita harus mengutak-atik bidang kedua. Mari kita cari tahu apa yang terjadi di sini :)
Field age = clazz.getDeclaredField("age");
Di sini, menggunakan Class clazzobjek kami, kami mengakses agebidang melalui getDeclaredField()metode. Ini memungkinkan kita mendapatkan bidang usia sebagai Field ageobjek. Tapi ini tidak cukup, karena kita tidak bisa begitu saja memberikan nilai ke privatefield. Untuk melakukan ini, kita perlu membuat bidang dapat diakses dengan menggunakan setAccessible()metode:
age.setAccessible(true);
Setelah kami melakukan ini ke bidang, kami dapat menetapkan nilai:
age.set(cat, 6);
Seperti yang Anda lihat, Field ageobjek kita memiliki semacam setter dalam-luar yang kita berikan nilai int dan objek yang bidangnya akan ditetapkan. Kami menjalankan main()metode kami dan melihat:
Cat{name='Fluffy', age=6}
Bagus sekali! Kita berhasil! :) Mari kita lihat apa lagi yang bisa kita lakukan...

Cara memanggil metode instance dengan nama.

Mari kita sedikit mengubah situasi pada contoh sebelumnya. Katakanlah Catpengembang kelas tidak membuat kesalahan dengan getter dan setter. Semuanya baik-baik saja dalam hal itu. Sekarang masalahnya berbeda: ada metode yang pasti kita butuhkan, tetapi pengembang membuatnya menjadi pribadi:
private void sayMeow() {

   System.out.println("Meow!");
}
Ini berarti bahwa jika kita membuat Catobjek dalam program kita, maka kita tidak akan dapat memanggil sayMeow()metode pada objek tersebut. Kita akan punya kucing yang tidak mengeong? Itu aneh :/ Bagaimana kita memperbaikinya? Sekali lagi, API Refleksi membantu kami! Kami tahu nama metode yang kami butuhkan. Segala sesuatu yang lain adalah teknis:
import learn.codegym.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("Fluffy", 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 kami melakukan banyak hal yang sama seperti yang kami lakukan saat mengakses bidang pribadi. Pertama, kita mendapatkan metode yang kita butuhkan. Itu dikemas dalam Methodobjek:
Method sayMeow = clazz.getDeclaredMethod("sayMeow");
Metode ini getDeclaredMethod()memungkinkan kita mendapatkan metode pribadi. Selanjutnya, kami membuat metode dapat dipanggil:
sayMeow.setAccessible(true);
Dan terakhir, kami memanggil metode pada objek yang diinginkan:
sayMeow.invoke(cat);
Di sini, pemanggilan metode kita terlihat seperti "panggilan balik": kita terbiasa menggunakan titik untuk menunjuk objek ke metode yang diinginkan ( cat.sayMeow()), tetapi saat bekerja dengan refleksi, kita meneruskan ke metode objek yang ingin kita panggil metode itu. Apa yang ada di konsol kami?
Meow!
Semuanya berhasil! :) Sekarang Anda dapat melihat kemungkinan besar yang diberikan mekanisme refleksi Java kepada kita. Dalam situasi yang sulit dan tidak terduga (seperti contoh kami dengan kelas dari perpustakaan tertutup), ini dapat sangat membantu kami. Tapi, seperti kekuatan besar lainnya, itu membawa tanggung jawab besar. Kerugian refleksi dijelaskan di bagian khusus di situs web Oracle. Ada tiga kelemahan utama:
  1. Performa lebih buruk. Metode yang disebut menggunakan refleksi memiliki kinerja yang lebih buruk daripada metode yang dipanggil dengan cara biasa.

  2. Ada batasan keamanan. Mekanisme refleksi memungkinkan kita mengubah perilaku program saat runtime. Tetapi di tempat kerja Anda, saat mengerjakan proyek nyata, Anda mungkin menghadapi batasan yang tidak memungkinkan hal ini.

  3. Risiko pemaparan informasi internal. Penting untuk dipahami bahwa refleksi adalah pelanggaran langsung terhadap prinsip enkapsulasi: ini memungkinkan kita mengakses bidang pribadi, metode, dll. Saya rasa saya tidak perlu menyebutkan bahwa pelanggaran langsung dan mencolok terhadap prinsip OOP harus dilakukan hanya dalam kasus yang paling ekstrim, ketika tidak ada cara lain untuk memecahkan masalah karena alasan di luar kendali Anda.

Gunakan refleksi dengan bijak dan hanya dalam situasi yang tidak dapat dihindari, dan jangan lupakan kekurangannya. Dengan ini, pelajaran kita telah berakhir. Ternyata cukup lama, tetapi Anda belajar banyak hari ini :)
Komentar
  • Populer
  • Baru
  • Lama
Anda harus login untuk memberikan komentar
Halaman ini belum memiliki komentar