آموزش قدم به قدم جاوا – قسمت بیست و یکم

انواع خطا در برنامه‌نویسی

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

خطای زمان کامپایل (Compile-Time Error)

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

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

نکته: به خطاهای زمان کامپایل خطای سینتکس یا خطای نحوی (Syntax Error) نیز گفته می‌شود.

خطای زمان اجرا (Runtime Error)

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

به کد زیر دقت کنید:

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

به دلیل اینکه اندازه آرایه‌ها هنگام کامپایل مشخص نیست (چون ممکن است اندازه یک آرایه مثلا با عددی که کاربر به عنوان ورودی به برنامه می‌دهد تعیین شود) پس کامپایلر این مشکل را نمی‌فهمد و خطای زمان کامپایل رخ نمی‌دهد بلکه هنگام اجرای برنامه وقتی برنامه به این کد رسید یک خطای زمان اجرا رخ می‌دهد. اگر کد بالا را اجرا کنید برنامه متوقف شده و خطایی تحت عنوان ArrayIndexOutOfBoundsException رخ می‌دهد.

استثنا (Exception)

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

تمام استثناها در جاوا از کلاسی به نام Throwable مشتق می‌شوند. به شکل زیر دقت کنید:

je1

دو کلاس Error و Exception از کلاس Throwable مشتق می‌شوند اما تفاوت زیادی بین آن‌ها وجود دارد.

کلاس Error بیانگر خطاهایی است که توسط محیطی که برنامه در آن در حال اجرا است رخ می‌دهند مثل OutOfMemoryError که هرگاه که JVM به دلیل کمبود حافظه نتواند به برنامه در حال اجرا حافظه اختصاص دهد رخ می‌دهد.

Errorها از کنترل برنامه‌نویس خارج هستند و در صورت بروز یک Error برنامه‌نویس معمولا نمی‌تواند آن را مدیریت کند و البته بسیار کمتر از Exception ها رخ می‌دهند در این مطلب ما به Error ها نمی‌پردازیم.

مدیریت استثنا (Exception Handling)

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

مثال:

این متد ساده دو عدد را دریافت کرده و حاصل تقسیم آن‌ها را برمی‌گرداند. اما اگر این متد را فراخوانی کنیم و به پارامتر b مقدار 0 دهیم آنگاه یک استثنا رخ می‌دهد (با توجه به اینکه تقسیم یک عدد بر صفر امکان پذیر نیست). نام این استثنا ArithmeticException (استثنای محاسباتی) می‌باشد.

کدی که دو عدد را بر هم تقسیم می‌کند را درون یک بلاک try و catch قرار می‌دهیم:

کد داخل بلاک try اجرا می‌شود. اگر اجرای این کد باعث بروز یک ArithmeticException شود آنگاه به جای اینکه برنامه متوقف شود کدهای داخل بلاک catch اجرا خواهد شد.

همانطور که می‌بینید برای catch یک پارامتر تعریف شده که e نام دارد و از نوع ArithmeticException است و به همین دلیل این بلاک catch فقط ArithmeticException ها را مدیریت می‌کند و اگر استثنایی از نوع دیگر رخ دهد دیگر این بلاک catch به کار نمی‌آید.

دو خط کد داخل بلاک catch ای که نوشتیم وجود دارد. خط اول عبارت Khataye Mohasebati را چاپ می‌کند. خط دوم از پارامتر e استفاده کرده و متد getMessage() را از آن فراخوانی می‌کند. متد getMessage پیغام استثنای رخ داده را برمی‌گرداند.

نکته دیگر اینکه اگر ArithmeticException رخ دهد دیگر حاصل تقسیم دو پارامتر برگردانده نمی‌شود اما با توجه به اینکه این متد باید در هر صورت یک مقدار int برگرداند پس اگر ArithmeticException رخ داد بعد از چاپ پیغام مناسب، عدد -1 را به عنوان داده برگشتی متد تعریف کردیم.

مدیریت چند استثنا با catchهای متعدد

به متد زیر دقت کنید:

پارامتر اول این متد یک آرایه از جنس int است. پارامتر دوم و سوم آن دو اندیس دلخواه از آرایه داده شده است. کار این متد این است که اعدادی که در خانه‌هایی از آرایه که اندیس‌های آن داده شده است را بر هم تقسیم کرده و نتیجه را برگرداند.

دو استثنا ممکن است در این متد رخ دهد:

  1. اگر اندیس‌های داده شده از تعداد خانه‌های آرایه بیشتر باشد استثنای ArrayIndexOutOfBoundsException رخ خواهد داد.
  2. اگر یکی از اعداد موجود در آرایه 0 باشد و در متغیر number2 قرار گیرد آنگاه تقسیم بر صفر انجام می‌شود و استثنای ArithmeticException رخ خواهد داد.

کدهایی که ممکن است یکی از این دو استثنا را تولید کنند را داخل بلاک try قرار داده و با دو بلاک catch هر دو استثنا را مدیریت می‌کنیم:

همانطور که می‌بینید برای هر استثنا پیغام مناسب چاپ شده و اگر هر یک از این دو استثنا رخ دهد عدد -1 به عنوان مقدار بازگشتی این متد برگردانده می‌شود.

نکته: گاهی اوقات می‌خواهیم هر استثنایی که در یک کد خاص رخ داد را به یک شکل مدیریت کنیم و برایمان اهمیتی ندارد که چه استثنایی رخ داده است. به کد زیر دقت کنید:

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

بلاک finally

به کد زیر دقت کنید:

کدهای داخل بلاک finally در هر صورت اجرا می‌شوند. یعنی اگر استثنایی رخ بدهد یا اگر هیچگونه استثنایی هم رخ ندهد باز هم کدهای داخل بلاک finally اجرا خواهند شد.

شاید این بلاک در این کد بی‌فایده به نظر برسد اما در قسمت‌های بعدی آموزش که به مبحث ورودی و خروجی بپردازیم می‌بینید که بلاک finally نقش مهمی را ایفا می‌کند.

تولید یک استثنا

تا به حال استثناهایی که توسط JVM تولید می‌شد را مدیریت می‌کردیم اما می‌توانیم خودمان یک استثنا را تولید کنیم. برای این کار ابتدا باید یک شی از کلاس استثنای مورد نظر ایجاد کنیم و با استفاده از دستور throw آن استثنا را اصطلاحا پرتاب (throw) کنیم تا آن استثنا رخ دهد.

مثال:

در بلاک try یک شی از کلاس ArithmeticException ایجاد کردیم و بلافاصله آن را با throw پرتاب کردیم تا استثنا رخ دهد.

شاید این سوال پیش بیاید که چرا باید خودمان باعث بروز یک استثنا شویم؟ پاسخ این است که ما به عنوان برنامه‌نویس معمولا استثناهایی که خودمان نوشتیم را به این صورت تولید می‌کنیم که در ادامه با نحوه ساخت یک استثنا آشنا خواهید شد.

انواع Exceptionها

دو نوع کلی استثنا در جاوا داریم:

  1. استثناهایی که در زمان کامپایل بررسی می‌شوند (Checked Exceptions): این استثناها مستقیما از کلاس Exception ارث‌بری دارند.
  2. استثناهایی که در زمان کامپایل بررسی نمی‌شوند (Unchecked Exceptions): این استثناها از کلاس RuntimeException ارث‌بری دارند که البته این کلاس خود از کلاس Exception مشتق می‌شود. استثناهای ArithmeticException و ArrayIndexOutOfBoundsException که با آن‌ها آشنا شدید از این نوع هستند.

در ادامه با تفاوت این دو نوع استثنا آشنا می‌شوید.

کلمه کلیدی throws

در برخی مواقع اگر یک متد حاوی کدی باشد که باعث بروز یک استثنا شود آنگاه یا در همان متد باید آن استثنا را مدیریت کنیم و یا باید با کلمه throws استثناهایی را که ممکن است در آن متد رخ دهد را مشخص کنیم.

برای استثناهای Unchecked مثل استثناهایی که تا الان دیدیم نیازی به این کار نیست.

مثال: متدی به نام readFile داریم که محتویات یک فایل را می‌خواند:

برای جلوگیری از پیچیدگی کدهای داخل این متد را ننوشتیم. همانطور که می‌بینید در قسمت امضای متد عبارت throws IOException نوشته شده است. IOException یک استثنا از نوع Checked است که هنگام کار با فایل‌ها ممکن است رخ دهد (در قسمت‌های بعدی با این مبحث آشنا خواهید شد)

در واقع ما یا باید IOException را درون همین متد مدیریت کنیم و یا به صورتی که دیدید اعلام کنیم که این متد ممکن است یک IOException بروز دهد که در این صورت کدی که این متد را فراخوانی می‌کند باید استثنای ذکر شده را مدیریت کند.

طراحی یک استثنا

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

برای این کار باید کلاسی طراحی کنیم که از کلاس RuntimeException و یا Exception ارث‌بری داشته باشد و انتخاب یکی از این دو کلاس بستگی به نوع استثنایی که قرار است بسازیم دارد (Checked یا Unchecked)

ما کلاسی به نام InvalidEmailException خواهیم نوشت که از کلاس Exception ارث‌بری دارد:

یک سازنده بدون پارامتر برای این کلاس نوشتیم و در این سازنده، یکی از سازنده‌های کلاس پدر یا همان Exception را فراخوانی کردیم. این سازنده یک پارامتر از نوع String دریافت می‌کند که بیانگر پیام استثنا می‌باشد. این پیام را می‌توان با فراخوانی متد getMessage از اشیا ساخته شده از این کلاس دریافت کرد.

این متد دو پارامتر می‌گیرد که اولی آدرس ایمیل و دومی متن ایمیل برای ارسال است. در شرطی که در ابتدای متد می‌بینید، وجود کاراکتر @ در آدرس ایمیل چک شده و اگر وجود نداشت یک استثنای InvalidEmailException رخ می‌دهد. چون این استثنا را از نوع Checked طراحی کردیم پس این متد باید با استفاده از عبارت throws اعلام کند که ممکن است فراخوانی این متد باعث بروز یک InvalidEmailException شود و بنابراین کدی که این متد را فراخوانی کند باید این استثنا را مدیریت کند.

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

مصطفی نصیری

دانشجوی نرم افزار هستم و علاقه شدیدی به برنامه نویسی مخصوصا با زبان جاوا دارم! در حال حاضر تمرکزم روی اندرویده. دوست دارم چیزایی که یاد میگیرم رو با بقیه به اشتراک بگذارم :)

همچنین ممکن است دوست داشته باشید ...

۶ واکنش

  1. testgmail گفت:

    سلام-سپاس گذارم-عالی بود

  2. testgmail گفت:

    لینک زیر نیز جالب مطلب رو توضیح داده البته در تکمیل حرف های شما
    https://www.javabrahman.com/corejava/understanding-exception-hierarchy-java-tutorial

  3. Masi گفت:

    InterruptedException دقیقا چیه؟

  4. ADEL گفت:

    سلام
    چرا من این کد رو میزنم واسم اجرا نمیشه
    بقیش درسته ولی نمی دونم چرا فقط زیر یک دستور خط قرمز میکشه
    مثلا اینو می نویسم زیرش خط قرمز میشه و هرچی میکنم برنامه اجرا نمیشه برای رفع خطای این کار باید چه کار کنم ; ۲ = / i

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *