شیء Field به شما امکان دریافت اطلاعات بیشتر درباره فیلد مربوطه را میدهد: Type و Modifier های آن و همچنین خواندن و تنظیم مقدار فیلد برای یک شیء خاص. این بخش همچنین نحوه کشف فیلدها در یک کلاس را پوشش میدهد.
کلاس Class دو دسته متود برای دسترسی به فیلدها ارائه میدهد:
Object. در حالت اول تمام فیلدها (Public، Protected، Default و Private) دریافت میشوند. در حالت دوم فقط فیلدهای Public دریافت میشوند.| Class API | آرایه فیلدها؟ | فیلدهای ارثبریشده؟ | فیلدهای Private؟ |
|---|---|---|---|
getDeclaredField(String) | خیر | خیر | بله |
getField(String) | خیر | بله | خیر |
getDeclaredFields() | بله | خیر | بله |
getFields() | بله | بله | خیر |
بیایید این متدها را روی کلاس ArrayList از JDK ببینیم.
ابتدا تمام فیلدهای declared شده این کلاس را چاپ کنید:
Class<?> arrayListClass = Class.forName("java.util.ArrayList");
Field[] fields = arrayListClass.getDeclaredFields();
for (Field field : fields) {
IO.println("Field: " + field.getName() +
" declared in " + field.getDeclaringClass().getSimpleName() +
" " + field.accessFlags());
}
Field: serialVersionUID declared in ArrayList [PRIVATE, STATIC, FINAL]
Field: DEFAULT_CAPACITY declared in ArrayList [PRIVATE, STATIC, FINAL]
Field: EMPTY_ELEMENTDATA declared in ArrayList [PRIVATE, STATIC, FINAL]
Field: DEFAULTCAPACITY_EMPTY_ELEMENTDATA declared in ArrayList [PRIVATE, STATIC, FINAL]
Field: elementData declared in ArrayList [TRANSIENT]
Field: size declared in ArrayList [PRIVATE]
فقط فیلدهای خود کلاس ArrayList یافت شدهاند، صرفنظر از Modifier هایشان.
با Field.getFields() امتحان کنید:
Class<?> arrayListClass = Class.forName("java.util.ArrayList");
Field[] fields = arrayListClass.getFields();
IO.println("Public fields for ArrayList: " + fields.length);
Public fields for ArrayList: 0
در واقع هیچ فیلد Public در ArrayList یا Super Class هایش وجود ندارد.
دو روش برای دریافت Type یک فیلد وجود دارد. اولین و سادهترین فراخوانی متود getType() است که یک شیء Class برمیگرداند. این تا زمانی که Type فیلد Generic نباشد خوب کار میکند. اگر Generic باشد، Type فقط Object است که ممکن است اطلاعات مبهمی به شما بدهد.
فرض کنید کلاس زیر را دارید:
class User {
private String name;
}
میتوانید Type فیلد name را با کد زیر دریافت کنید:
Class<?> userClass = User.class;
Field nameField = userClass.getDeclaredField("name");
Class<?> type = nameField.getType();
IO.println("type = " + type.getName());
توجه کنید getDeclaredField() میتواند NoSuchFieldException پرتاب کند.
type = java.lang.String
روش قبلی فقط زمانی کار میکند که Type فیلد یک Type Generic نباشد.
فرض کنید کلاس Generic زیر را دارید:
class Box<T> {
private T t;
}
با روش معمول Type فیلد را جستجو کنید:
Class<?> boxClass = Box.class;
Field field = boxClass.getDeclaredField("t");
Class<?> type = field.getType();
IO.println("type = " + type.getName());
Type Erased شده دریافت میشود که Object است:
type = java.lang.Object
به همین دلیل از Java SE 5، متود Field.getGenericType() به کلاس Field اضافه شده که یک شیء Type برمیگرداند. این شیء متود getTypeName() دارد که نام Type Generic فیلد را به شما میدهد.
کد قبلی را با کد زیر جایگزین کنید:
Class<?> boxClass = Box.class;
Field field = boxClass.getDeclaredField("t");
Type genericType = field.getGenericType();
IO.println("type = " + genericType.getTypeName());
type = T
نکته: این متود فقط Type اعلامشده در کد منبع را برمیگرداند، نه Type واقعی در runtime. حتی اگر در runtime Type خاصی به T اختصاص داده شده باشد، همچنان T بازمیگردد.
فرض کنید کلاس Box زیر را دارید:
public static class Box<T> {
private final T t;
public Box(T t) {
this.t = t;
}
}
و کد زیر را دارید:
Box<String> box = new Box<>("Hello");
Class<?> boxClass = box.getClass();
Field tField = boxClass.getDeclaredField("t");
Type type = tField.getGenericType();
IO.println("type = " + type);
خروجی همچنان T است:
type = T
Modifier فیلدها مشابه Modifier کلاسها عمل میکنند. متود Field.getModifiers() عددی int برمیگرداند که هر بیت نشاندهنده یک Modifier است. از متدهای ایستا کلاس Modifier برای رمزگشایی استفاده کنید.
فرض کنید کلاس User زیر را دارید:
class User {
private String name;
}
Modifier فیلد name:
Class<?> userClass = User.class;
Field nameField = userClass.getDeclaredField("name");
int modifiers = nameField.getModifiers();
IO.println("isStatic: " + Modifier.isStatic(modifiers));
IO.println("isPublic: " + Modifier.isPublic(modifiers));
IO.println("isPrivate: " + Modifier.isPrivate(modifiers));
IO.println("isFinal: " + Modifier.isFinal(modifiers));
isStatic: false
isPublic: false
isPrivate: true
isFinal: false
همچنین Field.accessFlags() مانند کلاسها قابل استفاده است:
Class<?> userClass = User.class;
Field nameField = userClass.getDeclaredField("name");
Set<AccessFlag> accessFlags = nameField.accessFlags();
IO.println("Flags = " + accessFlags);
Flags = [PRIVATE]
چون accessFlags یک Set است، میتوانید از الگوی زیر برای بررسی وجود یک Access Flag خاص استفاده کنید:
Class<?> userClass = User.class;
Field nameField = userClass.getDeclaredField("name");
boolean isStatic = nameField.accessFlags().contains(AccessFlag.STATIC);
IO.println("Is static? " + isStatic);
Is static? false
همچنین به یاد داشته باشید هیچ رابطه یک به یک بین Modifier ها و Access Flag ها وجود ندارد.
Reflection API به شما امکان خواندن و بهروزرسانی مقادیر فیلدها از طریق اشیاء Field را میدهد. این کار با دو متود Field.get(Object) و Field.set(Object,Object) انجام میشود.
در هر دو حالت باید نمونهای که میخوانید یا مینویسید را پاس دهید، چون شیء Field از Class دریافت میشود که برای هر کلاس یکتا است.
فرض کنید کلاس زیر را دارید:
class User {
String name;
public User(String name) {
this.name = name;
}
public String toString() {
return "User[name='" + name + "']";
}
}
مقدار فیلد name را برای یک نمونه بخوانید:
User maria = new User("Maria");
Class<?> userClass = maria.getClass();
Field nameField = userClass.getDeclaredField("name");
String name = nameField.get(maria);
IO.println("name of maria = " + name);
name of maria = Maria
و مقدار آن را تغییر دهید:
nameField.set(maria, "Mary");
اگر فیلد name Private باشد یا کد فراخوانی در پکیج دیگری باشد، خطای IllegalAccessException دریافت خواهید کرد. این نشان میدهد قوانین قابلیت دید هنوز در Reflection API برقرار هستند.
Exception in thread "main" java.lang.IllegalAccessException: class org.devjava.Main cannot access
a member of class org.devjava.User with modifiers "private"
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:420)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:709)
at java.base/java.lang.reflect.Field.checkAccess(Field.java:1140)
at java.base/java.lang.reflect.Field.get(Field.java:425)
at org.devjava.Main.main(Main.java:16)
میتوانید با فراخوانی setAccessible(true) این بررسیها را غیرفعال کنید:
nameField.setAccessible(true);
nameField.set(maria, "Mary");
این کد شیء فیلد nameField را قابل دسترسی میکند. یک اشتباه رایج این است که فکر کنید setAccessible(true) فیلد را از حالت Private خارج میکند. اینطور نیست. فقط بررسیهای Access Control را غیرفعال میکند.
نکته: محدودیتهایی برای فیلدهایی که میتوانید قابل دسترسی کنید وجود دارد. اطلاعات بیشتر در بخش «دسترسیپذیر کردن یک فیلد، متود یا Constructor» موجود است.
کلاسهای Field، Method و Constructor همه از یک Super Class مشترک به نام AccessibleObject ارث میبرند. این کلاس متود setAccessible() را تعریف کرده که میتواند بررسیهای Visibility Control را غیرفعال کند.
این فراخوانی در صورتی با شکست مواجه میشود که عنصری که میخواهید قابل دسترسی کنید به یک Named Module دیگر تعلق داشته باشد و آن Module خود یا پکیج حاوی عنصر را برای Reflection باز اعلام نکرده باشد.
اگر برنامه شما از Java Module System استفاده نمیکند، میتوانید با خیال راحت از Reflection API برای دسترسی به تمام عناصر برنامه استفاده کنید.
خواندن فیلدهای Final مشابه خواندن هر فیلد دیگری است. همانطور که در بخش قبلی دیدید، قابل دسترسی کردن یک فیلد بررسیهای Access Control包括 Visibility و Modifier Final را غیرفعال میکند.
فرض کنید کلاس زیر را دارید که فیلد name هم private و هم final است:
class User {
private final String name;
public User(String name) {
this.name = name;
}
public String toString() {
return "User[name='" + name + "']";
}
}
کد زیر را اجرا کنید:
User maria = new User("Maria");
IO.println("Maria = " + maria);
Class<?> userClass = maria.getClass();
Field nameField = userClass.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(maria, "Mary");
IO.println("Maria = " + maria);
خروجی نشان میدهد با وجود اینکه فیلد name Private و Final است، توانستید آن را با Reflection API تغییر دهید:
Maria = User[name='Maria']
Maria = User[name='Mary']
تنظیم setAccessible(true) دسترسی خواندن و نوشتن را فراهم میکند. اما استثناهایی وجود دارد: دسترسی نوشتن برای فیلدهای Static و فیلدهای Instance یک Record داده نمیشود.
از JDK 26 به بعد، هنگام تغییر یک فیلد Final، خروجی زیر دریافت خواهید کرد:
Maria = User[name='Maria']
WARNING: Final field name in class User has been mutated reflectively by class User in unnamed module @18b4aac2
WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning
WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled
Maria = User[name='Mary']
نکته مهم: از JDK 26 به بعد، هنگام تغییر یک فیلد Final از طریق Reflection، یک Warning دریافت خواهید کرد. در آینده این عملیات مسدود خواهد شد مگر اینکه Final Field Mutation فعال باشد. در حال حاضر تلاشی برای معنادار کردن واقعی
finalوجود دارد.
Type های Primitive میتوانند بهصورت خودکار به Type Wrapper مربوطه تبدیل (Box) شوند. Field.get(Object) نوع Object برمیگرداند و Field.set(Object,Object) نوع Object میگیرد. بنابراین هنگام خواندن فیلد باید نتیجه را Cast کنید.
برای Type های Primitive میتوانید از متدهای اختصاصی مانند Field.getInt(Object) و Field.setInt(Object,int) استفاده کنید تا از هزینه Boxing و Unboxing اجتناب شود.
مراقب باشید: فراخوانی یک متود اختصاصی روی فیلدی با Type نامناسب باعث پرتاب Exception میشود.
فرض کنید کلاس User با فیلد age از نوع int دارید:
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
کد زیر را اجرا کنید:
User maria = new User("Maria", 32);
Class<?> userClass = User.class;
Field ageField = userClass.getDeclaredField("age");
ageField.setAccessible(true);
int age = ageField.getInt(maria);
IO.println("age = " + age);
ageField.setInt(maria, 33);
age = ageField.getInt(maria);
IO.println("age = " + age);
age = 32
age = 33
اما اگر Type فیلد age را از int به Integer تغییر دهید، همین کد خطای IllegalArgumentException با پیام تبدیل Type غیرمجاز پرتاب میکند.
Exception in thread "main" java.lang.IllegalArgumentException:
Attempt to get java.lang.Integer field "org.devjava.User.age" with illegal data type conversion to int
at java.base/jdk.internal.reflect.FieldAccessorImpl.newGetIllegalArgumentException(FieldAccessorImpl.java:130)
at java.base/jdk.internal.reflect.FieldAccessorImpl.newGetIntIllegalArgumentException(FieldAccessorImpl.java:193)
at java.base/jdk.internal.reflect.MethodHandleObjectFieldAccessorImpl.getInt(MethodHandleObjectFieldAccessorImpl.java:84)
at java.base/java.lang.reflect.Field.getInt(Field.java:612)
at org.devjava.Main.main(Main.java:40)
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://dev.java/learn/