آشنایی با واژه کلیدی super در جاوا و کاربردهای آن

این اولین نوشته من در ویرگول است و مطمانا کم‌تجربگی‌ام چه در زمینه نوشتن و چه در زمینه برنامه‌نویسی در پایین بودن کیفیت آن بی‌تاثیر نخواهد بود.

اگر شما هم یک برنامه‌نویس جاوا و به صورت اختصاصی‌تر یک توسعه‌دهنده اپلیکیشن‌های اندرویدی با استفاده از این زبان برنامه‌نویسی هستید، مطمانا بارها و بارها با کلمه کلیدی super، به خصوص در شرایطی که قصد بازنویسی متدهای ارث‌بری شده از کلاس والد و یا ایجاد متد سازنده برای کلاس‌های فرزند خود را دارید، مواجه شده‌اید.

با وجود گذشت بیش از یک سال از ورود به دنیای برنامه‌نویسی اندروید و آغاز توسعه اپلیکیشن برای این سیستم‌عامل موبایلی، و همچنین با وجود مواجهه روزانه با کلمه کلیدی super، اما هیچ‌گاه به دنبال یافتن ماهیت وجودی و کاربردهای آن نرفته و صرفا به استفاده و یا نادیده گرفتن آن بسنده کرده‌ام.

اما حس کنجکاوی موجود در ذات هر انسان و به خصوص برنامه‌نویسی و همچنین ضرورت آشنایی با مفاهیم پایه و کاربردی زبان برنامه‌نویسی مورد استفاده در فرآیند توسعه نرم‌افزار، در نهایت منجر شد تا این کلمه کلیدی در ظاهر ساده اما بسیار پرکاربرد و مهم جاوا را با مراجعه به منابع مختلفی مورد مطالعه قرار داده و به سوالات شکل گرفته در ذهنم پاسخ‌هایی بیابم.

هرچند که ممکن است این مبحث از سوی بسیاری از برنامه‌نویسان باتجربه جاوا/اندروید موضوعی ابتدایی و ساده تلقی شود، اما تصمیم گرفتم تا با انتشار خلاصه‌ای از یافته‌هایم در خصوص دستور super آن را با سایر برنامه‌نویسان مبتدی اندروید و هم‌سطح با خودم به اشتراگ بگذارم تا شاید مفید واقع گردد.

جهت توضیح ماهیت و کاربرهای کلمه کلیدی super، ابتدا لازم است تا با اصطلاحات super class و sub class در زبان برنامه‌نویسی جاوا آشنا شویم.

در جاوا یک کلاس می‌تواند ویژگی‌ها (Attributes) و متدهای (Methods) کلاس دیگری را ارث‌بری کند. در فرآیند ارث‌بری با دو دسته از کلاس‌ها سروکار داریم:

سوپرکلاس‌ (Super Class): کلاس والدی که از آن ارث‌بری می‌شود

ساب‌کلاس (Sub Class): کلاس فرزندی که از یک کلاس والد ارث‌بری می‌کند

در جاوا برای آنکه یک کلاس از کلاس دیگر ارث‌بری کند، باید از کلمه کلیدی extends استفاده کنیم.

نکته قابل توجه در خصوص ارث‌بری در جاوا این است که ساب‌کلاس یا کلاس‌های فرزند یا کلاس‌هایی که ارث‌بری می‌کنند تنها می‌توانند به ویژگی‌ها (متغیرها یا پارامترها) و متدهایی از سوپرکلاس والد دسترسی داشته باشند که access modifier آنها یا از نوع public باشد یا protected. به بیانی دیگر، کلاس فرزند نمی‌تواند به متدها یا ویژگی‌هایی از سوپرکلاس دسترسی داشته باشد و آن‌ها را به ارث برد که از نوع private باشند.

در جاوا کلاس java.lang.Object در راس سلسله‌مراتب ارث‌بری کلاس‌ها قرار داشته و تمامی کلاس‌ها چه به صورت مستقیم و چه به صورت غیرمستقیم از این سوپرکلاس والد ارث‌بری می‌کنند. هر چه در سلسله‌مراتب ارث‌بری کلاس‌ها به پایین بیاییم (از والد به سمت فرزندها)، کلاس‌ها اختصاصی‌تر می‌شوند.

حال که با مفاهیم Super Class و Sub Class در جاوا آشنا شدیم، می‌توانیم به تشریح کلمه کلیدی و دستور super بپردازیم.

به مثال زیر که درواقع بازنویسی متد onCreate از سوپرکلاس AppCompatActivity در ساب‌کلاس MainActivity در یک پروژه اندروید می‌باشد توجه کنید:


@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
   // rest of the code
}

در بازنویسی این متد شاهد استفاده از دستور super می‌باشیم. همانطور که می‌توان حدس زد، نام این کلمه کلیدی با مفهوم super class بی‌ارتباط نیست. همانطور که در بالا نیز اشاره شد، در جاوا زمانی که کلاسی از کلاس دیگری ارث‌بری می‌کند، می‌توان متدهای غیر private کلاس والد را براساس نیازهای خودمان بازنویسی کرده و رفتار آنها را تغییر دهیم.

در پروسه بازنویسی متدهای کلاس والد در کلاس فرزند، دو رویکرد پیش‌روی برنامه‎نویسان و توسعه‌دهندگان قرار دارد.

  • بازنویسی و جایگزینی کامل متد
  • توسعه متد موجود در کلاس والد

و اینجا است که دستور super وارد عمل شده و امکان پیاده‌سازی رویکرد دوم را برای برنامه‌نویس عملی می‌سازد. در واقع در مثال فوق، با افزودن تکه کد super.onCreate(savedInstanceState); به ابتدای متد بازنویسی شده، به ماشین مجازی Dalvik می‌گوییم که کد نوشته شده توسط ما را همراه با کدهای موجود در متد onCreate سوپرکلاس AppCompatActivity به اجرا درآورد و با حذف این statement فقط و فقط کدهای نوشته شده توسط ما به اجرا درخواهد آمد.

به بیانی دیگر می‌توان گفت در صورتی که قصد داشته باشیم تا علاوه بر رفتار مورد انتظار ما از یک متد، رفتار تعیین شده از سوی کلاس والد نیز در خروجی متد ارث‌بری شده اعمال گردد، از دستور super استفاده می‌کنیم.

به منظور درک کامل کاربرد کلمه کلیدی super به مثال زیر توجه کنید.

کلاس زیر را به عنوان یک super class با قابلیت ارث‌بری از آن که دارای متدی تحت عنوان print() می‌باشد، در نظر بگیرید:

public class Superclass {
    public void print() {
        System.out.println("Printed in Superclass.");
    }
} 
 

کلاس زیر را نیز به عنوان یک sub class که از کلاس Superclass فوق ارث‌بری کرده و متد print() آن را بازنویسی می‌کند در نظر بگیرید:

public class Subclass extends Superclass {
    // overrides print in Superclass
    public void print() {
        super.print();
        System.out.println("Printed in Subclass");
    }
    public static void main(String[] args) {
        Subclass s = new Subclass();
        s.print();    
    }
}

زمانی که در کلاس فرزند، با استفاده از نمونه‌سازی، متد print را صدا می‌زنیم، در واقع متد print واقع در خود کلاس فرزند به اجرا درمی‌آید. متدی که خود یک بازنویسی از متد print موجود در کلاس والد می‌باشد. با توجه به استفاده از دستور super در ابتدای متد بازنویسی شده در کلاس فرزند، همانطور که حدس می‌زنیم خروجی اجرای کد فوق می‌بایست بدین شکل باشد:

Printed in Superclass.
Printed in Subclass

چرا که ما متد print را به صورت کامل بازنویسی نکرده و آن را با استفاده از دستور super توسعه دادیم. یعنی علاوه بر کدهای موجود در بدنه این متد در کلاس فرزند، کدهای موجود در بدنه متد مدنظر در کلاس والد نیز به اجرا درخواهند آمد.

یکی دیگر از کاربردهای super، استفاده از آن در متدهای سازنده کلاس‌های فرزند (Subclass Constructors) به منظور فراخوانی متد سازنده سوپرکلاس در متد سازنده ساب‌کلاس می‌باشد. فراخوانی سازنده کلاس والد در بدنه سازنده کلاس فرزند می‌بایست در خط اول از متد صورت گیرد.

public Subclass () {
    super(); //  invoke superclass's constructor
    // Subclass Constructor codes
}