Monday, May 22, 2017

এনক্যাপসুলেশন নিয়ে যত পাগলামী ॥ পাগলের প্রলাপ

অবজেক্ট অরিয়েন্টেড ল্যাঙ্গুয়েজের যে কয়েকটি স্বকীয় বৈশিষ্ট্য রয়েছে তার মধ্যে অন্যতম গুরুত্বপূর্ন এবং সবচেয়ে সহজবোধ্য বৈশিষ্ট্য হল এনক্যাপসুলেশন । আমিতো পাগল, আপনারা যেহেতু এটা পড়ছেন কিছুক্ষন পর আপনারাও পাগল হয়ে গেলে আমার কিছুই করার থাকবে না । তো চলুন আর কথা না বলে পাগলামী শুরু করি ।

আশাকরি জীবনে একবার হলেও অসুস্থ হয়েছেন । না হলে তো খুবই খুশির কথা । আর সেটা না হলেও ঔষধ হিসাবে খাওয়া হয় যে ক্যাপসুল সেটা নিশ্চয় দেখেছেন । তো এখন কিছুটা ক্যাপসুল নিয়ে কথা বলি ।

খেয়াল করলে দেখবেন ক্যাপসুলের বাইরে শক্ত একটা পরত থাকে এর সেটার মাঝে অনেকগুলা উপাদান থাকে যেগুলাকে ওই শক্ত আবরনটা বেষ্টন করে থাকে । তার মানে ওই উপাদানগুলাকে শক্ত খোলস দ্বারা আবদ্ধ করা হয়েছে সুরক্ষিত রাখার জন্য । মানে উপাদানগুলাে এনক্যাপসুলেট করা হয়েছে ওই খোলসের মাঝে । এমনটা করার কারন হল উপাদানগুলা যেন প্রাকৃতিক আলো, বাতাস, জীবানুতে ক্ষতিগ্রস্থ না হয়ে ভেতরে সুরক্ষিত থাকে । এধরনের ক্যাপসুল যখন খাওয়া হয় তখন সেটা হিউম্যান বডিতে প্রবেশ করে উপরের খোলসটি ফেটে যায় এবং বডিতে কাজ শুরু করে । তার মানে এই ক্যাপসুলের খোলসটি দেওয়া হয়েছে একটা সিকিউরিটি লেয়ার হিসাবে । সেটা যেন বডিতে কাজ করতে পারে কিন্তু অন্যান্য প্রাকৃতিক ইনগ্রিডিয়েন্ট থেকে সুরক্ষিত থাকে ।

অবজেক্ট অরিয়েন্টেড প্রোগ্রামিং এর অন্যতম একটি প্রধান বৈশিষ্ট্য কিন্তু ঠিক এই কনসেপ্ট যেটি একটু আগেই ব্যাখ্যা করলাম । আমরা সবাই জানি অবজেক্ট অরিয়েন্টেড প্রোগ্রামিং এর কনসেপ্টে দুনিয়ার তাবৎ সবকিছুই অবজেক্ট । আপনি, আমি, লতা-পাতা, গাছ, পাখি, ফুল যত যা কিছু আমরা দেখতে পাই, দেখা যায়না, অনুভব করতে পারি, বা কল্পনা করতে পারি সবই অবজেক্ট । একটি অবজেক্টের (জাভার রেসপেক্টে) ৪ টি প্রধান কম্পোনেন্ট থাকে, (i) ব্লক (ii) কনস্ট্রাক্টর (iii) অ্যাট্রিবিউট (স্টেট) (iv) মেথড (বিহ্যেবিয়র) ।
(i) & (ii) এই দুইটি কম্পোনেন্ট আপাতত আমাদের দরকার হবেনা, যখন হবে তখন জেনে নেওয়া যাবে ।  তবে আমি আশাকরব যেহেতু আপনারা এই আর্টিকেলটি পড়তে এসেছেন তাই এদের কাজ হালকা পাতলা হলেও কিছুটা জানেন ।
(iii) অ্যাট্রিবিউটঃ অ্যাট্রিবিউট মানে হল ভ্যারিয়েবল । এখানে যে ভ্যারিয়েবলের কথা বলা হচ্ছে সেটিকে ফিল্ডও বলা হয় কারন এটি সরাসরি কনটেইনার ক্লাসের আন্ডারে ডিক্লেয়ার করা হয় । কোন মেথডের মাঝে ডিক্লেয়ার করা ভেরিয়েবলকে লোকাল ভ্যারিয়েবল বলা হবে এবং ওটা ভোলাটাইল । অ্যাট্রিবিউটকে অবজেক্টের স্টেটও বলা হয় । কারন একটি অবজেক্টের ইন্সট্যান্ট কন্ডিশন বা স্টেট ধারন করে বিধায় এর এরকম নামকরন ।
(iv) মেথডঃ যারা প্রোসিডিউরাল প্রোগ্রামিং এর সাথে পরিচিত তারা এটাকে ফাংশন হিসাবে চিনে থাকবেন হয়ত । মেথডের মাঝে বিভিন্ন অ্যাট্রিবিউট নিয়ে নাড়াচাড়া করা হয় । একই অ্যাট্রিবিউটের উপর আলাদা আলাদা মেথডের প্রভাব আলাদা আলাদা হয়ে থাকে । মানে একই অ্যাট্রিবিউটকে যদি ২ টি আলাদা মেথডের মাঝে প্যারামিটার হিসাবে পাস করা হয় তবে সেই মেথড ২ টি উক্ত অ্যাট্রিবিউটের সাথে একই রকম ভদ্র ব্যাবহার নাও করতে পারে, বেশিরভাগ ক্ষেত্রেই করেনা । এজন্য মেথডকে একটি অবজেক্টের বিহ্যেবিয়রও বলা হয় ।

পরিচিতি পর্ব শেষ , এবার পাগলের প্রলাপ শুরু করা যাক । আমরা সবাই জানি যে একটি অবজেক্টের ব্লুপ্রিন্ট হল ক্লাস । তার মানে একটি অবজেক্টের মাঝে কি কি স্টেট থাকবে এবং উক্ত স্টেট গুলার জন্য কি কি ধরনের বিহ্যেব (আচরন) করতে পারে অবজেক্টটি সেগুলা সব ডেসক্রাইব করা থাকে তার ক্লাসে ।

এখন কথা হল একটি ক্লাস লেখার সময় আমরা ৪ ধরনের অ্যাট্রিবিউট ডিক্লেয়ার করতে পারি তাদের অ্যাক্সেসিবিলিটির উপর নির্ভর করে । তার মানে উক্ত অ্যাট্রিবিউটটিকে কে কে বা কোন কোন জায়গা থেকে অ্যাক্সেস এবং মডিফাই করা সম্ভব । অ্যাক্সেসিবিলিটি কনট্রোল করার এই ৪ প্রকার মডিফায়ারকে বলা হয় অ্যাক্সেস মডিফায়ার । এদের নাম এবং কাজ সম্পর্কে নিচের টেবিলটি দেখলে বুঝতে পারব ।
এখানে দেখতে পারছি public মডিফায়ার ব্যাবহার করলে সেই অ্যট্রিবিউটটিকে যেকোন স্থান থেকে সরাসরি অ্যাক্সেস করা সম্ভব । আর private মডিফায়ার ব্যাবহার করলে কেবল উক্ত ক্লাসের মেফডগুলাতে সেটির অ্যাক্সেস পাওয়া সম্ভব । ছোট্ট একটা উদাহরন দেখি ।

কোডঃ
public class EncapTest {

    public int x;
    private int y;

    public void showX() {

        System.out.println("In EncapTest -> " + x);
    }

    public void showY() {

        System.out.println("In EncapTest -> " + y);
    }
}

public class Main {

    public static void main(String[] args) {

        EncapTest et = new EncapTest();
        et.x = 10;
//        et.y = 5; //This field is not accessible here outside from EncapTest class
        et.showX();
        et.showY();
        System.out.println("Outside from EncapTest -> " + et.x);
//        System.out.println("Outside from EncapTest -> " + et.y); // Not valid
    }
}

আউটপুটঃ
In EncapTest -> 10
In EncapTest -> 0
Outside from EncapTest -> 10

উপরোক্ত কোডটিতে আমরা 'EncapTest' ক্লাসে ২ টি অ্যাট্রিবিউট ডিক্লেয়ার করেছি, 'x' যেটি পাবলিক এবং 'y' যেটি প্রাইভেট । অর্থাৎ 'x' কে উক্ত ক্লাস এবং উক্ত ক্লাসের বাইরে যেকোন ক্লাস থেকে এবং যেকোন মেথড থেকে অ্যাক্সেস করা সম্ভব কিন্তু 'y' কে কেবল মাত্র 'EncapTest' ক্লাসের অবস্থিত মেথড ব্যাতীত অন্য কোন স্থান থেকে অ্যাক্সেস বা মডিফাই করা সম্ভব নয় । অর্থাৎ 'y' অ্যাট্রিবিউটটিকে আমরা 'EncapTest' ক্লাসে এনক্যাপসুলেট করে ফেলেছি ।

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

কিন্তু একটা অ্যাট্রিবিউটকে যদি আমরা প্রয়োজনে মডিফাই নাই করতে পারলাম বা অ্যাক্সেসই না করতে পারলাম তাহলে সেটা রেখে লাভ কি ! ওকে, সেটার ব্যাবস্থাও আছে । এটার জন্য আমরা যে কাজটি করি সেটা হল পাবলিক একটা ইন্টারফেস (যে ইন্টারফেস ইমপ্লিমেন্ট করা হয় সেটা না) রাখি যে মাধ্যমে আমরা উক্ত অ্যাট্রিবিউটকে অ্যাক্সেস করতে পারি । এধরনের পাবলিক ইন্টারফেসকে বলা হয় গেটার (getter) এবং সেটার (setter) মেথড । এই মেথডগুলার অ্যাক্সেস মডিফায়ার সাধারনত (অসাধারনও হতে পারে) দেওয়া থাকে পাবলিক এবং যেকোন স্থান থেকে তাদের অ্যাক্সেস করা সম্ভব । গেটার মেথডের মাধ্যমে কোন একটি অ্যাট্রিবিউটের ভ্যালু রিড করা হয় এবং সেটার মেথডের মাধ্যমে কোন একটা অ্যাট্রিবিউটের ভ্যালু সেট করা হয় বা সহজ কথায় ভ্যালু ওভাররাইড করা হয় । চলুন একটা উদাহরন দেখে নেওয়া যাক ।

কোডঃ
public class EncapTest {

    private int x;
    private int y;

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
}

public class Main {

    public static void main(String[] args) {

        EncapTest et = new EncapTest();
        et.setX(5);
        et.setY(7);
        System.out.println("Value of x: " + et.getX()); //x is accessible through getter
//        System.out.println(et.x); //x is not directly accessible
        System.out.println("Value of y: " + et.getY());
    }
}

আউটপুটঃ
Value of x: 5
Value of y: 7

উপরোক্ত ছোট্ট প্রোগ্রামটিতে আমরা দেখতে পারছি যে 'EncapTest' ক্লাসের মাঝে ডিক্লেয়ার কৃত ২ টি অ্যাট্রিবিউট 'x' & 'y' প্রাইভেট তাই মেইন মেথডের মাঝ থেকে সরাসরি তাদের কোনভাবেই অ্যাক্সেস করা সম্ভব নয় । কিন্তু তাদের গেটার এবং সেটারের মাধ্যমে অ্যাক্সেস করা সম্ভব ।

মূল লক্ষনীয় বিষয় এবার । খেয়াল করে দেখুনতো একটা অ্যাট্রিবিউটকে প্রাইভেটলি ডিক্লেয়ার করে সেটার গেটার এবং সেটারকে যদি পাবলিক ডিক্লেয়ার করা হয় তবে উক্ত অ্যাট্রিবিউটটিকে বরং পাবলিক ডিক্লেয়ার করার মাঝে কোন তফাৎ থাকে কি না । নাহ কোন তফাতই থাকবে না, কারন প্রাইভেট প্রোপার্টি হওয়া সত্তেও উক্ত অ্যাট্রিবিউটকে যেকোন স্থান থেকে মেনুপুলেট করা সম্ভব । তাহলে আর এনক্যাপসুলেট করলাম কিভাবে !

অ্যাক্সেস লেয়ার অনুযায়ী এনক্যাপসুলেশন আসলে আমাদের করতে হবে গেটার এবং সেটার মেথডে । অর্থাৎ একটি অ্যাট্রিবিউট (অবশ্যই সেটা যদি পাবলিক টাইপ স্ট্যাটিক বা ফাইনাল না হয় তবে) ডিক্লেয়ার করার সময় প্রাইভেটলি ডিক্লেয়ার করতে হবে এবং মডিফিকেশন স্কোপ সেট করে দিতে হবে মেথড লেয়ারে । ওকে একটা উদাহরন দেখে ফেলি ।

কোডঃ
public class EncapTest {

    private int x;
    private int y;

    public int getX() {
        return x;
    }

    void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    void setY(int y) {
        this.y = y;
    }
}

public class Main {

    public static void main(String[] args) {

        EncapTest et = new EncapTest();
        et.setX(5);
        et.setY(7);
        System.out.println("Value of x: " + et.getX()); //x is accessible through getter
//        System.out.println(et.x); //x is not directly accessible
        System.out.println("Value of y: " + et.getY());
    }
}

আউটপুটঃ
Value of x: 5
Value of y: 7

উপরোক্ত প্রোগ্রামটিতে খুব ছোট্ট কিন্তু অনেক গুরুত্বপূর্ন যে পরিবর্তনটুকু করা হয়েছে সেটা হল আমরা 'x' & 'y' এর সেটারকে এনক্যাপসুলেট করে দিয়েছি প্যাকেজ স্কোপে । অর্থাৎ, যেখান থেকেই 'EncapTest' এর অবজেক্ট ক্রিয়েট করা হোক না কেন সেটার গেটার দুটি ঠিকই অ্যাক্সেস পাবে কিন্তু সেটার কেবল মাত্র উক্ত প্যাকেজের বাইরে অ্যাক্সেস পাবে না । মানে 'EncapTest' ক্লাসটি যে প্যাকেজে থাকবে উক্ত প্যাকেজে অবস্থিত যেকোন ক্লাসে যদি 'EncapTest' এর অবজেক্ট ক্রিয়েট করা হয় তবে সেসব ক্লাসে উক্ত অবজেক্টগুলার সেটার মেথডের মাধ্যমে 'x' এবং 'y' এর ভ্যালু মডিফাই করা সম্ভব কিন্তু প্যাকেজ স্কোপের বাইরে সেটা করা সম্ভব নয় । গেটার মেথড পাবলিক হওয়ায় যেকোন স্থান থেকে 'x' এবং 'y' ভ্যালু রিড করা সম্ভব । এখানে 'x' এবং 'y' এর মডিফিকেশন এনক্যাপসুলেট করে দেওয়া হয়েছে প্যাকেজ স্কোপে এবং ডাটা রিডের ক্ষেত্রে কোন প্রকার এনক্যাপসুলেশন আরোপ করা হয়নি ।

ওকে, এবার একটা জিনিস দেখি । এতক্ষন আমরা 'x' এবং 'y' নিয়ে কাজ করলাম যে দুটিই প্রিমিটিভ ভ্যালু । যদি এমন হয় যে আমরা অ্যাট্রিবিউট হিসাবে অন্য কোন ক্লাসের অবজেক্ট নিয়ে কাজ করছি সেক্ষেত্রে কি হবে ? এক্ষেত্রে আমাদের জানতে হবে অবজেক্ট রেফারেন্স কিভাবে কাজ করে । সেটা জানার জন্য আপনারা এই আর্টিকেলটি পড়ে ফেলতে পারেন । ওকে তাহলে আমরা জানি যে রেফারেন্স টাইপের ক্ষেত্রে গেটার মেথড আমাদের উক্ত অবজেক্টের রেফারেন্স রিটার্ন করবে । মানে আমরা যদি কোন প্রকার সেটার মেথড ব্যাবহার নাও করি তার পরেও আমরা উক্ত অবজেক্টের ম্যানুপুলেশন করতে পারব । ছোট্ট একটা উদাহরন দেখে ফেলি ।

কোডঃ
public class EncapObject {

    private int id;

    public int getId() {
        return id;
    }

    void setId(int id) {
        this.id = id;
    }
}

public class EncapTest {

    private EncapObject encapObject;

    public EncapTest(EncapObject encapObject) {
        this.encapObject = encapObject;
    }

    public EncapObject getEncapObject() {
        return encapObject;
    }
}

public class Main {

    public static void main(String[] args) {

        EncapObject encapObject = new EncapObject();
        encapObject.setId(5);
        EncapTest et = new EncapTest(encapObject);
        et.getEncapObject().setId(7);
        System.out.println(et.getEncapObject().getId());
    }
}

আউটপুটঃ
7

উক্ত ছোট্ট প্রোগ্রামটিতে আমরা দেখতে পারছি যে 'EncapTest' ক্লাসের মাঝে 'EncapObject' ক্লাসের যে অবজেক্ট 'encapObject' রয়েছে তার অ্যাক্সেস মডিফায়ার প্রাইভেট । মানে বাইরে থেকে অ্যাক্সেস পাওয়া সম্ভব নয় । উক্ত অবজেক্সেট কোন সেটার মেথডও তৈরি করা হয়নি কিন্তু একটি গেটার মেথড রয়েছে যেটি পাবলিক এবং গেটার মেথড উক্ত অবজেক্টের রেফারেন্সই রিটার্ন করে ভ্যালু নয় যেজন্য উক্ত অবজেক্টকে খুব সহয়ে ম্যানুপুলেট করা সম্ভব যদিও কোন সেটার মেথড প্রেজেন্ট নেই । এজন্য রেফারেন্স টাইপ অ্যাট্রিবিউটের ক্ষেত্রে আমাদের অবশ্যই এনক্যাপসুলেশন স্কোপ এমন ভাবে সেট করতে হবে যেন সেটার পল্যুশন না হয় ।

আশাকরি আপনারা সবাই বুঝতে পেরেছেন এনক্যাপসুলেশন কি এবং কেন সেটা এতটা গুরুত্বপূর্ন । বড় এবং সফিসিস্টিকেটেড অ্যাপ্লিকেশন যেখানে সিকিউরিটি খুবই গুরুত্বপূর্ন সেসব ক্ষেত্রে এনক্যাপসুলেশনের গুরুত্ব অপরিসীম ।

সবশেষে একটি প্রশ্ন, এনক্যাপসুলেশন আসলে কি ? কোন একটা অ্যাট্রিবিউটকে প্রাইভেট ডিক্লেয়ার করে তার পাবলিক গেটার এবং সেটার তৈরি করাই কি এনক্যাপসুলেশন ?

প্রশ্নের উত্তরটি আমারো জানা নেই । যদি কেউ জেনে থাকেন তবে আমাকে সেটা জানিয়ে বাধিত করবেন । সবাইকে ধন্যবাদ । আর হ্যা এরপর থেকে অসুখ করলে ডাক্তার যদি ক্যাপসুল খেতে দেয় তবে খাওয়ার আগে বুঝেশুনে খাবেন । কি না কি আছে ভেতরে সেটা তো আর জানেন না, হয়ত আপনার কাছে সেটা জানার ক্ষমতা দেওয়া হয়নি !

জাভা পাস-বাই-রেফারেন্স নাকি পাস-বাই-ভ্যালু ॥ পাগলের প্রলাপ

সবার প্রথমে একটা গল্প বলি । মূল গল্পটি কাহলিল জিবরানের , আমি সংক্ষিপ্ত এবং নিজের মত করেই লিখছি ।
গল্পঃ কোন এক কালে ছোট্ট কোন এক দেশে সবাই অনেক সুখে শান্তিতে বসবাস করত । সেই দেশে একটা কুপ ছিল যার পানি ছিল সুশীতল এবং সুমিষ্ট । রাজ্যের রাজা-প্রজা সবাই সেই কুপের পানি পান করত । এক রাত্রে এক ডাইনি এসে ওই কুপে কয়েক ফোটা পানি মিশিয়ে মনে মনে কিছু মন্ত্র পাঠ করে বিড়বিড় করে বলল যে আগামিকাল সকালে এই কুপের পানি যে যে পান করবে সে সে উন্মাদ হয়ে যাবে । পরের দিন রাজ্যের সবাই সেই কুপ থেকে পানি পান করল কেবল রাজা আর প্রধান উজির ব্যাতীত । কিছুক্ষন পর দেখা গেল রাজ্যের সব প্রজা নিজেদের কাজ বাদ দিয়ে কানা-ঘুষা শুরু করল যে, "আমাদের রাজা আর উজির পাগল হয়ে গিয়েছে, রাজ্যে নতুন রাজা নির্বাচিত করতে হবে নাহলে রাজ্য অচল হয়ে যাবে" । তো এমন অবস্থা দেখে রাজামশাই চিন্তিত হয়ে উজিরকে ডেকে বললেন যে যাও ওই কুপ থেকে ২ গ্লাস পানি নিয়ে এসো রাজ পেয়ালায় করে । তারপর রাজা আর উজির সেই পানি পান করলেন । পরদিন সকালে দেখা গেল সব স্বাভাবিক এবং সব প্রজা বলা বলি করতে লাগল, যাক আমাদের রাজামশাইয়ের তন্দ্রা ফিরে এসেছে, এবার রাজ্য চলবে । যথারীতি সুখে শান্তিতে সবাই জীবন যাপন করতে লাগল ।

গল্পটি বলা এজন্যই যে আপাতত এই আর্টিকেল পড়ার সময় ধরে নিন আমি একজন পাগল । আর পাগলের কোন কথা বিশ্বাস করতে হয়না ।

যাইহোক মূল কথায় আসি । অন্যান্য প্রোগ্রামিং ল্যাঙ্গুয়েজ তো বটেই খোদ জাভা ডেভলপারদের মাঝেই কনফিউশন আছে যে জাভা কি পাস-বাই-রেফারেন্স নাকি পাস-বাই-ভ্যালু । স্ট্যাকওভারফ্লো এই লিংকে জাভার প্যারামিটার পাসিং নিয়ে বেশ সুন্দর করে কিছু কথা বার্তা বলা হয়েছে । মূল বিষয় যেটি সেটি হল অধিকাংশ ডেভলপারই এটা পড়ার পর কনফিউশনে ভুগবেন যে এতদিন যেটা জেনে এসেছেন যে জাভা পাস-বাই-রেফারেন্স কিন্তু এখানে বলা হচ্ছে পাস-বাই-ভ্যালু সেটা কি তবে ভুল !
আসলে এটা জানার আগে আমাদের জানতে হবে পাস-বাই-ভ্যালু এবং পাস-বাই-রেফারেন্স কি জিনিস । আবারো স্ট্যাকওভারফ্লো এর একটা লিংক হাতে ধরিয়ে দেই । এখানে খুব সুন্দর ভাবে বুঝিয়ে বলা হয়েছে যে রেফারেন্স আর ভ্যালু পাসিং কি । সংক্ষেপে বলতে গেলে, কোন একটা ভ্যারিয়েবলের ভ্যালু কোন একটি মেথডে/ফাংশনের মাঝে পাস করা হলে যদি উক্ত মেথডের মাঝে ওই প্যারামিটারের কোন চেঞ্জ করা হয় তবে যেখান থেকে কল করা হয়েছিল সেখানেও চেঞ্জ হয়ে যায় তবে সেটাকে বলে রেফারেন্স পাসিং । মানে একই মেমোরিকে আমরা আলাদা আলাদা জায়গা থেকে অ্যাক্সেস করছি কিন্তু চেঞ্জ হবে একই জায়গায় । এটা না হয়ে যাদি এমন হয় যে কোন মেথডের মাঝে কোন একটা ভ্যারিয়েবলকে পাস করলে উক্ত ভ্যারিয়েবলের মানটি কেবল মাত্র পাস হয়ে যাচ্ছে কিন্তু যে ভ্যারিয়েবলটি পাস করা হয়েছে তার সাথে সম্পর্ক ছিন্ন হয়ে যাচ্ছে তবে সেটাকে বলে পাস বাই ভ্যালু ।



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

পাস বাই ভ্যালুঃ
public class ReferenceTest {

    private int x = 5;

    private void increment(int x) {
        x++;
        System.out.println("Incremented Value: " + x);
    }

    public static void main(String[] args) {

        ReferenceTest rt = new ReferenceTest();
        rt.increment(rt.x);
        System.out.println("Original Value: " + rt.x);
    }
}

আউটপুটঃ
Incremented Value: 6
Original Value: 5


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

পাস বাই রেফারেন্সঃ
class ReferredObject {

    private int id;

    public ReferredObject(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

public class ReferenceTest {

    private void increment(ReferredObject ro) {

        ro.setId(ro.getId() + 1);
        System.out.println("Incremented Value: " + ro.getId());
    }

    public static void main(String[] args) {

        ReferenceTest rt = new ReferenceTest();
        ReferredObject ro = new ReferredObject(5);
        rt.increment(ro);
        System.out.println("Original Value: " + ro.getId());
    }
}

আউটপুটঃ
Incremented Value: 6
Original Value: 6


উপরোক্ত ছোট্ট প্রোগ্রামটিতে দেখা যাচ্ছে যে আমরা ReferredObject ক্লাসের একটি অবজেক্টকে increment মেথডের মাঝে পাস করে উক্ত অবজেক্টের ভ্যালু চেঞ্জ করলে সেটা যেখান থেকে কল করা হয়েছিল সেখানেও চেঞ্জ হয়ে যাচ্ছে । তার মানে এটা পাস বাই রেফারেন্স । (পাগলে কি না বলে!!!)

এখন তো মহা সমস্যা । জাভা কি তাহলে পাস বাই রেফারেন্স , পাস বাই ভ্যালু, দুটিই একাসাথে নাকি কোনটিই না !

আসলে জাভা প্রিমিটিভের ক্ষেত্রে পাস বাই ভ্যালু এবং অবজেক্টের ক্ষেত্রে [জানিনা!!!] বিহেভিয়ার শো করে । মানে টা কি ! ফাজলামি নাকি !!

ওকে, সব কিছুর আগে আমাদের একটি বহুল ব্যাবহৃত কিওয়ার্ডের (new) কাজ সম্পর্কে ক্লিয়ার হয়ে নিতে হবে । 'new' কিওয়ার্ডের কাজ সম্পর্কে ওরাকলের টিউটোরিয়াল সিরিজে খুব সুন্দর চিত্র সহকারে ব্যাখ্যা করে বুঝিয়ে বলা হয়েছে । সংক্ষেপে বললে 'new' অপারেটর ছাড়া কোনভাবেই মেমোরিতে নতুন কোন স্পেস অ্যালোকেট করা সম্ভব নয় । কেবল মাত্র প্রিমিটিভ ভ্যারিয়েবল ছাড়া যেকোন অবজেক্ট এমনি অ্যারের ক্ষেত্রেও 'new' অপারেটর ব্যাবহার করতেই হবে মেমোরিতে নতুন স্পেস অ্যালোকেট করতে তথা নতুন অবজেক্ট ক্রিয়েট করতে । অন্য সব ক্ষেত্রে কেবল মাত্র একই অবজেক্টের লোকেশন পয়েন্টিং করা হবে আলাদা আলাদা নামে ।


কোডঃ
class ReferredObject {

    private int id;

    public ReferredObject(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

public class ReferenceTest {

    public static void main(String[] args) {

        ReferredObject ro1 = new ReferredObject(5);
        ReferredObject ro2 = ro1;
        ro2.setId(7);
        System.out.println("Value of ro2: " + ro2.getId());
        System.out.println("Value of ro1: " + ro1.getId());
    }
}

আউটপুটঃ
Value of ro2: 7
Value of ro1: 7

এই ছোট্ট প্রোগ্রামটিতে আমরা দেখতে পারছি যে 'ro1' অবজেক্টটি মেমোরিতে ক্রিয়েট হয়েছে কারন সেটাকে ক্রিয়েট করা হয়েছে 'new' অপারেটর এবং উক্ত ক্লাসের কনস্ট্রাক্টর ব্যাবহার করে, কিন্তু 'ro2' অবজেক্টরি মেমোরিতে নতুন কোন স্পেস অ্যালোকেট করে নি বরং 'ro1' যে মেমোরি স্পেসে রেফার করে ছিল ঠিক সেটাতেই রেফার করে আছে । এজন্য 'ro2' তে কোন প্রকার চেঞ্জ করা হলে সেটা ঠিক সেই মেমোরিকে ওভাররাইড করছে যে মেমোরি 'ro1' রেফার করে রয়েছে । ওকে , এবার তবে আমরা আরেকটা কোডে চলে যাই ।

কোডঃ
class ReferredObject {

    private int id;

    public ReferredObject(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

public class ReferenceTest {

    private void increment(ReferredObject ro, int newValue){
       
        ro.setId(newValue);
    }
   
    public static void main(String[] args) {

        ReferenceTest rt = new ReferenceTest();
        ReferredObject ro1 = new ReferredObject(5);
        ReferredObject ro2 = ro1;
        rt.increment(ro2, 11);
        System.out.println("Value of ro2: " + ro2.getId());
        System.out.println("Value of ro1: " + ro1.getId());
    }
}

আউটপুটঃ
Value of ro2: 11
Value of ro1: 11

উক্ত ছোট্ট প্রোগ্রামটি সব কিছু আগের মতই তবে এখানে ReferenceTest ক্লাসের মাঝে একটা মেথড increment অ্যাড করা হয়েছে যেটি ReferredObject টাইপের একটি প্যারামিটার এবং একটি ইন্টিজার ভ্যালু নেয় । যেহেতু increment মেথডের মাঝে পাস করার সময় আমরা ReferredObject কে নিউ দিয়ে ক্রিয়েট করিনি সেহেতু সে মেমোরিতে নতুন কোন স্পেস অ্যালোকেট করবে না বরং সেই মেমোরিকেই রেফার করবে যেটি 'ro1', 'ro2' করছে । অর্থাৎ 'ro1', 'ro2' এবং increment মেথডের 'ro' একই মেমোরি সেগমেন্টকে রেফার করে রয়েছে । যেকোন একটির মিউটেশন হলে সবগুলারই মিউটেশন হবে । এবার একটু ভিন্ন ধরনের একটা কোড দেখব আমরা ।

কোডঃ
class ReferredObject {

    private int id;

    public ReferredObject(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

public class ReferenceTest {

    private ReferredObject increment(ReferredObject ro, int newValue) {

        ro.setId(newValue);
        ro = new ReferredObject(newValue);
        ro.setId(ro.getId() + newValue);
        return ro;
    }

    public static void main(String[] args) {

        ReferenceTest rt = new ReferenceTest();
        ReferredObject ro1 = new ReferredObject(5);
        ReferredObject ro2 = ro1;
        ReferredObject ro3 = rt.increment(ro2, 11);
        System.out.println("Value of ro3: " + ro3.getId());
        System.out.println("Value of ro2: " + ro2.getId());
        System.out.println("Value of ro1: " + ro1.getId());
    }
}

আউটপুটঃ
Value of ro3: 22
Value of ro2: 11
Value of ro1: 11

উক্ত কোডটিও আগের মতই তবে এখানে কিছুটা পরিবর্তন করা হয়েছে । এখানে increment মেথডের মাঝে 'ro' এর মিউটেশন ততক্ষন 'ro1' এবং 'ro2' এর ক্ষেত্রেও প্রোযোজ্য হবে যতক্ষন পর্যন্ত তারা সেম রেফারেন্স পয়েন্ট করে থাকবে । যখনই 'ro' কে 'new' অপারেটর দিয়ে নতুন করে কনস্ট্রাক্ট করা হচ্ছে তখনই সেটা 'ro1' এবং 'ro2' থেকে ডিকাপলড হয়ে নতুন মেমোরি লোকেশনে রেফার করছে । এবং সেটার যেকোন মিউটেশন আর কোনভাবেই 'ro1' এবং 'ro2' তে প্রভাব বিস্তার করতে পারবে না ।

এবার অবজেক্টের মাঝে অবজেক্টের রেফারেন্স নিয়ে হালকা পাতলা কিছু কথা বলব ।
কোডঃ
class University {

    private String universityName;

    public University(String universityName) {
        this.universityName = universityName;
    }

    public String getUniversityName() {
        return universityName;
    }

    public void setUniversityName(String universityName) {
        this.universityName = universityName;
    }
}

class ReferredObject {

    private int id;
    private University university;

    public ReferredObject(int id, University university) {
        this.id = id;
        this.university = university;
    }

    public University getUniversity() {
        return university;
    }

    public void setUniversity(University university) {
        this.university = university;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

public class ReferenceTest {

    private ReferredObject increment(ReferredObject ro, int newValue) {

        ro.setId(newValue);
        ro = new ReferredObject(newValue, ro.getUniversity());
        ro.setId(ro.getId() + newValue);
        ro.getUniversity().setUniversityName("Changed University Name");
        return ro;
    }

    public static void main(String[] args) {

        ReferenceTest rt = new ReferenceTest();
        University university = new University("Universal University");
        ReferredObject ro1 = new ReferredObject(5, university);
        ReferredObject ro2 = ro1;
        ReferredObject ro3 = rt.increment(ro2, 11);
        System.out.println("Value of ro3: " + ro3.getId() + ", University Name: " + ro3.getUniversity().getUniversityName());
        System.out.println("Value of ro2: " + ro2.getId() + ", University Name: " + ro2.getUniversity().getUniversityName());
        System.out.println("Value of ro1: " + ro1.getId() + ", University Name: " + ro1.getUniversity().getUniversityName());
    }
}

আউটপুটঃ
Value of ro3: 22, University Name: Changed University Name
Value of ro2: 11, University Name: Changed University Name
Value of ro1: 11, University Name: Changed University Name

উপরোক্ত ছোট্ট উদাহরনটিতে আমরা দেখতে পারছি যে increment মেথডের মাঝে 'ro' কে নতুন করে কনস্ট্রাক্ট করা হয়েছে । তাই 'ro' , 'ro1' এবং 'ro2' থেকে ডিকাপলড হয়ে গিয়েছে । কিন্তু 'ro' এর মাঝে আমরা university এর সেই রেফারেন্সটিই রেফার করছি যেটি 'ro1' এবং 'ro2' তে রেফার হয়ে রয়েছে । সুতরাং 'ro' তে university এর যেকোন মিউটেশন 'ro1' এবং 'ro2' এর ক্ষেত্রেও প্রোযোজ্য হবে যদিনা আমরা 'ro' তে university কে 'new' কিওয়ার্ড দিয়ে নতুন করে কনস্ট্রাক্ট না করি ।

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

কোডঃ
class ReferredObject {

    private int id;

    public ReferredObject(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

class RefInThread implements Runnable {

    private final ReferredObject reffObject;
    private final long sleepingTime;
    private final int iteration;
    private final int threadId;

    public RefInThread(ReferredObject reffObject, long sleepingTime, int iteration, int id) {
        this.reffObject = reffObject;
        this.sleepingTime = sleepingTime;
        this.iteration = iteration;
        this.threadId = id;
    }

    @Override
    public void run() {

        try {

            for (int i = 0; i < this.iteration; i++) {

                Thread.sleep(this.sleepingTime);
                this.reffObject.setId(this.reffObject.getId() + 1);
                System.out.println("Changing in : " + this.threadId + ", Value: " + this.reffObject.getId());
            }
        } catch (InterruptedException e) {

            System.err.println(e.toString());
        }
    }
}

public class ReferenceTest {

    public static void main(String[] args) {

        ReferredObject ro = new ReferredObject(5);
        new Thread(new RefInThread(ro, 500, 10, 1)).start();
        new Thread(new RefInThread(ro, 800, 5, 2)).start();
    }
}

আউটপুটঃ
Changing in : 1, Value: 6
Changing in : 2, Value: 7
Changing in : 1, Value: 8
Changing in : 1, Value: 9
Changing in : 2, Value: 10
Changing in : 1, Value: 11
Changing in : 2, Value: 12
Changing in : 1, Value: 13
Changing in : 1, Value: 14
Changing in : 2, Value: 15
Changing in : 1, Value: 16
Changing in : 2, Value: 17
Changing in : 1, Value: 18
Changing in : 1, Value: 19
Changing in : 1, Value: 20

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


এতক্ষন আপনার দেখলাম যতসব অবজেক্ট নিয়ে কথা বার্তা । প্রিমিটিভ ভ্যাল্যুর ক্ষেত্রে রেফারেন্সের উপায় তবে কি ? সেটার উত্তরের জন্য আবারো স্ট্যাকওভারফ্লোর এই লিংকটি ধরিয়ে দেই।

এবার কাজের কথায় আসি । জাভা কি পাস বাই রেফারেন্স নাকি পাস বাই ভ্যালু ? এতক্ষন আমরা যে উদাহরন গুলা দেখলাম সেটা যদি পাস বাই ভ্যালুর উদাহরন মনে হয় তবে জাভা পাস বাই ভ্যালু। আর যদি সেটা মনে না হয় তবে জাভা পাস বাই রেফারেন্স। এখন কথা হল জাভা যখন কোন একটি মেথডের প্যারামিটার হিসাবে কোন অবজেক্টকে রেফার করে তবে সেটা হয় কিভাবে । আমরা জানি প্রতিটি অবজেক্ট মেমোরিতে ক্রিয়েট হওয়ার সময় সেটা নির্দিষ্ট একটি মেমোরি লোকেশনে অ্যালোকেট হয় । JVM এই মেমোরি লোকেশন গুলাকে ম্যাপ করার জন্য হ্যাশ ভ্যালু ব্যাবহার করে । প্রতিটি অবজেক্ট যাদের 'new' অপারেটর দিয়ে ইন্সট্যান্সিয়েট করা হয় তার জন্য JVM একটি ইউনিক হ্যাশ কোড জেনারেট করে । ক্ষেত্র বিশেষে কিছু এক্সেপশনাল কন্ডিশন আসতে পারে যেটা অন্য কোন একটি আর্টিকেলে আলোচনা করা হবে । String ক্লাসও এই ধরনের ব্যাতীক্রম একটা ক্লাস । এগুলা নিয়ে অন্য কোন একদিন আলোচনা করা যাবে । এবার আমরা দেখি হ্যাশ কোড আসলে কিভাবে কাজ করছে ।

কোডঃ

class University {

    private String universityName;

    public University(String universityName) {
        this.universityName = universityName;
    }

    public String getUniversityName() {
        return universityName;
    }

    public void setUniversityName(String universityName) {
        this.universityName = universityName;
    }
}

class ReferredObject {

    private int id;
    private University university;

    public ReferredObject(int id, University university) {
        this.id = id;
        this.university = university;
    }

    public University getUniversity() {
        return university;
    }

    public void setUniversity(University university) {
        this.university = university;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

public class ReferenceTest {

    private ReferredObject increment(ReferredObject ro, int newValue) {

        ro.setId(newValue);
        ro = new ReferredObject(newValue, ro.getUniversity());
        ro.setId(ro.getId() + newValue);
        ro.getUniversity().setUniversityName("Changed University Name");
        return ro;
    }

    public static void main(String[] args) {

        ReferenceTest rt = new ReferenceTest();
        University university = new University("Universal University");
        ReferredObject ro1 = new ReferredObject(5, university);
        ReferredObject ro2 = ro1;
        ReferredObject ro3 = rt.increment(ro2, 11);
        System.out.println("Value of ro3: " + ro3.getId() + ", University Name: " + ro3.getUniversity().getUniversityName());
        System.out.println("Hash of ro3: " + ro3.hashCode() + ", Hash of university: " + university.hashCode());
        System.out.println("Value of ro2: " + ro2.getId() + ", University Name: " + ro2.getUniversity().getUniversityName());
        System.out.println("Hash of ro2: " + ro2.hashCode() + ", Hash of university: " + university.hashCode());
        System.out.println("Value of ro1: " + ro1.getId() + ", University Name: " + ro1.getUniversity().getUniversityName());
        System.out.println("Hash of ro1: " + ro1.hashCode() + ", Hash of university: " + university.hashCode());
    }
}

আউটপুটঃ

Value of ro3: 22, University Name: Changed University Name
Hash of ro3: 1808253012, Hash of university: 589431969
Value of ro2: 11, University Name: Changed University Name
Hash of ro2: 1252169911, Hash of university: 589431969
Value of ro1: 11, University Name: Changed University Name
Hash of ro1: 1252169911, Hash of university: 589431969

উপরোক্ত আউটপুটে আমরা দেখতে পারছি 'ro1' এবং 'ro2' এর হ্যাশ ভ্যালু একই কিন্তু 'ro3' এর হ্যাশ ভ্যালু আলাদা। 'ro1', 'ro2', এবং 'ro3' এর সবার ক্ষেত্রেই university এর হ্যাশ ভ্যালু একই । আসলে এই হ্যাশ ভ্যালুই হল JVM এর মেমোরি অ্যাড্রেসের টোকেন যে টোকেন দিয়ে JVM প্রতিটি অবজেক্টকে সিস্টেম মেমোরির সাথে ম্যাপ করে । যখন মেথডের মাধ্যমে কোন অবজেক্টকে পাস করা হয় তখন সেখানে এই হ্যাশ ভ্যালুই পাস হয় মেমোরি রেফারেন্স হিসাবে । যেহেতু রেফারেন্স হিসাবে একটা ভ্যালুকে পাস করা হচ্ছে তাই এটাকে পাস বাই ভ্যালু বলা যেতে পারে ।

আসলে জাভা যে কি সেটা বোধহয় খোদ ওরাকলও ভালোমত বলতে পারবে না সেখানে আমি কোথাকার নচ্ছার । আপনি যদি এটাকে পাস বাই রেফারেন্স হিসাবে অভিহিত করেন তবে সেটা আপনার ব্যাপার আর যদি পাস বাই ভ্যালু হিসাবে অভিহিত করেন তবে সেখানেও আমার কোন আপত্তি থাকার কথা নয় ।

যে কারনে এই আর্টিকেল লেখা সেটা হল আপনারা খারাপ ধাচের হলেও যেন কিছু জাভার প্রোগ্রাম পড়েন । অন্তত সেটুকু নিজে প্রাকটিস করেন । এটা পড়ে আহামরি কোন প্রোগ্রামার হয়ে যাবেন না সেটা আমি মনে প্রানে বিশ্বাস করি ।

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

সবাইকে ধন্যবাদ ।