Java Exception Handling | දෝෂ නිරාකරණය SC Guide

Java Exception Handling | දෝෂ නිරාකරණය SC Guide

ආයුබෝවන් හැමෝටම!

අපි හැමෝම සොෆ්ට්වෙයාර් කරනකොට, හරි, දකිනකොට, සමහර වෙලාවට වැඩේ අනාථ වෙනවා නේද? Click කරාම වැඩ කරන්නේ නෑ, එහෙම නැත්තම් එක පාරටම 'Error!' කියලා එනවා. අපිට පිස්සු හැදෙනවා. එහෙම නැත්තම් අපි හදපු program එක user කෙනෙක් use කරනකොට, හිතපු නැති විදියට data දීලා වැඩේ අවුල් කරනවා. මේ වගේ අවස්ථා Java වලදී Exception Handling වලින් කොහොමද handle කරන්නේ කියලා අද අපි කතා කරමු.

හිතන්නකෝ, ඔයා බස් එකේ යනවා. එක පාරටම බස් එක කැඩිලා පාර මැද නතර වෙනවා. බස් එකේ normal flow එක කැඩිලා නේද? අන්න ඒ වගේ තමයි program එකක normal flow එක කැඩෙනකොට Exception එකක් කියන්නේ. මේවා හරියට handle නොකළොත් program එක එකපාරටම Crash වෙන්න පුළුවන්. ඒ කියන්නේ, "ප්‍රශ්නයක් ආවා, මම ඉවරයි!" කියලා වැඩේ නවතිනවා. ඒක user කෙනෙක්ට නම් කිසිසේත්ම හොඳ අත්දැකීමක් නෙවෙයි. ඒ නිසා, මේ Exception Handling කියන concept එක හොඳට තේරුම් ගන්න එක ඔයාට පට්ට වැදගත් වෙනවා!

Exceptions කියන්නේ මොනාද?

සරලවම කිව්වොත්, Exception එකක් කියන්නේ program එක run වෙනකොට (runtime) සිදුවන අනපේක්ෂිත සිදුවීමක් (an unexpected event) හෝ දෝෂයක් (error). මේක program එකේ සාමාන්‍ය ක්‍රියාකාරිත්වය (normal flow) කඩාකප්පල් කරනවා. Java වලදී මේ Exception කියන එක object එකක් විදියට තමයි නිරූපණය වෙන්නේ. මේවා java.lang.Throwable කියන class එකෙන් extend වෙනවා.

දැන් අපි පොඩි උදාහරණ කීපයක් බලමු:

  • ArithmeticException: අපි බිංදුවෙන් බෙදන්න හැදුවොත් එන Exception එක. (int x = 10 / 0;)
  • NullPointerException: Reference කරපු object එක null වුණාම එන Exception එක. (String s = null; s.length();)
  • ArrayIndexOutOfBoundsException: Array එකක size එකෙන් එහාට index කරන්න හැදුවොත් එන Exception එක. (int[] arr = new int[5]; arr[10] = 5;)
  • FileNotFoundException: File එකක් කියවන්න හරි ලියන්න හරි හදනකොට, ඒ File එක නැතිනම් එන Exception එක. (new FileInputStream("nonExistentFile.txt");)

මේවා තමයි පොදුවේ දකින්න ලැබෙන Exception වර්ග. මේවා නිසා අපේ program එක අතරමග නතර වෙන්න පුළුවන්. ඒක වළක්වා ගන්න තමයි අපි Exception Handling techniques පාවිච්චි කරන්නේ.

The try-catch-finally Block එක

Java වලදී Exception Handling කරන්න තියෙන ප්‍රධානම ක්‍රමය තමයි try-catch-finally block එක. මේක හරියට ආරක්ෂක වැටක් වගේ. වැඩේ අවුල් වෙන්න පුළුවන් තැනකදී අපි මේ block එක පාවිච්චි කරනවා.

  • try block: මේක ඇතුලේ තමයි Exception එකක් එන්න පුළුවන් කියලා ඔයා හිතන code එක තියන්නේ. මේකේදී code එක සාමාන්‍ය විදියට execute වෙන්න බලනවා.
  • catch block: try block එක ඇතුලේ Exception එකක් ආවොත්, ඒක අල්ලගන්න (catch) තමයි මේක පාවිච්චි කරන්නේ. ඔයාට ඕන නම්, ආපු Exception එක handle කරන්න (deal with) පුළුවන් code එක මේක ඇතුලේ ලියන්න පුළුවන්.
  • finally block: මේ block එක ඇතුලේ තියෙන code එක try block එකේ Exception එකක් ආවත්, නැතත්, හැම වෙලාවෙම execute වෙනවා. මේක ගොඩක් වෙලාවට resources (file streams, database connections) වහන්න වගේ දේවල් වලට පාවිච්චි කරනවා. මොකද Exception එකක් ආවත්, නැතත්, resources ටික නිවැරදිව වහන්න ඕනේ නිසා.

දැන් අපි පොඩි උදාහරණයක් බලමු.

public class SimpleExceptionHandling {
    public static void main(String[] args) {
        System.out.println("වැඩසටහන ආරම්භ වේ...");

        try {
            // මේක ඇතුලේ තමයි Exception එකක් එන්න පුළුවන් code එක තියෙන්නේ
            int numerator = 10;
            int denominator = 0; // මෙතන 0 නිසා Exception එකක් එනවා
            int result = numerator / denominator; // ArithmeticException එකක් එනවා
            System.out.println("Result: " + result); // මේක execute වෙන්නේ නෑ Exception එකක් ආවොත්

        } catch (ArithmeticException e) {
            // ArithmeticException එකක් ආවොත් මේ block එක execute වෙනවා
            System.err.println("දෝෂයක් සිදුවී ඇත: බිංදුවෙන් බෙදිය නොහැක!");
            System.err.println("Error Message: " + e.getMessage()); // Exception එකේ message එක ගන්න පුළුවන්
            // e.printStackTrace(); // Debugging වලට මේක ගොඩක් වැදගත්
        } catch (Exception e) {
            // වෙනත් ඕනෑම Exception එකක් අල්ලගන්න මේ generic catch block එක පාවිච්චි කරන්න පුළුවන්
            System.err.println("අනපේක්ෂිත දෝෂයක් සිදුවී ඇත: " + e.getMessage());
        } finally {
            // මේ කොටස හැමවිටම ක්‍රියාත්මක වේ. Exception එකක් ආවත්, නැතත්
            System.out.println("මෙම කොටස හැමවිටම ක්‍රියාත්මක වේ.");
            // Database connection වහනවා වගේ දේවල් මෙතන කරන්න පුළුවන්
        }

        System.out.println("වැඩසටහන සාර්ථකව අවසන් විය.");
    }
}

මේ code එකේදී, try block එක ඇතුලේ 10 / 0 කියන operation එක කරනකොට ArithmeticException එකක් එනවා. එතකොට try block එකේ ඉතුරු code එක execute වෙන්නේ නැතුව, control එක අදාළ catch (ArithmeticException e) block එකට යනවා. ඒක ඇතුලේ තියෙන code එක execute වෙනවා. ඊට පස්සේ, finally block එක execute වෙලා, program එක සාමාන්‍ය විදියට ඉතුරු code එක run කරගෙන යනවා. මේකෙන් program එක crash වෙන එක වළක්වනවා.

ඔබට එක try block එකකට catch block කිහිපයක් එකතු කරන්න පුළුවන්. ඒකෙන් විවිධ Exception වර්ග විවිධ විදියට handle කරන්න පුළුවන්. හැබැයි මතක තියාගන්න, වඩාත් specific (විශේෂිත) Exception එක මුලින්ම catch කරන්න ඕනේ. Generic (සාමාන්‍ය) Exception එක පස්සේ catch කරන්න ඕනේ. (උදාහරණයක් විදියට, ArithmeticException එක Exception එකට කලින් catch කරන්න ඕනේ.)

Checked vs. Unchecked Exceptions

Java වලදී Exceptions ප්‍රධාන වර්ග දෙකකට බෙදනවා:

Checked Exceptions

මේවා තමයි Java compiler එකෙන් ඔයාව "හොඳට බලාගන්න" උත්සාහ කරන Exceptions. ඒ කියන්නේ, මේ වගේ Exception එකක් එන්න පුළුවන් කියලා program එකේ compiler එකට තේරෙනවා නම්, ඔයාට ඒක කනිෂානට් කරන්න වෙනවා. ඒ කියන්නේ, එක්කෝ ඒක try-catch block එකකින් handle කරන්න ඕනේ, එහෙම නැත්තම් ඒක අදාළ method එකේ signature එකේ throws keyword එක පාවිච්චි කරලා "මම මේක handle කරන්නේ නෑ, මේ method එක call කරන කෙනා handle කරගනියි!" කියලා declare කරන්න ඕනේ.

උදාහරණ: IOException, SQLException, ClassNotFoundException.

FileNotFoundException කියන්නේ IOException එකේ subclass එකක්. හිතන්න ඔයා File එකකින් data කියවනවා. compiler එක දන්නවා ඒ file එක නැති වෙන්න පුළුවන් කියලා. එතකොට compiler එක කියනවා "මචං, මේක හැන්ඩල් කරපන් නැත්නම් මම compile වෙන්නේ නෑ" කියලා.

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

public class CheckedExceptionDemo {
    public static void main(String[] args) {
        FileReader reader = null;
        try {
            reader = new FileReader("example.txt"); // මේක FileNotFoundException (IOException) එකක් විසි කරන්න පුළුවන්
            int i = reader.read();
            System.out.println("File read successfully.");
        } catch (IOException e) {
            System.err.println("ගොනුව සොයා ගැනීමට හෝ කියවීමට නොහැක: " + e.getMessage());
        } finally {
            if (reader != null) {
                try {
                    reader.close(); // close() method එකත් IOException එකක් විසි කරන්න පුළුවන්
                } catch (IOException e) {
                    System.err.println("ගොනුව වැසීමේදී දෝෂයක්: " + e.getMessage());
                }
            }
        }
    }
}

මේ code එක compile කරන්න නම්, ඔයාට IOException එක try-catch කරන්නම වෙනවා. නැත්නම් compiler error එකක් එනවා.

Unchecked Exceptions (Runtime Exceptions)

මේවා RuntimeException class එකෙන් extend වෙන Exceptions. මේවා compiler එකෙන් handle කරන්න කියලා බල කරන්නේ නෑ. ඒ කියන්නේ, මේ වගේ Exception එකක් එන්න පුළුවන් කියලා compiler එකට තේරෙන්නේ නෑ. මේවා සාමාන්‍යයෙන් program එකේ logic error එකක් නිසා හරි, program එකේ වැරදි භාවිතයක් (bad programming practice) නිසා හරි ඇති වෙනවා.

උදාහරණ: NullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException.

මේවා නම් මාර නිහඬයි. Compiler එක මුකුත් කියන්නේ නෑ, හැබැයි program එක run වෙනකොට එක පාරටම "බොක්කෙන් අදිනවා". ඒ කියන්නේ, program එක crash වෙනවා.

public class UncheckedExceptionDemo {
    public static void main(String[] args) {
        String name = null;
        // System.out.println(name.length()); // මෙතන NullPointerException එකක් එනවා
                                            // Compiler එකෙන් මේක handle කරන්න කියන්නේ නෑ.

        // හැබැයි අපිට ඕන නම් handle කරන්න පුළුවන්
        try {
            System.out.println(name.length());
        } catch (NullPointerException e) {
            System.err.println("දෝෂය: String එක null වේ!");
        }
    }
}

Unchecked Exceptions handle කිරීම අනිවාර්ය නොවුණත්, program එකක stability එකට මේවා handle කරන එක ඉතා වැදගත්.

Exceptions Throw කරන හැටි (throw keyword)

සමහර වෙලාවට අපිට program එකේ යම්කිසි තත්ත්වයක් (condition) වැරදි කියලා දැනගත්තම, අපිටම Exception එකක් "විසි කරන්න" (throw) ඕනේ වෙනවා. මේකට අපි throw keyword එක පාවිච්චි කරනවා.

මේක ගොඩක් වෙලාවට data validation (දත්ත වලංගු කිරීම) වගේ දේවල් වලට පාවිච්චි කරනවා. උදාහරණයක් විදියට, user කෙනෙක් දුන්නු වයස වලංගු නැත්නම්, අපිට IllegalArgumentException එකක් throw කරන්න පුළුවන්, එහෙම නැත්තම් අපේම Custom Exception එකක් හදලා throw කරන්නත් පුළුවන්.

Custom Exceptions (අපේම Exceptions)

අපේ program එකේ විශේෂිත අවස්ථා වලට ගැලපෙන විදියට අපිටම Exceptions හදාගන්න පුළුවන්. ඒකට කරන්න තියෙන්නේ Exception class එකෙන් හරි, RuntimeException class එකෙන් හරි, අපේම class එකක් extend කරන එක.

// අපේම Exception class එකක්
public class InvalidAgeException extends Exception {
    public InvalidAgeException(String message) {
        super(message);
    }
}

public class ThrowExample {
    public static void validateAge(int age) throws InvalidAgeException {
        if (age < 0 || age > 120) {
            throw new InvalidAgeException("වයස අවලංගුයි. වයස 0ත් 120ත් අතර විය යුතුය.");
        } else if (age < 18) {
            throw new InvalidAgeException("වයස අවුරුදු 18ට වඩා අඩු නිසා ඇතුල් විය නොහැක.");
        } else {
            System.out.println("සාදරයෙන් පිළිගනිමු!");
        }
    }

    public static void main(String[] args) {
        try {
            validateAge(16); // InvalidAgeException එකක් throw වෙනවා
        } catch (InvalidAgeException e) {
            System.err.println("දෝෂය: " + e.getMessage());
        }

        try {
            validateAge(25); // සාර්ථකයි
        } catch (InvalidAgeException e) {
            System.err.println("දෝෂය: " + e.getMessage());
        }

        try {
            validateAge(-5); // InvalidAgeException එකක් throw වෙනවා
        } catch (InvalidAgeException e) {
            System.err.println("දෝෂය: " + e.getMessage());
        }
    }
}

මේ code එකේ validateAge method එක ඇතුලේ අපිම condition එකක් check කරලා, ඒක වැරදියි නම් අපේම InvalidAgeException එකක් throw කරනවා. main method එකේදී අපි ඒක try-catch block එකකින් අල්ලගන්නවා.

The throws Keyword එක

throws keyword එක throw keyword එකට වඩා වෙනස්. throws කියන එක method signature එකේ පාවිච්චි කරන්නේ, ඒ method එක ඇතුලේදී යම්කිසි Exception එකක් එන්න පුළුවන් බව declare කරන්න. ඒකෙන් කියවෙන්නේ, "මේ method එක මේ Exception එක throw කරන්න පුළුවන්, ඉතින් මේ method එක call කරන කෙනා මේක handle කරන්න ඕනේ" කියන එකයි.

මේක ප්‍රධාන වශයෙන් Checked Exceptions වලට තමයි පාවිච්චි කරන්නේ. මොකද compiler එක Checked Exceptions handle කරන්න කියලා බල කරන නිසා, අපි try-catch කරන්නේ නැත්නම්, අනිවාර්යයෙන්ම throws keyword එක පාවිච්චි කරන්න වෙනවා.

import java.io.IOException;

public class ThrowsKeywordExample {

    // මේ method එකෙන් IOException එකක් throw වෙන්න පුළුවන් කියලා declare කරනවා
    public static void readDataFromFile(String filePath) throws IOException {
        System.out.println("ගොනුවෙන් දත්ත කියවමින් පවතී: " + filePath);
        // ගොනුවක් කියවනකොට IOException එකක් එන්න පුළුවන්.
        // අපි මේක try-catch නොකර, call කරන කෙනාට handle කරන්න දෙනවා.
        if (!filePath.endsWith(".txt")) {
            throw new IOException("වලංගු නොවන ගොනු වර්ගය. .txt ගොනුවක් විය යුතුය.");
        }
        // ගොනුවෙන් කියවීමේ actual logic එක මෙතනට එනවා
        // For demonstration, let's just simulate an error
        if (filePath.equals("non_existent.txt")) {
             throw new IOException("ගොනුව සොයා ගත නොහැක: " + filePath);
        }
        System.out.println("ගොනුව සාර්ථකව කියවන ලදී.");
    }

    public static void main(String[] args) {
        try {
            // readDataFromFile method එකෙන් IOException එකක් එන්න පුළුවන් නිසා,
            // අපි ඒක try-catch කරන්න ඕනේ.
            readDataFromFile("data.txt");
            readDataFromFile("non_existent.txt"); // මෙතනදී Exception එකක් එනවා
        } catch (IOException e) {
            System.err.println("ගොනුව හා සම්බන්ධ දෝෂයක්: " + e.getMessage());
        }

        try {
            readDataFromFile("image.jpg"); // මෙතනදීත් Exception එකක් එනවා
        } catch (IOException e) {
            System.err.println("ගොනු වර්ගයේ දෝෂයක්: " + e.getMessage());
        }

        System.out.println("වැඩසටහන අවසන් විය.");
    }
}

මේ උදාහරණයේ, readDataFromFile method එක IOException එකක් throws කරනවා. ඒ කියන්නේ, මේ method එක call කරන main method එක වගේ තැනකදී, අනිවාර්යයෙන්ම try-catch block එකක් පාවිච්චි කරලා IOException එක handle කරන්න ඕනේ. නැත්නම් compiler error එකක් එනවා.

throw vs. throws - පොඩි වෙනස

  • throw: මේක පාවිච්චි කරන්නේ තනි Exception object එකක් program එකේ යම්කිසි තැනකදී මැනුවලි විසි කරන්න. (throw new IllegalArgumentException("Invalid input");)
  • throws: මේක පාවිච්චි කරන්නේ method signature එකේ, ඒ method එක ඇතුලේදී කිසියම් Exception වර්ගයක් එන්න පුළුවන් බව ප්‍රකාශ කරන්න. (public void myMethod() throws IOException { ... })

හොඳම පුරුදු සහ උපදෙස් (Best Practices and Tips)

Exception Handling කියන්නේ code කරනකොට පට්ට වැදගත් දෙයක්. ඒක හරියට කළොත් ඔයාගේ software එක ගොඩක් ස්ථායී (stable) වෙනවා, users ලටත් හොඳ අත්දැකීමක් ලැබෙනවා. මෙන්න පොඩි tips ටිකක්:

  • Specific Exception අල්ලන්න: හැමවෙලාවෙම catch (Exception e) කියලා generic විදියට අල්ලනවාට වඩා, specific Exception වර්ග (ArithmeticException, NullPointerException වගේ) අල්ලන්න උත්සාහ කරන්න. එතකොට ඔයාට ප්‍රශ්නයට අදාළවම නිවැරදි විදියට handle කරන්න පුළුවන්.
  • Exception එක නොසලකා හරින්න එපා: catch block එකක් ඇතුලේ මුකුත්ම නොකර "empty" catch block එකක් තියන එක ගොඩක් නරක පුරුද්දක්. (catch (Exception e) { /* nothing here */ }) එහෙම කළොත් program එකේ දෝෂයක් ආවම ඒක ගැන ඔයාට දැනගන්න ලැබෙන්නේ නෑ. අඩුම තරමේ error message එක log කරන්නවත් පුරුදු වෙන්න.
  • පරිශීලකයාට වැදගත් පණිවිඩයක් දෙන්න: Exception එකක් ආවොත්, ඒක user කෙනෙක්ට තේරෙන භාෂාවෙන් (තාක්ෂණික නොවන විදියට) පණිවිඩයක් දෙන්න. (උදා: "ඔබ ඇතුළත් කළ දත්ත වලංගු නොවේ." වගේ)
  • Resources නිවැරදිව වහන්න: Files, Database connections වගේ resources open කළා නම්, finally block එක පාවිච්චි කරලා ඒවා අනිවාර්යයෙන්ම වහන්න. Java 7 වල ඉඳන් try-with-resources කියන feature එක මේකට ගොඩක් පහසුයි. (ඒ ගැන වෙනම article එකකින් කතා කරමු.)
  • Exception hierarchy එක තේරුම් ගන්න: Java Exception hierarchy එක ගැන පොඩි අවබෝධයක් තියාගන්න එක ගොඩක් ප්‍රයෝජනවත්. ඒකෙන් ඔයාට Exception වර්ග සහ ඒවා අතර සම්බන්ධකම් තේරුම් ගන්න පුළුවන්.
  • Error logging: Production environment එකකදී, Exception එකක් ආවම ඒක නිවැරදිව log කරන එක ගොඩක් වැදගත්. ඒකෙන් අපිට පස්සේ ඒ දෝෂය හොයාගෙන fix කරන්න පුළුවන්. Log4j, SLF4J වගේ Libraries මේකට පාවිච්චි කරන්න පුළුවන්.

අවසාන වශයෙන්

ඉතින් යාළුවනේ, මේක තමයි Java Exception Handling කියන්නේ. program එකක් හදනකොට මේක හරියටම පාවිච්චි කරන එක පට්ට වැදගත්. program එක users ලට ලස්සනට වැඩ කරන්නත්, අපිට debugging පහසු කරගන්නත් මේක ගොඩක් උදව් වෙනවා. ඒ වගේම, හොඳට Exception Handling කරපු program එකක් කියන්නේ, ඒක හරියට හරියටම වැටෙන වැඩක් වගේ. මොකද, මොන ප්‍රශ්නයක් ආවත්, ඒක handle කරගන්න ක්‍රමයක් තියෙන නිසා.

මේ article එකෙන් ඔයාලට Java Exception Handling ගැන හොඳ අවබෝධයක් ලැබෙන්න ඇති කියලා මම හිතනවා. මතක තියාගන්න, practice කරන එක තමයි වැදගත්ම දේ. මේ concepts ඔයාගේ project වලට apply කරලා බලන්න. එතකොට තව ගොඩක් දේවල් ඉගෙන ගන්න පුළුවන්.

ඔයාලා මේ concepts try කරලා බැලුවද? මොනවද ඔයාලට තියෙන ප්‍රශ්න? ඔයාලා Exception Handling කරනකොට use කරන tips මොනවද? comment section එකේ කියන්න! ඔයාලගේ අදහස් දැනගන්න මම ආසයි. තවත් මේ වගේ Tech knowledge එකක් අරගෙන ඉක්මනින්ම හම්බවෙමු!

සුභ දවසක්!