Milad Sadeghi
Milad Sadeghi
خواندن ۷ دقیقه·۱۴ روز پیش

آموزش رفلکشن (Reflection) در جاوا

سلام به همگی ، امیدوارم حالتون خوب باشه. بعد از مدت ها برگشتم با یه پست جدید ، این بار با آموزش یه فیچر از زبان برنامه نویسی جاوا.


رفلکشن چیه؟

در حالت کلی ما با استفاده از رفلکشن میتونیم در زمان runtime یه سری تغییرات رو روی کلاس هایی که نوشتیم اعمال کنیم. اجازه بدید با یه مثال شروع کنیم و ببینیم که رفلکشن (reflection) چیه و چه امکاناتی رو در اختیار ما قراره میده:

فرض کنید که یک کلاس به نام Cat پیاده سازی کردیم که طبق تصویر بالا یک سری property و method داره. برای تست کردن امکانات رفلکشن نیاز داریم که یک متد main هم داشته باشیم تا یک آبجکت از کلاس Cat رو داخلش ایجاد کنیم:

برای شروع بریم ببینیم که میتونیم لیست فیلدهای داخل کلاس Cat رو به دست بیاریم یا نه. زمانی که قصد داریم تا اطلاعاتی رو از کلاس یک آبجکت به دست بیاریم ، از متد getClass استفاده میکنیم. متد getClass به ما اجازه میده تا از امکانات مربوط به reflection که جاوا در اختیارمون قرار داده استفاده کنیم. خب برای به دست آوردن فیلدهای تعریف شده توی کلاس Cat ، باید از متد getDeclaredFields استفاده کنیم و از آنجایی که این متد آرایه ای از نوع Field برمیگردونه باید مقدار برگشتی اون رو داخل یک متغییر با همین نوع ذخیره کنیم:

درضمن برای استفاده از کلاس Field باید اون رو از داخل پکیج java.lang.reflect ایمپورت کنیم. حالا ما یک آرایه از نوع Field داریم که محتوی تمامی فیلدهای تعریف شده داخل کلاس Cat هست و میتونیم با استفاده از یک حلقه ی for ، اون رو پیمایش کنیم:

حالا اگر برنامه رو اجرا کنیم ، لیست فیلدهای کلاس Cat رو خواهیم دید:

name
age

جالبه، نه؟! اینکه میتونیم کدی بنویسیم که ساختار داخلی یک کلاس توی جاوا رو ببینه.


اگر کلاس Cat رو دوباره بررسی کنیم میبینیم که یک فیلد به نام name از نوع private و final داره ، به این معنی که ما دسترسی مستقیمی به اون فیلد ، خارج از کلاس خودش نداریم و این فیلد تنها یک بار میتونه مقداردهی بشه اون هم فقط داخل constructor کلاس هست. به عبارت دیگه اگر ما قصد داشته باشیم که کد زیر رو بنویسیم:

کدی که نوشتیم حتی کامپایل هم نمیشه و خطا میده:

نتیجه اینکه بدون استفاده از رفلکشن اصلا امکان تغییر مقدار فیلد name از داخل متد main وجود نداره. ولی اگر پای رفلکشن به قضیه باز بشه ، اوضاع کلا فرق میکنه. چطور؟! الان بهتون میگم.


با استفاده از رفلکشن ما میتونیم بدون ایجاد تغییر توی کلاس Cat ، جاوا رو مجبور کنیم تا تغییراتی که میخوایم رو روی کلاس مدنظرمون اعمال کنه.

حالا قصد داریم که مقدار فیلد name کلاس Cat رو تغییر بدیم. برای این کار باید روی آرایه ی فیلدهای کلاس Cat پیمایش کنیم و فیلد مورد نظر رو به دست بیاریم. پس مینویسیم:

برای ایجاد تغییرات در مقدار از متد set استفاده میکنیم. این متد دو تا پارامتر ورودی داره:

اولی: آبجکتی هست که قصد داریم روی فیلدهای اون تغییرات ایجاد کنیم

دومی: مقداری هست که قصد داریم ست کنیم

پس مینویسیم:

بعد از نوشتن کد بالا، خطای Unhandled Exception میگیریم که برای رفع اون باید throws Exception رو به signature متد main اضافه کنیم، پس مینویسیم:

حالا اگر برنامه رو اجرا کنیم، خطای IllegalAccessException میگیریم که کاملا منطقی هست چون قصد داریم که به یک فیلد private دسترسی داشته باشیم. برای رفع این خطا باید سطح دسترسی فیلد name رو هم قبل از مقداردهی تغییر بدیم:

اگر الان برنامه رو اجرا کنیم ، هیچ خطایی نمیگیریم. فقط باید ببینیم که تغییراتی که میخواستیم اعمال شدن یا نه. پس مینویسیم:

یک بار دیگه برنامه رو اجرا میکنیم و خروجی زیر رو میبینیم:

Jerry

نتیجه اینکه علی رغم private و final بودن فیلد name توی کلاس Cat ، میتونیم با استفاده از رفلکشن اون رو دستکاری کنیم و تغییرات مدنظرمون رو اعمال کنیم.


امکانات مربوط به رفلکشن فقط محدود به فیلدهای یک کلاس نمیشه. مثلا ما میتونیم روی متدهای تعریف شده داخل یک کلاس هم تغییرات اعمال کنیم.

اول ببینیم که میتونیم لیست متدهای تعریف شده توی کلاس Cat رو به دست بیاریم یا نه. برای اینکار داخل متد main مینویسیم:

متد getDeclaredMethods آرایه ای از کلاس Method رو برمیگردونه ، پس باید مقدار اون رو داخل یک متغیر با همون نوع ذخیره کنیم:

برای استفاده از کلاس Method باید اون رو از داخل پکیج java.lang.reflect ایمپورت کنیم. حالا بریم ببینیم که تونستیم لیست متدها رو به دست بیاریم یا نه. برای اینکار باید روی آرایه متدها پیمایش کنیم:

حالا اگر برنامه رو اجرا کنیم، خروجی زیر رو میبینیم:

getName
thisIsPublicStatic
thisIsPrivateStatic
getAge
setAge
meow
heyThisIsPrivate

نکته ای که باید در نظر داشته باشید اینه که جاوا هیچ ترتیب خاصی رو برای آرایه متدها رعایت نمیکنه به همین خاطر همیشه باید روی این آرایه پیمایش کنیم و المانی که مدنظرمون هست رو انتخاب و روش کار کنیم.


یکی از کارهای جالبی که با آبجکت method میشه انجام داد، اصطلاحا invoke کردن یا صدا زدن متد هست. برای اینکار از متد invoke استفاده میکنیم. فرض کنید که قصد داریم تا متد meow داخل کلاس Cat رو با استفاده از رفلکشن صدا بزنیم. برای اینکار مینویسیم:

متد invoke یک سری پارامتر ورودی میگیره:

اولی: آبجکتی که قصد داریم روی کلاس اون کار کنیم

بعدی ها: اگر متدی که میخوایم باهاش کار کنیم ، خودش پارامتر ورودی داشته باشه ، باید مقدار اونها رو به ترتیب تعریف شده داخل کلاسش ، به متد invoke پاس بدیم. توی مثال بالا از اونجایی که متد meow هیچ پارامتر ورودی ای نمیگیره ، فقط پارامتر اول رو به متد invoke پاس میدیم.

حالا اگر برنامه رو اجرا کنیم ، قاعدتا باید متد meow صدا زده بشه و خروجی زیر رو ببینیم:

Meow!

حالا شاید سوال براتون پیش بیاد که چرا برای صدا زدن متد meow که از نوع public هست ، باید از رفلکشن استفاده کنیم؟ مادامی که میتونیم بطور مستقیم این متد رو صدا بزنیم:

سوالتون کاملا منطقیه ، در واقع توی مثال بالا نیازی نیست که از رفلکشن استفاده کنیم. ولی اگر بخوایم متدی رو صدا بزنیم که بهش دسترسی نداریم ، چطور؟!

برای مثال داخل کلاس Cat متدی داریم به نام heyThisIsPrivate که یک متد از نوع private هست به این معنی که خارج از کلاس خودش بهش نمیتونیم دسترسی داشته باشیم. برای اینکه بتونیم این متد رو صدا بزنیم، مینویسیم:

حالا اگر برنامه رو اجرا کنیم، خروجی زیر رو میبینیم:

This is a private method

داخل کلاس Cat یک متدی داریم به نام thisIsPublicStatic که از نوع static هست. برای صدا زدن متدهای static که مربوط به کلاس میشن نه آبجکت های ساخته شده از اون کلاس ، اگر بخوایم از متد invoke توی رفلکشن استفاده کنیم ، باید به عنوان پارامتر اول بجای myCat ، کلید واژه ی null رو پاس بدیم:

حالا اگر برنامه رو اجرا کنیم ، خروجی زیر رو میبینیم:

This is a public static method

سناریوی بالا برای متدهای private و static هم صادق هست با این تفاوت که قبل از صدا زدن متد ، باید سطح دسترسی اون رو دستکاری کنیم:

حالا اگر برنامه رو اجرا کنیم ، خروجی زیر رو میبینیم:

This is a private static method

چرا باید از رفلکشن استفاده کنیم؟

  1. چون فیچر جذابی هست
  2. توی فریمورک ها خیلی استفاده میشه. مثلا فریمورک spring برای دستکاری و تغییر کلاس هایی که ما نوشتیم از رفلکشن استفاده میکنه
  3. توی تست کردن برنامه ، اگر از رفلکشن استفاده کنیم ، دستمون به مراتب باز تره بخاطر امکان کار کردن با المان های private

چرا نباید از رفلکشن استفاده کنیم؟

  1. توی استفاده از رفلکشن اگر ندونید که دارید چیکار میکنید میتونید باگ هایی تولید کنید که به سادگی قابل رفع نیستن
  2. چون تمامی کارهای مربوط به رفلکشن در runtime انجام میشه ، امکان اعمال فرایندهای بهینه سازی زمان کامپایل وجود نداره در نتیجه کدهای مربوط به رفلکشن معمولا از بقیه کدها کُند تر هستن.
  3. در حالت کلی اگر کاری که قصد دارید انجام بدید ، بدون کمک گرفتن از رفلکشن قابل انجام هست ، نباید ازش استفاده کنید.

این هم از آموزش reflection در جاوا

امیدوارم براتون مفید بوده باشه. منتظر نظراتتون توی کامنت ها هستم

موفق باشید :)


جاوابرنامه نویسیبکندکامپیوتر
Java Backend Software Engineer
شاید از این پست‌ها خوشتان بیاید