نقطه ورود تمام عملیات Reflection، کلاس java.lang.Class است. هیچکدام از کلاسهای java.lang.reflect که برای دسترسی Reflective به هر عنصر کلاس نیاز دارید، Constructor های Public ندارند. کلاس java.lang.reflect.ReflectPermission استثناست، اما این کلاس اکنون منسوخ (Deprecated) شده است. برای دسترسی به این کلاسها، لازم است متدهای مناسب را روی Class فراخوانی کنید. روشهای متعددی برای دریافت یک Class وجود دارد که بستگی دارد کد شما به یک شیء، نام کلاس، Type یا یک Class موجود دسترسی دارد یا نه.
هر Type یا Reference است یا Primitive. کلاسها، Enum ها، Record ها، Lambda ها و آرایهها (که همه از java.lang.Object ارث میبرند) و همچنین Interface ها، همه Reference Type هستند. نمونههایی از Reference Type ها شامل java.lang.String، تمام کلاسهای Wrapper برای Type های Primitive مانند java.lang.Double، Interface java.io.Serializable و Enum java.nio.file.StandardOpenOption هستند. Type های Primitive مجموعه ثابتی دارند: boolean، byte، short، int، long، char، float و double.
برای هر Type شیء، JVM یک نمونه تغییرناپذیر (Immutable) از java.lang.Class ایجاد میکند که متدهایی برای بررسی ویژگیهای Runtime شیء شامل اعضا و اطلاعات Type ارائه میدهد. این کلاس همچنین قابلیت ایجاد کلاسها و اشیای جدید را فراهم میکند. مهمتر از همه، نقطه ورود تمام API های Reflection است. این بخش نحوه دریافت نمونه java.lang.Class مورد نیاز را توضیح میدهد.
اگر نمونهای از یک شیء موجود باشد، سادهترین روش دریافت Class آن، فراخوانی Object.getClass() است. البته این فقط برای Reference Type ها کار میکند.
کد زیر متود getClass() را روی یک نمونه از کلاس String فراخوانی میکند:
Class<?> c = "foo".getClass();
IO.println("class: " + c);
class: class java.lang.String
توجه کنید متود toString() کلاس Class عبارت class را جلوی نام کلاس اضافه میکند.
کنسول منحصربهفردی به ماشین مجازی مرتبط است که توسط متود ایستا System.console() بازمیگردد. مقدار بازگردانده توسط getClass()، Class مربوط به java.io.Console است.
Class<?> c = System.console().getClass();
IO.println("class: " + c);
خروجی (java.io.Console یک کلاس Sealed است با یک امتداد مجاز ProxyingConsole. این کلاس Public نیست و توسط JDK در runtime فراهم میشود):
class: class java.io.ProxyingConsole
SATURDAY یک نمونه از Enum Days است؛ بنابراین getClass() Class مربوط به Enum Type Days را برمیگرداند:
enum Days {SATURDAY, SUNDAY}
Class<?> c = Days.SATURDAY.getClass();
IO.println("class: " + c);
خروجی بستگی به نحوه اعلام Enum شما دارد: پکیجی که در آن قرار دارد و اینکه آیا در کلاس مستقل، یا بهصورت Local یا Inner Class اعلام شده است.
class: class org.devjava.Days
چون آرایهها نمونههایی از Object هستند، فراخوانی getClass() روی آنها نیز امکانپذیر است:
byte[] bytes = new byte[1024];
Class<?> c = bytes.getClass();
IO.println("class: " + c);
class: class [B
رشته [B نشاندهنده آرایهای از byte ها است. این قرارداد خاص را بعداً بررسی خواهیم کرد.
در مثال زیر، java.util.Set یک Interface روی شیء java.util.HashSet است. مقدار بازگردانده توسط getClass() کلاس java.util.HashSet است:
import java.util.HashSet;
import java.util.Set;
Set<String> s = new HashSet<String>();
Class<?> c = s.getClass();
IO.println("class: "+c);
class: class java.util.HashSet
توجه کنید Generic Type این Set به دلیل Type Erasure در اینجا وجود ندارد.
Lambda Expression ها نیز کلاسهایی دارند که میتوانید آنها را دریافت کنید:
Supplier<String> supplier = () -> "Hello";
Class<?> c = supplier.getClass();
IO.println("class: " + c);
خروجی (ممکن است روی ماشین شما متفاوت باشد):
class: class org.devjava.Main$$Lambda/0x00000290d4003538
کلاسهای Anonymous توسط کامپایلر برای شما ایجاد میشوند:
Object key = new Object() {};
Class<?> c = key.getClass();
IO.println("class: " + c);
class: class org.devjava.Main$1
اگر Type موجود باشد اما نمونهای وجود نداشته باشد، میتوانید .class را به نام Type اضافه کنید تا Class دریافت شود. این سادهترین روش برای دریافت Class یک Type Primitive نیز هست.
فراخوانی getClass() روی یک Type Primitive خطای کامپایل ایجاد میکند:
boolean b = true;
Class<?> c = b.getClass(); // compile-time error
سینتکس .class Class مربوط به Type را برمیگرداند:
boolean b = true;
Class<?> c = boolean.class; // correct
IO.println("class: " + c);
class: boolean
این سینتکس برای Reference Type ها نیز قابل استفاده است:
Class<?> c = java.io.PrintStream.class;
IO.println("class: " + c);
class: class java.io.PrintStream
سینتکس .class برای دریافت Class آرایههای چندبعدی نیز قابل استفاده است:
Class<?> c = int[][][].class;
IO.println("class: " + c);
class: class [[[I
اگر نام کامل یک کلاس موجود باشد، میتوانید Class مربوطه را با متود ایستا Class.forName() دریافت کنید. این برای Type های Primitive قابل استفاده نیست اما برای هر آرایهای قابل استفاده است.
Class<?> c = Class.forName("java.lang.String");
این عبارت شیء کلاس را از نام کامل دادهشده برمیگرداند. اگر این کلاس وجود نداشته باشد، ClassNotFoundException پرتاب میکند.
دو مثال برای آرایه ساده و دوبعدی:
Class<?> simpleDoubleArray = Class.forName("[D");
Class<?> bidiStringArray = Class.forName("[[Ljava.lang.String;");
متغیر simpleDoubleArray شامل Class مربوط به آرایه Type Primitive double است (یعنی معادل double[].class). متغیر bidiStringArray شامل Class مربوط به آرایه دوبعدی String است (یعنی معادل String[][].class).
متود Class.forPrimitiveName() در Java SE 22 به کلاس Class اضافه شده است. این متود کلاس مربوطه را برای هر Type Primitive از جمله void برمیگرداند.
Class<?> c = Class.forPrimitiveName("int");
IO.println("class: " + c);
class: int
اگر رشتهای غیر از Type Primitive پاس دهید، null برمیگرداند و اگر null پاس دهید، NullPointerException پرتاب میکند.
هر Type Primitive و void یک کلاس Wrapper در java.lang برای Boxing دارد.
بهعنوان مثال، کلاس java.lang.Double Type Primitive double را هر جا که Object لازم باشد بستهبندی (Wrap) میکند.
هر کلاس Wrapper شامل یک فیلد ایستا به نام TYPE است که معادل Class Type Primitive بستهبندیشده است:
Class<?> c = Double.TYPE;
IO.println("class: " + c);
class: double
همچنین با Type Void قابل استفاده است:
Class c = Void.TYPE;
Void.TYPE معادل void.class است.
API های متعددی کلاسها را برمیگردانند، اما فقط در صورتی قابل دسترسی هستند که از قبل یک Class بهصورت مستقیم یا غیرمستقیم دریافت شده باشد.
متود Class.getSuperclass() تنها Super Class یک کلاس مشخص را برمیگرداند. برای کلاس Object مقدار null برمیگرداند.
کد زیر Super Class های کلاس NullPointerException را چاپ میکند:
Class<?> c = NullPointerException.class;
while (c != null) {
IO.println("class: " + c);
c = c.getSuperclass();
}
class: class java.lang.NullPointerException
class: class java.lang.RuntimeException
class: class java.lang.Exception
class: class java.lang.Throwable
class: class java.lang.Object
متود Class.getInterfaces() آرایهای از Interface هایی که مستقیماً توسط یک کلاس پیادهسازی شدهاند برمیگرداند. Interface های پیادهسازیشده توسط Type های بالادست را برنمیگرداند.
Class<?> c = String.class;
Class<?>[] implementedInterfaces = c.getInterfaces();
for (Class<?> implementedInterface : implementedInterfaces) {
IO.println("i: " + implementedInterface);
}
i: interface java.io.Serializable
i: interface java.lang.Comparable
i: interface java.lang.CharSequence
i: interface java.lang.constant.Constable
i: interface java.lang.constant.ConstantDesc
این متود روی آرایهها نیز قابل استفاده است:
int[] ints = {0, 1, 2};
Class<?> superClass = ints.getClass().getSuperclass();
IO.println("Super class: " + superClass);
Class<?>[] interfaces = ints.getClass().getInterfaces();
IO.println("Implemented interfaces");
Arrays.stream(interfaces).forEach(IO::println);
Super class = class java.lang.Object
Implemented interfaces
interface java.lang.Cloneable
interface java.io.Serializable
این نتیجه دو چیز را نشان میدهد:
Object هستند و Type آنها از Object ارث میبرد.Cloneable و Serializable را پیادهسازی میکند.متود Class.getClasses() تمام کلاسها، Interface ها، Record ها و Enum های Public را که عضو این کلاس و تمام Super Class هایش هستند برمیگرداند.
Class<?> c = Character.class;
for (var memberClass: c.getClasses()) {
IO.println("class: " + memberClass);
}
class: class java.lang.Character$UnicodeBlock
class: class java.lang.Character$UnicodeScript
class: class java.lang.Character$Subset
متود Class.getDeclaredClasses() آرایهای از تمام کلاسهای عضو این کلاس را برمیگرداند، چه Public باشند، Protected، Package-Protected یا Private. کلاسهای عضو ارثبریشده بازگردانده نمیشوند.
Class<?> c = Character.class;
for (var memberClass: c.getDeclaredClasses()) {
IO.println("class: " + memberClass);
}
نتیجه متفاوت است. کلاس java.lang.Character$CharacterCache که یک کلاس عضو Private است، اضافه شده. قبلاً بازگردانده نشده بود چون Class.getClasses() فقط اعضای Public را برمیگرداند:
class: class java.lang.Character$CharacterCache
class: class java.lang.Character$UnicodeBlock
class: class java.lang.Character$UnicodeScript
class: class java.lang.Character$Subset
متود Class.getDeclaringClass() کلاسی را که این کلاس در آن اعلام شده برمیگرداند. اگر این کلاس عضو هیچ کلاسی نباشد، null برمیگرداند.
Class<?> c = Character.UnicodeBlock.class;
Class<?> declaringClass = c.getDeclaringClass();
IO.println("class: " + declaringClass);
class: class java.lang.Character
کلاس Anonymous مقدار null برمیگرداند:
var key = new Object() {}; // this is an anonymous class
Class<?> anonymousClass = key.getClass();
Class<?> declaringClass = anonymousClass.getDeclaringClass();
IO.println("class: " + declaringClass);
class: null
متود Class.getEnclosingClass() نزدیکترین کلاس محصور یک کلاس مشخص را برمیگرداند.
کلاس Enclosing میتواند با کلاس Declaring یکسان یا متفاوت باشد. مثال زیر هر دو حالت را نشان میدهد:
public class Main {
class User {}
void main() {
var key = new Object() {}; // this is an anonymous class
Class<?> thisClass = this.getClass();
Class<?> anonymousClass = key.getClass();
Class<?> enclosingClassOfAnonymous = anonymousClass.getEnclosingClass();
Supplier<String> supplier = () -> "Hello";
Class<?> lambdaClass = supplier.getClass();
IO.println("This class: " + thisClass);
IO.println("Enclosing class of Main: " + thisClass.getEnclosingClass());
IO.println("Enclosing class of User: " + User.class.getEnclosingClass());
IO.println("Declaring class of User: " + User.class.getDeclaringClass());
IO.println("Anonymous class: " + anonymousClass);
IO.println("Enclosing class of anonymous: " + anonymousClass.getEnclosingClass());
IO.println("Declaring class of anonymous: " + anonymousClass.getDeclaringClass());
IO.println("Enclosing class of lambda: " + lambdaClass.getEnclosingClass());
IO.println("Declaring class of lambda: " + lambdaClass.getDeclaringClass());
}
}
برای Inner Class User، هر دو متود یکسان عمل میکنند. برای کلاس Anonymous، مقدار Enclosing با Declaring متفاوت است و Enclosing کلاسی است که Anonymous Class در آن اعلام شده:
This class: class Main
Enclosing class of Main: null
Enclosing class of User: class Main
Declaring class of User: class Main
Anonymous class: class Main$1
Enclosing class of anonymous: class Main
Declaring class of anonymous: null
Enclosing class of lambda: null
Declaring class of lambda: null
فیلدها، متدها و Constructor ها در Reflection API توسط کلاسهای مخصوص خود مدلسازی شدهاند: Field، Method و Constructor. همه آنها یک Interface مشترک به نام Member را پیادهسازی میکنند. این Interface متدهای متعددی دارد از جمله Member.getDeclaringClass() که کلاسی را که عضو مربوطه در آن اعلام شده برمیگرداند.
فیلدها، متدها و Constructor ها بهتفصیل در ادامه این فصل پوشش داده میشوند، همچنین سایر متدهای Interface Member.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://dev.java/learn/