اینترفیس (Interface)
اینترفیسها را میتوان به دو صورت تعریف کرد:
- اینترفیس یک نوع قرارداد است. هر کلاس میتواند یک یا چند اینترفیس را بهکار گیرد و پیادهسازی (implement) کند. کلاسی که یک اینترفیس را پیادهسازی کند باید قواعد تعریف شده در آن را رعایت کند.
در عمل اینترفیس ساختاری مانند کلاس دارد که تعدادی متد در آن تعریف میشوند اما این متدها بدنه ندارند و فقط امضای آنها تعریف شده است. کلاسی که یک اینترفیس را پیادهسازی میکند باید تمام متدهای موجود در آن اینترفیس را Override کند.
متدهای موجود در یک اینترفیس بدنهای ندارند و بنابراین مشخص نیست که چه کاری انجام میدهند. هر کلاسی که اینترفیس را پیادهسازی میکند میتواند به شکل دلخواه خود بدنه آن متد را تعریف کنند. - دومین تعریف و شاید تعریف بهتر این است که اینترفیس راهی برای توصیف مجموعهای از کلاسها است. فرض کنید اینترفیسی داریم که متدهای مربوط به کار با شبکه در آن تعریف شدهاند. حال میتوانیم کلاسهایی که این اینترفیس را پیادهسازی میکنند به عنوان کلاسهایی که با شبکه کار میکنند گروهبندی و توصیف کنیم.
ایجاد و تعریف یک اینترفیس
اینترفیسها به شکل کلی زیر در فایلی با فرمت .java ذخیره میشوند:
interface نام اینترفیس {
}
اینترفیس میتواند دارای تعیینکننده دسترسی public و یا بدون تعیینکننده دسترسی باشد.
میخواهیم اینترفیسی به نام Animal با دو متد تعریف کنیم. فایلی با فرمت .java ایجاد کرده و کدهای زیر را درون آن مینویسیم (در ایکلیپس میتوانید با راست کلیک روی پکیج و از منوی New گزینه Interface را انتخاب کنید):
public interface Animal { void eat(); void walk(); }
همانطور که میبینید این متدها بدنه ندارند. در واقع مشخص نیست که این متدها چه کاری انجام میدهند و این به عهده کلاسهایی که این اینترفیس را پیادهسازی میکنند است که بدنه این متدها را تعریف کنند.
نکته دیگر اینکه این دو متد هیچکدام تعیینکننده دسترسی ندارند. به طور پیشفرض تمام متدهای موجود در یک اینترفیس public هستند. البته اگر برای این دو متد تعیینکننده دسترسی public هم بنویسیم مشکلی پیش نمیآید اما نیازی به این کار نیست.
حال کلاسی به نام Dog ساخته و به شکل زیر اینترفیس Animal را در آن پیادهسازی میکنیم:
public class Dog implements Animal { }
عبارت implements Animal به این معناست که این کلاس اینترفیس Animal را پیادهسازی میکند. اگر از ایکلیپس یا هر IDE دیگر استفاده میکنید با خطایی مبنی بر اینکه باید متدهای اینترفیس Animal را پیادهسازی کنید مواجه خواهید شد.
در ایکلیپس با کلیک بر روی ناحیه قرمز مشخص شده در تصویر زیر منویی باز میشود. با کلیک بر روی گزینه Add unimplemented methods متدهای اینترفیس Animal به این کلاس اضافه میشوند:
درون متدهای اضافه شده به کلاس یک دستور چاپ متناسب با آن نوشتیم و حال کلاس ما به صورت زیر است:
public class Dog implements Animal { @Override public void eat() { System.out.println("Eating Meat!"); } @Override public void walk() { System.out.println("Walking like a Dog!"); } }
همانطور که دیدید متدهای eat و walk که در اینترفیس Animal بودند را متناسب با نوع کلاس (سگ) تعریف کردیم. کلاس دیگری به نام Horse مینویسیم و اینترفیس Animal را در آن پیادهسازی میکنیم:
public class Horse implements Animal { @Override public void eat() { System.out.println("Eating Apples!"); } @Override public void walk() { System.out.println("Walking like a Horse!"); } }
وقتی ایکلیپس متدهای اینترفیس Animal را به کلاسی که نوشتیم اضافه کرد بالای این متدها عبارت @Override را هم قرار داد.
به عباراتی از این قبیل که با @ شروع میشوند Annotation گفته میشود (که در آینده با آن آشنا خواهید شد)
عبارت @Override به این معنی است که این دو متد Override شدهاند. با مفهوم Override در قسمت وراثت آشنا شدید. وقتی از اینترفیس متدی را در کلاسی پیادهسازی میکنید در واقع آن را Override کردهاید. البته وجود عبارت @Override الزامی نیست و همین که امضای متد نوشته شده در کلاس با امضای متد موجود در اینترفیس برابر باشد به این معنی است که آن متد Override شده است اما اگر متدی را به قصد Override کردن بنویسید و اشتباها امضای آن متد با امضای متد اصلی تفاوت داشته باشد هیچ اخطاری به شما داده نمیشود اما اگر این عبارت را بالای متدی که میخواهید Override کنید بنویسید با این کار به کامپایلر میگویید که این متد را از یک اینترفیس یا یک کلاس پدر Override کردهاید و اگر اشتباها خطایی در نوشتن متد رخ دهد و متد Override شده با متد اصلی همخوانی نداشته باشد کامپایلر به شما اطلاع میدهد.
پیادهسازی چند اینترفیس
بر خلاف بحث وراثت که در آن یک کلاس فقط از یک کلاس دیگر میتواند ارثبری داشته باشد در اینترفیسها این محدودیت را نداریم. یعنی یک کلاس میتواند هر تعداد اینترفیس را که بخواهد پیادهسازی کند که در این صورت باید متدهای موجود در تمام اینترفیسهایی که پیادهسازی کرده را Override کند.
به عنوان مثال کلاس زیر دو اینترفیس را پیاده سازی میکند:
public class Tiger implements Animal, Cats { // محتویات کلاس }
همانطور که دیدید نام اینترفیسهایی که کلاس پیادهسازی میکند با علامت کاما از هم جدا شده است.
متغیرها در اینترفیس
اینترفیسها علاوه بر متدها میتوانند شامل متغیرها نیز باشند. متغیرهایی که در یک اینترفیس تعریف میشوند همگی public و static و final هستند حتی اگر هنگام تعریف صراحتا این موارد را ذکر نکنیم.
نکته: متغیرهای final متغیرهایی هستند که بعد از مقداردهی اولیه به آنها دیگر مقدار آنها قابل تغییر نیست. این متغیرها هنگام تعریف باید حتما مقداردهی شوند. نام این متغیرها طبق قرارداد تماما با حروف بزرگ نوشته شده و کلمات آن با _ از هم جدا میشوند. مثل:
int MY_VARIABLE = 13;
مثال: در اینترفیس زیر متغیری به نام PI با مقدار ۳٫۱۴ داریم. هر کلاسی که این اینترفیس را پیادهسازی کند میتواند مستقیما به این متغیر دسترسی داشته باشد:
public interface Circle { float PI = 3.14; void draw(); }
نکته: توجه داشته باشید که متغیرهایی که در یک اینترفیس تعریف میکنید با آن اینترفیس مرتبط باشند و مورد استفاده متدهای موجود در آن اینترفیس قرار گیرند.
وراثت در اینترفیس
نکته جالب دیگری که در مورد اینترفیسها وجود دارد این است که یک اینترفیس میتواند از یک اینترفیس دیگر ارثبری داشته باشد. اگر کلاسی اینترفیسی را پیادهسازی کند که آن اینترفیس جزو یک سلسله مراتب وراثتی باشد آنگاه آن کلاس باید تمام متدهای موجود در اینترفیسهای پدر که در این سلسله مراتب وجود دارند را پیادهسازی کند.
برای فهم بهتر این مطلب به مثال زیر توجه کنید:
سه اینترفیس با نامهای A و B و C داریم که اینترفیس C از B و اینترفیس B از A ارث بری دارد (البته این اینترفیسها هر کدام در فایلهای جدا نوشته میشوند):
public interface A { void method1(); } public interface B extends A { void method2(); } public interface C extends B { void method3(); }
حال اگر کلاسی اینترفیس C را پیادهسازی کند باید علاوه بر متدهای اینترفیس C متدهای اینترفیسهای B و A را نیز Override کند. اگر کلاسی اینترفیس B را پیاده سازی کند فقط باید متدهای اینترفیس B و A را Override کند و دیگر نیازی به Override کردن متدهای اینترفیس C نیست چون C جزو اینترفیسهای پدر اینترفیس B نمیباشد.
متدهای default در اینترفیس
در ابتدا گفتیم که متدهای موجود در یک اینترفیس فاقد بدنه هستند و پیادهسازی و تفسیر آنها به عهده کلاسی است که آن اینترفیس را پیادهسازی میکند اما در نسخه ۸ جاوا قابلیت جدیدی به اینترفیسها اضافه شد که به وسیله آن میتوان در یک اینترفیس متدی داشت که پیادهسازی شده باشد. این متدها باید با کلمه کلیدی default تعریف شوند.
نکته مهم این است که اگر یک اینترفیس شامل یک یا چند متد default باشد آنگاه کلاسی که آن اینترفیس را پیادهسازی میکند مجبور به Override کردن متدهای default آن نیست.
مثال: اینترفیسی به شکل زیر داریم که یک متد default در آن نوشته شده است:
public interface Test { default void msg() { System.out.println("Hello"); } }
حال کلاسی میسازیم که این اینترفیس را پیادهسازی کند:
public class A implements Test { }
همانطور که گفتیم کلاس A مجبور به Override کردن متد msg نیست اما Override نکردن متد msg به این معنا نیست که این متد در کلاس A وجود ندارد. اگر شیئی از کلاس A ایجاد کنید میتوانید متد msg را از آن فراخوانی کنید:
A obj = new A(); obj.msg();
نتیجه فراخوانی این متد چاپ عبارت Hello است.
اگر بخواهید یک متد default را Override کنید باید آن را با تعیینکننده دسترسی public در کلاس خود بنویسید. مثلا اگر بخواهیم متد msg را Override کنیم به صورت زیر عمل میکنیم:
public void msg() { }
در قسمت وراثت گفتیم که متد Override شده میتواند متدی که از آن Override شده را با کلمه super فراخوانی کند. اگر در یک متد default که از یک اینترفیس Override کرده باشیم بخواهیم متد اصلی را فراخوانی کنیم به صورت زیر عمل میکنیم:
public void msg() { Test.super.msg(); }
همانطور که میدانید نام اینترفیسی که متد msg در آن قرار دارد Test است.
متدهای استاتیک در اینترفیس
قابلیت دیگری که در نسخه ۸ جاوا به اینترفیسها اضافه شد امکان تعریف متدهای استاتیک در اینترفیس است. متدهای استاتیک موجود در یک اینترفیس میتوانند مستقیما فراخوانی شوند بدون اینکه نیاز به پیادهسازی آن اینترفیس باشد.
مثال:
public interface Test { static void sayHello() { System.out.println("Hello"); } }
مانند متدهای استاتیکی که در کلاسها تعریف میشوند، متدهای استاتیک اینترفیسها را نیز تنها با استفاده از نام اینترفیس و نوشتن نام متد استاتیک فراخوانی میکنیم:
Test.sayHello();
هرجا که بخواهید میتوانید با این روش متد sayHello را فراخوانی کنید بدون نیاز به پیادهسازی اینترفیس Test
استفاده از اینترفیس به عنوان نوع داده
میتوانید متغیری از نوع یک اینترفیس تعریف کنید. مثلا متغیری از نوع اینترفیس Test که قبلا نوشتیم تعریف میکنیم:
Test t;
این متغیر فعلا فقط تعریف شده و مقداری به آن نسبت ندادهایم. اما چه چیزی میتوان در این متغیر قرار داد؟ واضح است که نمیتوانیم به صورت زیر عمل کنیم:
Test t = new Test();
چون Test یک اینترفیس است نه کلاس که بخواهیم از آن شی بسازیم.
اشیائی را میتوانیم به این اینترفیس نسبت دهیم که از کلاسهایی ساخته شده باشند که این اینترفیس را پیادهسازی کرده باشند.
مثال: اینترفیسی به نام Animal با یک متد داریم:
public interface Animal { void eat(); }
کلاسی به نام Cat میسازیم که اینترفیس Animal را پیادهسازی میکند. این کلاس دو متد دارد که یکی از آنها متد eat از اینترفیس Animal است که آن را Override میکنیم:
public class Cat implements Animal { @Override public void eat() { System.out.println("Eating fish..."); } public void sayHello() { System.out.println("Meow!"); } }
حال در کلاسی که متد main ما در آن قرار دارد یک متغیر از نوع اینترفیس Animal تعریف میکنیم و یک شی از جنس کلاس Cat به آن نسبت میدهیم:
Animal animal = new Cat();
حال میتوانید متد eat که در اینترفیس Animal داشتیم را از متغیر animal فراخوانی کنید:
animal.eat();
توجه داشته باشید که چون متغیر animal حاوی آدرس یک شی از جنس Cat است بنابراین متد eat که در کلاس Cat آن را Override کردیم فراخوانی میشود.
توجه داشته باشید که اگرچه متغیر animal اکنون به یک شی از کلاس Cat اشاره دارد اما نمیتوان متد sayHello که در کلاس Cat داشتیم را نمیتوانیم از متغیر animal فراخوانی کنیم چون متغیر animal از نوع اینترفیس Animal است و در اینترفیس Animal فقط متد eat وجود دارد.
نکته جالب اینجاست که اگرچه نوع متغیر animal تغییر نمیکند و همیشه از نوع اینترفیس Animal است اما رفتار آن میتواند با توجه به شیئی که به آن نسبت دادیم متفاوت باشد و یکی از مصادیق اصل چندریختی که در ابتدای بحث شیگرایی به آن اشاره کردیم همین است.
مثال: کلاسی به نام Dog ایجاد کردیم که اینترفیس Animal را پیادهسازی میکند:
public class Dog implements Animal { @Override public void eat() { System.out.println("Eating meat..."); } }
حال کدهای زیر را در متد main خود مینویسیم:
Animal animal = new Cat(); animal.eat(); animal = new Dog(); animal.eat();
در این کد ابتدا یک شی از کلاس Cat به متغیر animal نسبت داده شده و متد eat فراخوانی شده است. سپس یک شی از کلاس Dog به همان متغیر animal نسبت دادیم و دوباره متد eat را فراخوانی کردیم. نتیجه اجرای برنامه چاپ عبارات زیر است:
Eating fish…
Eating meat…
پس همانطور که دیدید نوع متغیر animal هیچ وقت تغییر نکرد و فقط شیئی که به آن نسبت داده شده بود را تغییر دادیم و به این وسیله شاهد تغییر رفتار این متغیر بودیم.
خیلی کامل و دقیق نوشته شده آموزشها بهنظرم. ممنون و خسته نباشی.
عالی بود بخصوص بخش جاوای ۸٫ تشکر
خیلی ممنون مختصر و مفید
بسیار شفاف و واضح توضیح داده شده بود. بی نهایت ممنونم
سلام یه سوال : اگر یک interface یک interface دیگه رو extends کنه ( یه اینترفیس از یه اینترفیس دیگه ارث بری کنه ) متدهای static کلاس superClass به subClass انتقال پیدا میکنه یا خیر ؟ متد های default پدر چی به پسر به به ارث میرسه ؟
# مرسی از راهنماییتون
عالی بود
سپاس. عالی بود.