পাঠ ৬: জাভা এক্সেপশান হ্যান্ডেলিং

  • এক্সেপশান বেসিকস
  • ট্রাই ক্যাচ- ফাইনালি
  • চেকড-একসেপশান
  • আনচেকড-একসেপশান
  • থ্রুয়িং একসেপশান
  • NullPointerException
  • ArrayIndexOutOfBoundsException
  • কিভাবে নিজস্ব এক্সেপশান লিখবো
  • সারসংক্ষেপ

আমরা একটি প্রোগ্রাম লিখি যার একটি নরমাল ফ্লো থাকে, তবে কোন কারণে যদি এই ফ্লো ব্যাহত হয় তাহলে জাভা রানটাইম একটি ইভেন্ট ফায়ার করে, একে এক্সেপশান বলা হয়।

সহজ কথায় এক্সেপশন হচ্ছে এক ধরণের ইরর যা কিনা প্রোগ্রাম চলাকালীন সময়ে দেখা দিতে পারে ।

একটি উদাহরণ দেখা যাক-

‌‌

public class Main {

    public static void main(String[] args) {
        int a = 1;
        int b = 0;

        int result = divide(a, b);   // ‌1
        System.out.println("Result: " + result);  // 2
    }

    public static int divide(int a, int b) {
        return a / b;
    }
}

১. এখানে divide() মেথডটিতে a এবং b আর্গুমেন্ট পাস করা হলে মেথডটি প্রথম আর্গুমেন্টকে দ্বিতীয় আর্গুমেন্ট দিয়ে ভাগ করে ফলাফল ‌result ভ্যারিয়েবল-টিতে এসাইন করবে।

২. এখানে result এর মান প্রিন্ট করা হবে।

আমরা যদি এই প্রোগ্রামটি রান করি তাহলে console এ নিচের আউটপুট-টি পাবো-

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at com.bazlur.exception.Main.divide(Main.java:18)
    at com.bazlur.exception.Main.main(Main.java:13)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
‌

এই আউটপুট থেকে আমরা বুঝতে পারি যে, আমাদের প্রোগ্রামটি-তে একটি সমস্যা হয়েছে এবং প্রোগ্রামটি এখানেই থেমে গেছে, System.out.println("Result: " + result); এই লাইনটি এক্সিকিউট হয় নি।

এবার আমরা নিচের প্রোগ্রামটি রান করি-

public class Main {

    public static void main(String[] args) {
        int a = 1;
        int b = 0;

        int result = 0;
        try {
            result = divide(a, b);
        } catch (ArithmeticException e) {
            System.out.println("You can't divide " + a + " by " + b);
        }

        System.out.println("Result: " + result);
    }


    public static int divide(int a, int b) {
        return a / b;
    }
}

এবার console এ নিচের আউটপুটটি দেখবো -

You can't divide 1 by 0

Result: 0

এবার লক্ষ্য করুন। প্রোগ্রামটি কিন্তু থেমে যাই নি, বরং শুণ্য দিয়ে যে কোন সংখ্যাকে ভাগ করা যাবে না, তার জন্য একটি মেসেস প্রিন্ট করেছে এবং শেষ পর্যন্ত প্রত্যেকটি লাইন এক্সিকিউট হয়েছে।

এই প্রোগ্রামটিতে আমরা নতুন কিওয়ার্ড ব্যবহার করেছি, সেগুলো হলো- try, catch এবং এগুলো দিয়ে আমাদের যে কোড ব্লকটিতে ইরর হওয়ার সম্ভবনা ছিল, সেই অংগটুকুকে wrap করেছি। এতে করে এই কোড ব্লক-এ যদি কোন ধরণের ইরর হয় তাহলে প্রোগ্রামটি catch ব্লক-এ চলে যায়, এবং এই ব্লক এর ইন্সট্রাকশন গুলো এক্সিকিউট করে এরপর নিচের কোড ব্লক এ চলে যায়।

আর এই প্রক্রিয়াকে আমরা এক্সেপশন হ্যান্ডেলিং বলি, অর্থাৎ প্রোগ্রাম এর কোন অংশে যদি কোন ধরণের এক্সেপশন বা ইরর হয় তাহলে আমাদের প্রোগ্রামটি যাতে বন্ধ না হয় যায় বরং সেইসব অবস্থায় ইউজারকে যাতে করে অর্থপূর্ণ মেসেস দেওয়াকে এক্সেপশন হ্যান্ডেলিং বলে।

The try Block

যদি কোন কোড ব্লক -এ যদি ইরর হওয়ার সম্ভবনা থাকে তাহলে আমরা সেই কেড ব্লক-কে try ব্লক দিয়ে ইনক্লােজ করতে হয়। উদাহরণ-

try {
    code
}
catch and finally blocks . . .

এই try ব্লক এর মাঝে এক বা একাধিক লাইন কোড থাকতে পারে। catch এবং finally ব্লক পরের সেকশনে দেখানো হবে।

একটি উদাহরণ দেখা যাক-

private List<Integer> list;
    private static final int SIZE = 10;

    public void writeList() {
        PrintWriter out = null;
        try {
            System.out.println("Entered try statement");
            out = new PrintWriter(new FileWriter("file.txt"));
            for (int i = 0; i < SIZE; i++) {
                out.write(i);
            }

        } catch (IOException e) {
        }
    }

উপরের প্রোগ্রামটিতে একটি মেথড আছে - writeList() যা কিনা একটি ফাইল এ একটি লিস্ট থেকে ভ্যালু পড়ে তা রাইট করে। এই মেথড-টি তে একাধিক এক্সেপশান বা ইরর হতে পারে। যেমন -
out = new PrintWriter(new FileWriter("file.txt")); এই লাইনটিতে আমরা একটি ফাইল অপেন করার চেষ্টা করেছি। কিন্তু এই ফাইলটি সিস্টেমে নাও থাকতে পারে, কিংবা থাকলেও সেটি অপেন করা যাচ্ছে না ইত্যাদি। সেক্ষেত্রে আমাদরে সিস্টেম IOException থ্রু করবে এবং প্রোগ্রামটি বন্ধ হয়ে যাবে। এছাড়াও আমরা একটি ফর লুপ ব্যবহার করেছি, এক্ষেত্রে ফাইল এ রাইট করার সময়ও ইরর বা এক্সেপশন হতে পারে। তাই এইসব ইরর বা এক্সেপশন কে হ্যান্ডেল করার জন্যে আমরা কোড ব্লকটিকে try ব্লক এর ভেতরে রেখেছি। এখন প্রোগ্রামটি চলার সময় যদি কোন ইরর বা একসেপশন হয় তাহলে প্রোগ্রাম এক্সিকিশান সেখান থেকেই catch ব্লক এ চলে যাবে।

The catch Blocks

try ব্লক এর সাথেই catch ব্লক লিখতে হয়। তবে আমরা একটি try ব্লকের সাথে একাধিক catch ব্লক লিখতে পারি। উদাহরণ-

try {

} catch (ExceptionType name) {
//  catch blog # 1
} catch (ExceptionType name) {
//  catch blog # 1
}

catch কিওয়ার্ড এর সাথে প্যারেন্থেসিস এর মাঝে আমরা আর্গুমেন্ট দিতে হয় যা কি টাইপ এক্সেপশন হ্যাল্ডেল করা হচ্ছে তা নির্দেশ করে। এখানে ExceptionType একটি প্লেস হোল্ডার । এখানে যে কোন ক্লাস যা কিনা Throwable ক্লাস কে ইনহেরিট করে তা বসতে পারে।

try ব্লক এর কোন কোড-এ যদি কোন এরর বা এক্সিসেপশন হয় তাহলে প্রেগ্রামের এক্সিকিউশান পয়েন্ট catch ব্লকে চলে আসে এবং শুধুমাত্র তখনি catch ব্লক এর কোড এক্সিকিউট হয়।

যদি একাধিক catch ব্লক থাকে তাহলে এক্সেপশন এর টাইপ অনুযায়ী ‌catch ব্লক সিলেকটেড হয়।

‌‌

try {

} catch (IndexOutOfBoundsException e) {
    System.err.println("IndexOutOfBoundsException: " + e.getMessage());
} catch (IOException e) {
    System.err.println("Caught IOException: " + e.getMessage());
}

এখানে try ব্লকে যদি IndexOutOfBoundsException হয় তাহলে প্রথম catch ব্লকটি এক্সিকিউট হবে । আর যদি IOException হয় তাহলে পরের catch ব্লকটি এক্সিকিউট হবে।

জাভা ৭ এবং পরবর্তি ভার্সন গুলোর জন্যে একটি নতুন ফিচার আছে যাতে করে একটি catch ব্লক দিয়ে অনেকগুলো এক্সেপশন হ্যান্ডেল করা যায়। উহারহণ -

catch (IOException|SQLException ex) {
    logger.log(ex);
}

এখানে catch ক্লজ-এ একাধিক এক্সেপশান একটি ভার্টিকেল বার (|) দিয়ে আলাদা করা হয়।

The finally Block

উপরের উদাহরণ গুলো থেকে দেখলাম যে , try ব্লক এর কোড -এ এক্সেসেপশন হলে শুধুমাত্র ‌catch ব্লকের কোড গুলো এক্সিকিউট হয়। তবে আমাদের এমন কোন সিচুয়েশন থাকতে পারে যখন আমরা চাই ইরর হোক বা না হোক, একটি কোড ব্লক আমরা সবসমই এক্সিকিউট করতে চাই , তাহলে আমরা finally ব্যবহার করি।

public void openFile() {
        FileReader reader = null;
        try {
            reader = new FileReader("someFile");
            int i = 0;
            while (i != -1) {
                i = reader.read();
                System.out.println((char) i);
            }
        } catch (IOException e) {
            //do something clever with the exception
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    //do something clever with the exception
                }
            }
            System.out.println("--- File End ---");
        }
    }

উপরের প্রোগ্রামটি তে আমরা একটি ফাইল অপেন করছি এবং কিছু কাজ করেছি। এজন্যে একটি FileReader ক্লাসের অবজেক্ট তৈরি করেছি। আমরা চাই এই FileReader অবজেক্টি কাজ শেষ হয়ে গেলে ক্লোজ করতে। এক্ষেত্রে আমরা finally ব্লক এ আমাদের একই ক্লোজিং এর কোডটি লিখেছি। এতে করে এই সুবিধা হচ্ছে যে, আমাদের এই ট্রাই ব্লক-এর কোড কাজ করুক আর না করুক, শেষে আমাদের FileReader এর অবজেক্টটি ক্লোজ হয়ে যাচ্ছে।

অর্থাৎ আমরা শুধুমাত্রে তখনি ফাইনালী ব্লক ব্যবহার করি যখন আমরা নো ম্যাটার হুয়াট, একটি কোড ব্লক সবসময়ই এক্সিকউট করতে চাই।

Identifying Exception Point

try , catch এবfinally ব্লক ব্যাবহার করে এক্সেপশন হ্যান্ডেল করার সময় আমাদের যে বিষয়টির উপর বিশেষ গুরুত্ব দিতে হবে সেটি হল নির্দিষ্ট পয়েন্টেই কেবলtry এবং catch ব্যাবহার করা । ঠিক যেখানে এক্সেপশন ঘটবে বা ঘটার সম্ভাবনা থাকবে সেখানেই কেবল আমাদের প্রপার এক্সেপশন হ্যান্ডেলিং থাকা জরুরী । অন্যথায় কোড রান করবে কিন্তু কাঙ্খিত ফলাফল পাওয়া যাবেনা ।

উদাহরন হিসাবে আমরা মনে করি আমাদের একটি মেথড লিখতে বলা হল যেটিতে একটি স্ট্রিং অ্যারে পাস করা হবে এবং সেই অ্যারে এর মাঝ থেকে যে স্ট্রিং গুলা ইন্টিজার নাম্বার রিপ্রেজেন্ট করে সেগুলার যোগফল রিটার্ন করতে হবে । আমরা যদি কোডটি এভাবে লিখিঃ

public class Main {

    public static void main(String[] args) {

        String[] strings = {"1", "2", "3", "4", "5", "6"};
        System.out.println(new Main().getSum(strings));
    }

    public int getSum(String[] strings){

        int result  = 0;

        try {

            for (String string : strings) {
                result += (Integer.valueOf(string));
            }
        } catch (NumberFormatException e) {

            System.err.println(e);
        }

        return result;
    }
}

উপরের কোডটি 21 সংখ্যাটি প্রিন্ট করবে যেটি getSum নামক মেথটি রিটার্ন করছে । কিন্তু আমরা যদি ইনপুট স্ট্রিংটি একটু মডিফাই করে String[] strings = {"1", "2", "3", "four", "5", "6"}; করে দেই তাহলে প্রথমে প্রিন্ট করবে 6 এবং তারপর প্রিন্ট করবে java.lang.NumberFormatException: For input string: "four । কিন্তু প্রবলেম অনুযায়ী প্রিন্ট করা কথা ছিল 17 । কারন four বাদ দিলে বাকী যতগুলা স্ট্রিং ইন্টিজার নাম্বার রিপ্রেজেন্ট করে সেগুলার যোগফল । সেক্ষেত্রে আমরা যদি কোডটি একটু মডিফাই করে ঠিক যেখানে এক্সেপশন হওয়া সম্ভব সেখানেই try ব্লকটি ব্যাবহার করতাম তাহলে এই সমস্যা থেকে মুক্তি পাওয়া সম্ভব ছিল । কোডটি যদি এভাবে করিঃ

public class Main {

    public static void main(String[] args) {

        String[] strings = {"1", "2", "3", "four", "5", "6"};
        System.out.println(new Main().getSum(strings));
    }

    public int getSum(String[] strings) {

        int result = 0;

        for (String string : strings) {

            try {

                result += (Integer.valueOf(string));
            } catch (NumberFormatException e) {

                System.err.println(e);
            }
        }

        return result;
    }
}

এবার যদি আমরা String[] strings = {"1", "2", "3", "four", "5", "6"}; এই স্ট্রিংটি ইনপুট আকারে দেই তাহলে দেখবো একটা এক্সেপশন ঠিকই থ্রো করছে তবে রেজাল্ট হিসাবে আমরা যেটি চেয়েছিলাম সেটিও প্রিন্ট করছে । এভাবে আমরা ঠিক নির্দিষ্ট পয়েন্টে এক্সেপশন ডিটেক্ট করে হ্যান্ডেল করতে পারি । এতে করে ওভারঅল কোডের পার্ফরমেন্স যেমন বাড়বে তেমন কোড অনেক বেশি বাগফ্রী ও হবে ।

Checked or Unchecked Exceptions

জাভাতে সব এক্সেপশান গুলো Throwable ক্লাসকে ইনহেরিট করে তৈরি। অর্থাৎ এক্সেপশান হাইআরকি এর একদপ উপরে এই Throwable ক্লাস এর অব্স্থান। এর ঠিক নিচেই দুটি সাব ক্লাস হলে - Exception এবং অন্যটি হলো RuntimeException । এবং এই দুটি ক্লাস দুটি আলাদা শ্রেণীবিভাগের সূচনা করেছে। তবে এই শ্রেণীবিভাগের আরেকটি শাখা আছে, সেটি হলো - Error তবে এগুলো প্রোগ্রাম চলাকালিন সময়ে সাধারণত ধরা হয় না। এগুলো মূলত জাভা রানটাইম সিস্টেম নিজে থেকে হ্যান্ডেল করে এবং এটি আমাদের এই বইয়ের আলোচনার বাইরে।

Throwable

public class ExceptionDemo5 {

    public void fetchData(String url) {
        try {
            String data = fetchDataFromUrl(url);
        } catch (CheckedException e) {
            e.printStackTrace();
        }
    }

    public String fetchDataFromUrl(String url) throws CheckedException {
        if (url == null) {
            throw new CheckedException("Url Not found");
        }

        String data = null;
        //read lots of data over HTTP and return
        //it as a String instance.

        return data;
    }
}
public class ExceptionDemo6 {
    public void fetchData(String url) {
        String data = fetchDataFromUrl(url);
    }

    public String fetchDataFromUrl(String url) {
        if (url == null) {
            throw new UncheckedException("Url Not found");
        }

        String data = null;
        //read lots of data over HTTP and return
        //it as a String instance.

        return data;
    }
}
public class CheckedException extends Exception {
    public CheckedException(String message) {
        super(message);
    }
}
public class UncheckedException extends RuntimeException {
    public UncheckedException(String message) {
        super(message);
    }
}