شنبه ۲۹ دي ۱۴۰۳
Tut24 آموزش برنامه نویسی و مجله تخصصی فناوری ورود/عضویت

آموزش جاوا - استثناها

استثناء (یا رویداد استثنایی) مشکلی است که در حین اجرای یک برنامه به وجود می آید. هنگامی که یک استثناء رخ می دهد، جریان عادی برنامه متوقف می شود و برنامه / برنامه کاربردی به طور نامعمولی خاتمه می یابد که توصیه نمی شود، بنابراین، این استثنائات باید مورد بررسی قرار گیرند.

یک استثناء ممکن است به دلایل مختلفی رخ دهد. در زیر چندین سناریو وجود دارد که استثناء رخ می دهد.

  • کاربر داده های نامعتبری وارد کرده است.

  • فایلی که باید باز شود، پیدا نمی شود.

  • اتصال شبکه در حین ارتباطات گم شده است یا JVM از حافظه خارج شده است.

بعضی از این استثنائات به دلیل خطاهای کاربر، بعضی به دلیل خطاهای برنامه نویس و بعضی به دلیل منابع فیزیکی که به هر نحوی خراب شده اند، رخ می دهند.

بر اساس این، سه دسته از استثنائات وجود دارند. شما باید آنها را درک کنید تا بدانید چگونه مدیریت استثناء در جاوا کار می کند.

  • استثنائات بررسی شده (Checked exceptions) - استثناء بررسی شده استثناء ای است که در زمان کامپایل توسط کامپایلر بررسی می شود، این استثنائات نیز به عنوان استثنائات زمان کامپایل نیز نامیده می شوند. این استثنائات نمی توانند به سادگی نادیده گرفته شوند، برنامه نویس باید این استثناء ها را مدیریت کند.

برای مثال، اگر در برنامه خود از کلاس FileReader برای خواندن داده ها از یک فایل استفاده کنید، اگر فایل مشخص شده در سازنده آن وجود نداشته باشد، آنگاه یک FileNotFoundException رخ می دهد و کامپایلر برنامه نویس را به مدیریت استثناء فرا می خواند.

مثال


import java.io.File;
import java.io.FileReader;

public class FilenotFound_Demo {

   public static void main(String args[]) {		
      File file = new File("E://file.txt");
      FileReader fr = new FileReader(file); 
   }
}

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

خروجی


C:\>javac FilenotFound_Demo.java
FilenotFound_Demo.java:8: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
      FileReader fr = new FileReader(file);
                      ^
1 error

توجه − از آنجا که متدهای read() و close() کلاس FileReader استثناء IOException را پرتاب می کنند، می توانید مشاهده کنید که کامپایلر خواستار مدیریت استثناء IOException در کنار FileNotFoundException می شود.

  • استثنائات بررسی نشده (Unchecked exceptions) − استثناء بررسی نشده استثناء ای است که در زمان اجرا رخ می دهد. اینها نیز به عنوان استثنائات زمان اجرا نیز نامیده می شوند. این استثنائات شامل خطاهای برنامه نویسی مانند خطاهای منطقی یا استفاده نادرست از یک API هستند. در زمان کامپایل از استثنائات زمان اجرا صرف نظر می شود.

به عنوان مثال، اگر در برنامه خود یک آرایه با اندازه 5 تعریف کرده و سعی کنید به عنصر ششم آرایه دسترسی پیدا کنید، یک استثناء ArrayIndexOutOfBoundsExceptionexception رخ می دهد.

مثال


public class Unchecked_Demo {
   
   public static void main(String args[]) {
      int num[] = {1, 2, 3, 4};
      System.out.println(num[5]);
   }
}

اگر برنامه بالا را کامپایل و اجرا کنید، استثناء زیر را دریافت خواهید کرد.

خروجی


Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
	at Exceptions.Unchecked_Demo.main(Unchecked_Demo.java:8)
  • خطاها (Errors) − اینها به هیچ وجه استثناء نیستند، بلکه مشکلاتی هستند که خارج از کنترل کاربر یا برنامه نویس به وجود می آیند. خطاها به طور معمول در کد شما نادیده گرفته می شوند زیرا به ندرت می توانید در مورد یک خطا هیچ کاری انجام دهید. به عنوان مثال، اگر یک سرریز استک رخ دهد، یک خطا پدید می آید. همچنین در زمان کامپایل نادیده گرفته می شوند.

سلسله مراتب استثناء

همه کلاسهای استثناء زیر نوعی از کلاس java.lang.Exception هستند. کلاس استثناء یک زیر کلاس از کلاس Throwable است. علاوه بر کلاس استثناء، یک زیر کلاس دیگر به نام Error نیز وجود دارد که از کلاس Throwable ارث بری کرده است.

خطاها شرایط غیرطبیعی هستند که در صورت بروز خطاهای شدید رخ می دهند و توسط برنامه های جاوا مدیریت نمی شوند. خطاها برای نشان دادن خطاهای تولید شده توسط محیط اجرایی ایجاد می شوند. به عنوان مثال: حافظه JVM پر است. به طور معمول، برنامه ها قادر به بازیابی از خطاها نیستند.

کلاس استثناء دو زیرکلاس اصلی دارد: کلاس IOException و کلاس RuntimeException.

Exceptions1

در زیر لیستی از رایج ترین استثنائات چک شده و بدون چک استثنائات درونی جاوا آورده شده است.

متدهای استثناء ها

در زیر لیستی از متدهای مهم موجود در کلاس Throwable آورده شده است.

ردیف متد و توضیحات
1

public String getMessage()

یک پیام تفصیلی درباره استثناء را که رخ داده است، باز می گرداند. این پیام در کانستراکتور Throwable مقداردهی اولیه می شود.

2

public Throwable getCause()

علت استثناء را که توسط یک شیء Throwable نشان داده می شود، باز می گرداند.

3

public String toString()

نام کلاس را با نتیجه getMessage() یکجا باز می گرداند.

4

public void printStackTrace()

نتیجه toString() را به همراه ردیابی استک به System.err، جریان خروجی خطا، چاپ می کند.

5

public StackTraceElement [] getStackTrace()

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

6

public Throwable fillInStackTrace()

ردیابی استک این شی Throwable را با ردیابی استک کنونی پر می کند و به اطلاعات قبلی در ردیابی استک اضافه می کند.

دریافت استثناء ها

یک متد با استفاده از ترکیب کلمات کلیدی try و catch استثناء را دریافت می کند. یک بلوک try/catch در اطراف کد قرار می گیرد که ممکن است استثناء تولید کند. کد درون یک بلوک try/catch به عنوان کد محافظت شده شناخته می شود و نحوی برای استفاده از try/catch به صورت زیر است:

نحو


try {
   // Protected code
} catch (ExceptionName e1) {
   // Catch block
}

کدی که در معرض استثناء قرار دارد، در بلوک try قرار می گیرد. هنگامی که یک استثناء رخ می دهد، آن استثناء توسط بلوک catch مرتبط با آن کنترل می شود. هر بلوک try باید بلافاصله توسط یک بلوک catch یا finally دنبال شود.

یک بیانیه catch شامل اعلام نوع استثناء است که شما سعی می کنید آن را دریافت کنید. اگر یک استثناء در کد محافظت شده رخ دهد، بلوک (ها) catch که بعد از بلوک try قرار دارد بررسی می شود. اگر نوع استثناء رخ داده در یک بلوک catch لیست شده باشد، استثناء به بلوک catch منتقل می شود تقریباً مانند یک آرگومان به یک پارامتر روش منتقل می شود.

مثال

در زیر یک آرایه با 2 عنصر اعلام شده است. سپس کد سعی می کند به عنصر 3 ام آرایه دسترسی پیدا کند که یک استثناء ایجاد می کند.


// File Name : ExcepTest.java
import java.io.*;

public class ExcepTest {

   public static void main(String args[]) {
      try {
         int a[] = new int[2];
         System.out.println("Access element three :" + a[3]);
      } catch (ArrayIndexOutOfBoundsException e) {
         System.out.println("Exception thrown  :" + e);
      }
      System.out.println("Out of the block");
   }
}

این عمل، نتیجه زیر را تولید خواهد کرد −

خروجی


Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 3
Out of the block

بلوک های چندگانه catch

یک بلوک try می تواند توسط چند بلوک catch دنبال شود. نحو برای بلوک های چندگانه catch به شکل زیر است −

نحو


try {
   // Protected code
} catch (ExceptionType1 e1) {
   // Catch block
} catch (ExceptionType2 e2) {
   // Catch block
} catch (ExceptionType3 e3) {
   // Catch block
}

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

مثال

قطعه کد زیر نشان می‌دهد که چگونه از بیانیه‌های چندگانه try/catch استفاده کنیم.


try {
   file = new FileInputStream(fileName);
   x = (byte) file.read();
} catch (IOException i) {
   i.printStackTrace();
   return -1;
} catch (FileNotFoundException f) // Not valid! {
   f.printStackTrace();
   return -1;
}

مدیریت چند نوع استثنا (Catching Multiple Type of Exceptions)

از جاوا 7 به بعد، شما می‌توانید بیش از یک استثنا را با استفاده از یک بلوک catch واحد مدیریت کنید. این ویژگی کد را ساده‌تر می‌کند. اینجا نحوه انجام آن را مشاهده می‌کنید −


catch (IOException|FileNotFoundException ex) {
   logger.log(ex);
   throw ex;

کلمات کلیدی Throws/Throw

اگر یک متد با استثناهای چک‌شده (checked exception) برخورد نکند، باید از کلمه کلیدی throws استفاده کند. کلمه کلیدی throws در انتهای امضای متد ظاهر می‌شود.

می‌توانید یک استثنا را، یا یک استثنای تازه ساخته شده یا یک استثنایی که تازه مدیریت کرده‌اید، با استفاده از کلمه کلیدی throw پرتاب کنید.

سعی کنید تفاوت بین کلمات کلیدی throws و throw را درک کنید، throws برای به تعویق انداختن مدیریت استثناهای چک‌شده استفاده می‌شود و throw برای فراخوانی یک استثنا به طور صریح استفاده می‌شود.

متد زیر اعلام می‌کند که یک استثنای RemoteException را پرتاب می‌کند −

مثال


import java.io.*;
public class className {

   public void deposit(double amount) throws RemoteException {
      // Method implementation
      throw new RemoteException();
   }
   // Remainder of class definition
}

یک متد می‌تواند اعلام کند که چندین استثنا را پرتاب می‌کند، در این صورت استثناها در یک لیست با استفاده از کاما اعلام می‌شوند. به عنوان مثال، متد زیر اعلام می‌کند که یک استثنای RemoteException و یک استثنای InsufficientFundsException را پرتاب می‌کند −

Example


import java.io.*;
public class className {

   public void withdraw(double amount) throws RemoteException, 
      InsufficientFundsException {
      // Method implementation
   }
   // Remainder of class definition
}

بلوک finally

بلوک finally پس از بلوک try یا بلوک catch قرار می‌گیرد. بلوک finally همیشه اجرا می‌شود، بدون توجه به وقوع استثنا.

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

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

ساختار


try {
   // Protected code
} catch (ExceptionType1 e1) {
   // Catch block
} catch (ExceptionType2 e2) {
   // Catch block
} catch (ExceptionType3 e3) {
   // Catch block
}finally {
   // The finally block always executes.
}

مثال

.


public class ExcepTest {

   public static void main(String args[]) {
      int a[] = new int[2];
      try {
         System.out.println("Access element three :" + a[3]);
      } catch (ArrayIndexOutOfBoundsException e) {
         System.out.println("Exception thrown  :" + e);
      }finally {
         a[0] = 6;
         System.out.println("First element value: " + a[0]);
         System.out.println("The finally statement is executed");
      }
   }
}

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

خروجی


Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 3
First element value: 6
The finally statement is executed

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

  • یک بلاک catch نمی‌تواند بدون یک بلاک try وجود داشته باشد.

  • اجباری نیست که همیشه بلاک finally وجود داشته باشد هنگامی که یک بلاک try/catch وجود دارد.

  • بلاک try نمی‌تواند بدون بلاک catch یا بلاک finally وجود داشته باشد.

  • هیچ کدی نمی‌تواند در بین بلاک‌های try، catch و finally وجود داشته باشد.

استفاده از منابع همراه با try

در کلیت، هنگام استفاده از منابع مانند استریم‌ها، اتصالات و غیره، باید آن‌ها را به صورت صریح با استفاده از بلاک finally ببندیم. در برنامه زیر، ما داده‌ها را از یک فایل با استفاده از FileReader می‌خوانیم و آن را با استفاده از بلاک finally می‌بندیم.

مثال


import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class ReadData_Demo {

   public static void main(String args[]) {
      FileReader fr = null;		
      try {
         File file = new File("file.txt");
         fr = new FileReader(file); char [] a = new char[50];
         fr.read(a);   // reads the content to the array
         for(char c : a)
         System.out.print(c);   // prints the characters one by one
      } catch (IOException e) {
         e.printStackTrace();
      }finally {
         try {
            fr.close();
         } catch (IOException ex) {		
            ex.printStackTrace();
         }
      }
   }
}

try-with-resources، همچنین به عنوان مدیریت خودکار منابع شناخته می‌شود، یک مکانیزم جدید برای مدیریت استثناء است که در جاوا ۷ معرفی شد و به طور خودکار منابع استفاده شده در بلاک try-catch را بسته می‌کند.

برای استفاده از این بیانیه، شما فقط باید منابع مورد نیاز را در پرانتز مشخص کنید و منبع ساخته شده به طور خودکار در انتهای بلاک بسته خواهد شد. دستورات try-with-resources به صورت زیر است:

نحو


try(FileReader fr = new FileReader("file path")) {
   // use the resource
   } catch () {
      // body of catch 
   }
}

در ادامه، برنامه‌ای که با استفاده از بیانیه try-with-resources داده‌ها را از یک فایل می‌خواند، آمده است.

مثال


import java.io.FileReader;
import java.io.IOException;

public class Try_withDemo {

   public static void main(String args[]) {
      try(FileReader fr = new FileReader("E://file.txt")) {
         char [] a = new char[50];
         fr.read(a);   // reads the contentto the array
         for(char c : a)
         System.out.print(c);   // prints the characters one by one
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
}

در هنگام کار با بیانیه try-with-resources، نکات زیر را در نظر داشته باشید:

  • برای استفاده از یک کلاس با بیانیه try-with-resources، باید رابط AutoCloseable را پیاده‌سازی کند و متد close() آن به صورت خودکار در زمان اجرا فراخوانی می‌شود.

  • می‌توانید بیش از یک کلاس را در بیانیه try-with-resources معرفی کنید.

  • در صورتی که چندین کلاس را در بلوک try بیانیه try-with-resources معرفی کنید، این کلاس‌ها به ترتیب معکوس بسته می‌شوند.

  • به استثنای تعریف منابع در داخل پرانتز، همه چیز مانند بلوک try/catch عادی یک بلوک try است.

  • منبع تعریف شده در بلوک try قبل از شروع بلوک try برای نمونه‌سازی است.

  • منبع تعریف شده در بلوک try به صورت ضمنی به عنوان نهایی (final) تعریف می‌شود.

استثنائات تعریف شده توسط کاربر

می‌توانید استثنائات خود را در جاوا ایجاد کنید. در هنگام نوشتن کلاس‌های استثنائات خود، نکات زیر را در نظر داشته باشید:

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

  • اگر می‌خواهید یک استثناء بررسی شده بنویسید که به صورت خودکار توسط قاعده Handle یا Declare بررسی شود، باید کلاس Exception را گسترش دهید.

  • اگر می‌خواهید یک استثناء در زمان اجرا بنویسید، باید کلاس RuntimeException را گسترش دهید.

می‌توانیم کلاس استثناء خود را به صورت زیر تعریف کنیم:


class MyException extends Exception {
}

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

نمونه


// File Name InsufficientFundsException.java
import java.io.*;

public class InsufficientFundsException extends Exception {
   private double amount;
   
   public InsufficientFundsException(double amount) {
      this.amount = amount;
   }
   
   public double getAmount() {
      return amount;
   }
}

برای نمایش استفاده از استثناء تعریف شده توسط کاربر، کلاس CheckingAccount زیر شامل یک متد withdraw() است که یک استثناء InsufficientFundsException پرتاب می‌کند.


// File Name CheckingAccount.java
import java.io.*;

public class CheckingAccount {
   private double balance;
   private int number;
   
   public CheckingAccount(int number) {
      this.number = number;
   }
   
   public void deposit(double amount) {
      balance += amount;
   }
   
   public void withdraw(double amount) throws InsufficientFundsException {
      if(amount <= balance) {
         balance -= amount;
      }else {
         double needs = amount - balance;
         throw new InsufficientFundsException(needs);
      }
   }
   
   public double getBalance() {
      return balance;
   }
   
   public int getNumber() {
      return number;
   }
}

برنامه BankDemo زیر نمونه‌ای از فراخوانی متدهای deposit() و withdraw() از کلاس CheckingAccount را نشان می‌دهد.


// File Name BankDemo.java
public class BankDemo {

   public static void main(String [] args) {
      CheckingAccount c = new CheckingAccount(101);
      System.out.println("Depositing $500...");
      c.deposit(500.00);
      
      try {
         System.out.println("\nWithdrawing $100...");
         c.withdraw(100.00);
         System.out.println("\nWithdrawing $600...");
         c.withdraw(600.00);
      } catch (InsufficientFundsException e) {
         System.out.println("Sorry, but you are short $" + e.getAmount());
         e.printStackTrace();
      }
   }
}

تمامی سه فایل فوق را کامپایل کرده و برنامه BankDemo را اجرا کنید. این کار نتیجه زیر را تولید خواهد کرد:

خروجی


Depositing $500...

Withdrawing $100...

Withdrawing $600...
Sorry, but you are short $200.0
InsufficientFundsException
         at CheckingAccount.withdraw(CheckingAccount.java:25)
         at BankDemo.main(BankDemo.java:13)

استثنائات متداول

در جاوا، امکان تعریف دو دسته استثنائات و خطاها وجود دارد.

  • استثنائات JVM − این استثنائات/خطاها به صورت انحصاری یا منطقی توسط JVM پرتاب می‌شوند. مثال‌ها: NullPointerException، ArrayIndexOutOfBoundsException، ClassCastException.

  • استثنائات برنامه‌ای − این استثنائات به صورت صریح توسط برنامه یا برنامه‌نویسان API پرتاب می‌شوند. مثال‌ها: IllegalArgumentException، IllegalStateException.