مدیریت خطاها – Exception Handling

مدیریت خطا در جاوا- علیرضا پیر

خطاها در جاوا به دو دسته Error و Exception دسته بندی می شوند، ارور ها به خطاهایی مانند خطای کمبود حافظه یا پر شدن حافظه استک و یا امثال آن گفته می شود، در واقع ارور ها به خطاهایی گفته می شود که در یک برنامه عادی نیازی به حساس بودن روی آنها و گرفتنشان وجود ندارد. برای مثال قطعه کد زیر بعد از چند لحظه دچاتشرشر ارور Stack over flow می شود:

class StackOverflow { 
    public static void test(int i) 
    { 
        // No correct as base condition leads to 
        // non-stop recursion. 
        if (i == 0) 
            return; 
        else { 
            test(i++); 
        } 
    } 
} 
public class ErrorEg { 
  
    public static void main(String[] args) 
    { 
  
        // eg of StackOverflowError 
        StackOverflow.test(5); 
    } 
}

در مقابل Exception به شرایطی از خطا گفته می شود که یک برنامه طبیعی به احتمال زیاد باید برای آن چاره ای بی اندیشد. برای مثال خطای تقسیم بر صفر، یک exception است که برنامه نویس باید برای آن فکری بی اندیشد، برای هندل کردن این نوع خطاها، از بلاک try…catch استفاده می کنیم.

متدی که داخلِ آن Exception رخ دهد باید در تعریف خود این اعلان را داشته باشد، برای مثال قطعه کد زیر این آمادگی را دارد که خطا throw کند یا اصطلاحا خطا پرتاب کند:

	public void test() throws Exception{
		.
		.
		.
		throw new Exception("bad parameter");
		
	}

با این تعریف، به کامپایلر گفته ایم که این متد می تواند Exception پرتاب کند، بنابراین جایی که میخواهیم از متد مورد نظر استفاده کنیم (موقع فراخوانی) باید آن را در بلاک try…catch قرار دهیم.

اطلاعات مربوط به نوع exception پرتاب شده در داخل شی ای به نام Stack Trace قرار دارد، می توان با دستور printStackTrace و یا getStackTrace این شیء را دریافت یا اطلاعات آن را چاپ کرد.

یک بلاک try…catch می تواند چند بلاک catch نیز داشته باشد. برای مثال در کدِ زیر:

	try {
		...
	}catch(IOException e) {
		
	}catch(ArithmeticException d) {
		
	}

چند exception با هم بررسی می شوند، واضح است که نمی توان همزمان یک نوع از Exception و زیرکلاسهایش را در یک بلاک catch کنیم چون همیشه catch سطح پدر اجرا می شود.

بعضی از Exception ها، در زمان اجرا مشخص می شوند، مثلا خطای محاسباتی (مثل تقسیم بر صفر).

به این نوع از Exception ها unchecked Exception گفته می شود؛ کلاس هایی مثل runtime یا null pointer exception مثالی از این نوع Exception ها هستند.

این نوع از Exception ها، نیازی به کلمه کلیدی throws در تعریف تابع مربوط به خود ندارند.

در مقابل CheckedException ها، توسط کامپایلر و در زمان کامپایل بررسی می شوند و در صورت نبود بلاک try…catch و یا کلمه کلیدی throws ارور می دهند.

بلاک try…catch شامل سه بخش است، بخش try، بخش catch و بخش finally. بخش finally چه اجرای try موفقیت آمیز باشد و چه نه اجرا می شود. همچنین این بلاک می تواند catch نداشته ولی finally  داشته باشد.

در قطعه کد زیر خطوط اول بدون مشکل هستند چون از نوع unchecked هستند ولی خط آخر ارور می دهد چون از نوع checked  هست.

	void example(int x) {
		if(x==1) 
			throw new Error();
		else if(x==2)
			throw new RuntimeException();
		else if (x==3)
			throw new NullPointerException();
		else
			throw new IOException();
	}

اگر میخواهید کلاسِ Exception شخصی خودتان از نوعِ uncheck باشد، می توانید آن را فرزند RuntimeException قرار دهید.

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

	class parent{
		void f() {};
	}
	class child extends parent{
		void f() throws Exception{};
	}

از نسخه 7 جاوا به بعد، می توان چند catch که بدنه یکسانی دارند را با استفاده از عملگرِ | با هم نوشت، مثل کد زیر:

	void test() throws Exception{
		
		try {
			
		}
		catch(IOException  | ArithmeticException e) {
			
		}
	}

try with resources

از نسخه 7 جاوا، امکان try with resources به این زبان اضافه شده است. این امکان، به برنامه نویس قابلیت استفاده از ریسورس ها و باز کردنِ آنها را بدون نگرانی از بسته شدنِ آنها می دهد. یعنی دیگر نیازی نخواهد بود در try یک ریسورس را باز کنیم و در finally آن را ببنیدم، می توانیم ریسورس را داخل پرانتز مربوط به بلاک try قرار دهیم و اینکار به صورت خودکار پس از اتمام کار بلاک انجام می گیرد. قطعه کد زیر نمونه ای از این عملکرد می باشد:

		try(BufferedReader br = new BufferedReader(...)) {
			return br.readLine();	
		}