আমরা জাভা-এর টাইপ সিস্টেম সর্ম্পকে জানি। আমরা জানি জাভাতে কোন প্রোগ্রাম লিখতে হলে আমাদের কে টাইপ বলে দিতে হয় । যেমন আমরা যদি একটি মেথড লিখি তাহলে মেথডটি কি টাইপ প্যারামিটার এক্সেপ্ট করবে তা বলে দিতে হয়।
তবে জাভাতে একটি চমৎকার ফিচার আছে যাতে করে আমরা অনেক সময় টাইপ না বলে দিয়েই কোড লিখতে পারি। আমরা জেনেরিকস শুরু করার আগে একটি গুরুত্বপূর্ণ তথ্য জেনে নিই- জাভা প্রোগ্রামিং ল্যাংগুয়েজ এ সব ক্লাস java.lang.Object ক্লাসটিকে ইনহেরিট করে। আমরা এটি নিয়ে অন্য কোন চ্যাপ্টারে আলোচনা করবো, তবে এখন আমাদের শুধু এই তথ্যটুকু মনে রাখলেই চলবে।
সহজ কথায় যদি বলি, তাহলে জেনেরিকস দিয়ে আমরা যখন অবজেক্ট তৈরি করবো তখন টাইপ প্যারিমিটারাইজ করতে পারি। অর্থাৎ আমরা যখন new অপারেটর দিয়ে অবজেক্ট তৈরি করবো তখন আসলে সিন্ধান্ত নেবো এটির টাইপ কি হবে। এর আগে আমরা এমন ভাবে একটা ক্লাস বা মেথড লিখে ফেলতে পারি যাতে করে এটি যে কোন টাইপ এর জন্যে কাজ করে।
বরং একটা উদাহরণ দেখা যাক-
//একটি সিম্পল ক্লাস , এখানে T হচ্ছে টাইপ প্যারামিটার যা অবজেক্ট তৈরি করার সময় রিয়েল টাইপ দিয়ে রিপ্লেস হবেpublicclassGeneric<T> {T obj;// একটা টাইপ ভ্যারিয়বল ডিক্লেয়ার করা হলো // কনস্ট্রাকটর – যে একটি রিয়েল অবজেক্ট আর্গুমেন্ট হিসেবে নেয় publicGeneric(T obj) {this.obj= obj; }// অবজেক্টটি একসেস করার জন্যে একটি মেথড publicTgetObj() {return obj; }// রানটাইমে অবজেক্ট-এর টাইপ আসলে কি , তা প্রিণ্ট করে দেখিpublicvoidshowType() {System.out.println("Type of T is: "+obj.getClass().getName()); }publicstaticvoidmain(String[] args) {// একটি ইন্টিজার এর রেফারেন্স Generic<Integer> iObj;// অবজেক্ট তৈরি করি এবং iObj রেফারেন্স এ এসাইন করি এবং কনস্ট্রাকটর আর্গুমেন্ট হিসেবে 88 পাস করি iObj =newGeneric<Integer>(88);// রানটাইম-এ তাহলে জেনেরিক ক্লাসটিতে T obj একটি ইন্টিজার হয়ে যাওয়ার কথা, প্রিন্ট করে দেখা যাক iObj.showType();int v =iObj.getObj();// ইন্টিজার ভ্যালুটি এর ভ্যালু একসেস ককরে v তে রাখা হলSystem.out.println("value: "+ v);// প্রিন্ট করি, যেখা যাক, আমরা এর ভ্যালু ঠিক ঠাক মতো পাওয়া যায় কিনা //এভাবে আমরা একটি স্ট্রিং টাইপ দিয়েও পরীক্ষা করতে পারি। Generic<String> strOb =newGeneric<String>("This is a Generics Test");strOb.showType();String str =strOb.getObj();System.out.println("value: "+ str); } }`
এই প্রোগ্রামটি যদি রান করা হয়, তাহলে নিচের আউটপুট গুলো দেখা যাবে -
Type of T is: java.lang.Integer value: 88 Type of T is: java.lang.String value: This is a Generics Test
আউটপুট গুলো থেকে বুঝা যাচ্ছে যে , আমাদের প্রোগ্রামটি সঠিক ভাবে কাজ করছে এবং একটি জেনেরিক ক্লাসে একটি ইন্টিজার এবং একটি স্ট্রিং প্যারামিটারাইজড করতে পেরেছি।
এভাবে আমরা আরও অন্যান্য টাইপ-ও প্যারামিটারাইজড করে পারি।
এবার আরও ভালভাবে এই প্রোগ্রামটি খেয়াল করা যাক-
publicclassGeneric<T> { }`
এখানেT হচ্ছে টাইপ প্যারামিটার। এটি মূলত একটি প্লেস হোল্ডার।
লক্ষ্য করুন – এর T কিন্তু <> এর মধ্যে থাকে।
আমরা সাধারণত যেভাবে ভ্যারিয়েবল ডিক্লেয়ার করি, সেভাবেই আমরা জেনেরিক্স-এ ভ্যারিয়েবল ডিক্লেয়ার করতে পারি। এর জন্যে আলাদা কোন নিয়ম নেই।
T obj;
এখানে T অবজেক্ট তৈরি করার সময় একটি রিয়েল অবজেক্ট অর্থাৎ আমরা যে অবজেক্ট প্যারিমিটারাইড করবো তা দ্বারা প্রতিস্থাপিত(replaced) হবে ।
আমরা জানি যে জাভা একটি স্ট্যাটিক টাইপ অর্থাৎ টাইপ সেইফ ল্যাংগুয়েজ। অর্থাৎ জাভা কোড কম্পাইল করার সময় এর টাইপ ইনফরমেশন ঠিক ঠাক আছে কিনা তা চেক করে নেয়।
অর্থাৎ -
Generic<Integer> iObj;
এখানে iObj একটি ইন্টিজার প্যারমিটাইরজড অবজেক্ট রেফারেন্স ।
iObj =newGeneric<Double>(88.0); // Error!`
এখন অবজেক্ট তৈরি করার সময় যদি ডাবল প্যারমিটাইরজড করি এবং iObj তে এসাইন করি, তাহলে
Error:(24,16) java: incompatible types:Generic<java.lang.Double> cannot be converted to Generic<java.lang.Integer>`
কম্পাইল করার সময় উপরের ইররটি দেখতে পাবো।
জেনেরিকস শুধুমাত্র অজজেক্ট নিয়ে কাজ করে-
আমারা জানি যে, জাভা দুই ধরণের টাইপ সাপোর্ট করে- PrimitiveType এবং ReferenceType। জেনেরিকস শুধুমাত্র ReferenceType অর্থাৎ শুধু মাত্র অবজেক্ট নিয়ে কাজ রে।
তাই-
Generic<int> intObj =newGeneric<int>(50);`
এই স্ট্যাটমেন্ট টি ভ্যালিড নয়। অর্থাৎ প্রিমিটিভ টাই এর ক্ষেত্রে জেনেরিকস কাজ করবে না।
আমরা চাইলে একাধিক জেনেরিক টাইপ প্যারমিটাইরজড করতে পারি।
এবার তাহলে দুটি টাইপ প্যারামিটার নিয়ে একটি উদাহরণ দেখা যাক-
publicclassTuple<X,Y> {privateX x;privateY y;publicTuple(X x,Y y) {this.x= x;this.y= y; }publicXgetX() {return x; }publicYgetY() {return y; }publicvoidshowTypes() {System.out.println("Type of T is "+x.getClass().getName() +" and Value: "+ x);System.out.println("Type of V is "+y.getClass().getName() +" and Value: "+ y); }publicstaticvoidmain(String[] args) {Tuple<String,String> tuple =newTuple<String,String>("Hello","world");tuple.showTypes();Tuple<String,Integer> person =newTuple<>("Rahim",45);person.showTypes(); } }
এই প্রোগ্রামটি রান করলে নিচের আউটপুট-টি পাওয়া যাবে –
Type of T is java.lang.String and Value: Hello Type of V is java.lang.String and Value: world Type of T is java.lang.String and Value: Rahim Type of V is java.lang.Integer and Value: 45
একটি টাপলের মধ্যে আমরা চাইলে আরেকটি টাপল রাখে পারি - নিচের উদাহরণটি চমৎকার-
Tuple<String, Tuple<Integer, Integer>> tupleInsideTuple = new Tuple<String, Tuple<Integer, Integer>>("Tuple", new Tuple<Integer, Integer>(45, 89));
তবে আমরা যদি জাভা ৭ অথবা ৮ ব্যবহার করি তাহলে উপরের লাইনটি সংক্ষিপ্তভাবে লিখতে পারি –
অর্থাৎ জেনেরিকস লেখার সময় বাম পাশে টাইপ প্যারামিটার ইনফরমেশন গুলো লিখলে ডান পাশে লিখতে হয় না । এটি অটোম্যাটিক্যালী ইনফার করতে পারে।
Bounded Types
আমরা উপরে দুটি উদাহরণ দেখেছি যেগুলোতে আমরা যে কোন ধরণের টাইপ প্যারামিটারাইউজড করতে পারি। কিন্তু কখনো কখনো আমাদের টাইপ restrict করতে হয়। যেমন- আমরা একটি জেনেরিক ক্লাস লিখতে চাই যা কিনা একটি এরে-তে রাখা কতগুলো নাম্বার-এর গড়(average) রিটার্ন করবে এবং আমরা চাই, এই এরে তে যে কোন ধরণের নাম্বার থাকতে পারে, যেমন- ইন্টিজার, ফ্লোটিং পয়েন্ট, ডাবল ইত্যাদি। আমরা টাইপ প্যারামিটার দিয়ে বলে দিতে চাই কখন কোনটা থাকবে। উদারহরণ দেখা যাক-
publicclassStats<T> {T[] nums;publicStats(T[] nums) {this.nums= nums; }// Return type double in all cases.doubleaverage() {double sum =0.0;for (T num : nums) { sum +=num.doubleValue(); // Error!!! }return sum /nums.length; } }`
এভারেজ ক্যালকুলেট করার জন্য আমাদের এভারেজ মেথড সবসময় এরে থেকে ডাবল ভ্যালু এক্সেপেক্ট করে। কিন্তু আমাদের এরে-এর টাইপ যেহেতু যে কোন রকম হতে পারে, সুতরাং সব অবজেক্ট থেকে ডাবল ভ্যালু পাওয়ার উপায় নেই।
ইনফ্যাক্ট এই ক্লাসটি কিন্তু কম্পাইল হবে না।
এই ক্লাসটিতে আমরা একটি restriction এড করতে পারি যাতে করে এই টাইপ প্যারামিটার শুধুমাত্র নাম্বার(ইন্টিজার, ফ্লোটিং পয়েন্ট,ডাবল) হবে, নতুবা এটি কাজ করবে না।
আমরা জানি যে সব নিউমেরিক অবজেক্ট গুলোর সুপার ক্লাস হচ্ছে Number. এবং Number এ doubleValue() মেথড ডিফাইন করা আছে। সুতরাং আমাদের ক্লাসটিকে একটু পরিবর্তন করি।
publicclassStats<TextendsNumber> {T[] nums;publicStats(T[] nums) {this.nums= nums; }// Return type double in all cases.doubleaverage() {double sum =0.0;for (T num : nums) { sum +=num.doubleValue(); // Error!!! }return sum /nums.length; } }
একটু লক্ষ্য করুন-
publicclassStats<TextendsNumber>{ }
আমরা ক্লাস ডেফিনেশনে আমাদের টাইপ প্লেসহোল্ডার T নাম্বারকে extend করে। এটি আমাদের টাইপ প্যারামিটার পাস করতে restrict করে । অর্থাৎ আমরা শুধু মাত্র সেসব টাইপ পাস করতে পারবো যারা Number এর সাব টাইপ।
সুতরাং আমাদের এই Stats ক্লাস এখন Integer, Double, Float, Long, Short, BigInteger, BigDecimal, Byte ইত্যাদি অবজেক্ট এর জন্যে কাজ করবে।
সুতরাং দেখা যাচ্ছে যে, জেনেরিকস এর সুবিধা ব্যবহার করে আমরা এই স্ট্যাট ক্লাসটি আলাদা আলাদা করে অনেকগুলো না লিখে একটি দিয়েই কাজ করে ফেলা সম্ভব হল।
Wildcard Arguments
নিচের উদাহরণটি লক্ষ্য করি-
ArrayList<Object> lst =newArrayList<String>();
এটি যদি কম্পাইল করতে চেষ্টা করি, তাহলে কম্পাইলার incompatible types ইরর দেবে। কিন্তু আমরা জানি যে, সকল অবজেক্ট এর সুপার ক্লাস Object। তাছাড়া আমরা polymorphism থেকে জানি যে
আমরা সাব ক্লাসের রেফারেন্স কে সুপার ক্লাসের রেফারেন্স এ এসাইন করতে পারি। সুতরাং উপরের স্ট্যাটমেন্ট-টি কাজ করার কথা।
২ নাম্বার লাইনটি কাজ করছে না । যদিও বা এটি কাজ করে এবং আর্বিট্রারি কোন একটি অবজেক্ট যদি objLst এড করা হয় তাহলে কিন্তু strList করাপ্টেড হয়ে যাবে এবং সেটি আর স্ট্রিং থাকবে না।
ধরা যাক, আমরা একটা print মেথড লিখতে চাই যা কিনা একটি লিস্ট এর ইলিমেন্ট গুলো প্রিন্ট করে।
publicstaticvoidprint(List<Object> lst) { // accept List of Objects only,// not List of subclasses of objectfor (Object o : lst) {System.out.println(o); } }
এটি কিন্তু শুধুমাত্র List<Object> একসেপ্ট করবে , List<String> অথবা List<Integer> করবে না।
এই সমস্যা দূর করার জন্যে জাভাতে একটি একটি অপারেটর ব্যবহার করা হয় – যার নাম wildcard (?)।
আমরা যদি আমাদের print() মেথডটি নিচের মতো করে লিখি, তাহলে কিন্তু আমাদের সমস্যা দূর হয়ে যাবে।
publicstaticvoidprint(List<?> lst) { // accept List of Objects only,// not List of subclasses of objectfor (Object o : lst) {System.out.println(o); } }
List<?> lst এর মানে হচ্ছে আমরা এর টাইপ আমাদের জানা নেই, এটি যে কোন টাইপ হতে পারে। যেহেতু সব টাইপ এর সুপার ক্লাস Object সুতরাং এটি যেকোন টাইপ এর জন্যে কাজ করবে।
Bounded Types এর মতো আমরা Wildcard Arguments কেও Bounded করে ফেলতে পারি ।
জেনেরিক মেথড-এ রিটার্নটাইপ এর আগে টাইপ-প্লেস হোল্ডার <> লিখতে হয়।
আমরা এবার চেষ্টা করবো কিভাবে আমরা একটি জেনেরিক সিংগলি লিংকলিস্ট লিখতে পারি --
/** * @author Bazlur Rahman Rokon * @date 2/4/15. */publicclassSinglyLinkedList<Type> {privatelong size;privateNode<Type> head;privateNode<Type> tail;publicvoidaddFirst(Type value) {addFirst(newNode<>(value)); }publicvoidaddLast(Type value) {addLast(newNode<>(value)); }privatevoidaddLast(Node<Type> node) {if (size ==0) { head = node; } else {tail.setNext(node); } tail = node; size++; }publicvoidaddFirst(Node<Type> node) {Node<Type> temp = head; head = node;head.setNext(temp); size++;if (size ==1) { tail = head; } }publicNode<Type> getHead() {return head; }publicNode<Type> getTail() {return tail; }publicvoidremoveFirst() {if (size !=0) { head =head.getNext(); size--; }if (size ==0) { tail =null; } }publicvoidremoveLast() {if (size !=0) {if (size ==1) { head =null; tail =null; } else {Node<Type> current = head;while (current.getNext() != tail) { current =current.getNext(); }current.setNext(null); tail = current; } size--; } }publicTypegetFirst() {returngetHead().getValue(); }// four scenario// 1. empty list- do nothing// 2. single node : ( previous is null)// 3. Many nodes// a. node to remove is first node// b. node to remove is the middle or lastpublicbooleanremove(Type type) {Node<Type> prev =null;Node<Type> current = head;while (current !=null) {if (current.getValue().equals(type)) {if (prev !=null) {// just skip the current node. it works fineprev.setNext(current.getNext());if (current.getNext() ==null) { tail = prev; } size--; } else {removeFirst(); }returntrue; } prev = current; current =current.getNext(); }returnfalse; }publiclonggetSize() {return size; }publicvoidprint() {System.out.print("Total elements : "+ size +" -> ");Node node = head;while (node !=null) {System.out.print(node.getValue().toString() +" ,"); node =node.getNext(); }System.out.println(); }publicvoidclear() {for (Node<Type> x = head; x !=null; ) {Node<Type> next =x.next;x.next=null;x.value=null; x = next; } head = tail =null; size =0; }privateclassNode<Type> {privateType value;privateNode<Type> next;publicNode(Type value) {this.value= value; }publicTypegetValue() {return value; }publicvoidsetValue(Type value) {this.value= value; }publicNode<Type> getNext() {return next; }publicvoidsetNext(Node<Type> next) {this.next= next; } } }
এবার আমরা এটিকে রান করে দেখি-
/** * @author Bazlur Rahman Rokon * @date 2/4/15. */publicclassLinkedListDemo {publicstaticvoidmain(String[] args) {SinglyLinkedList<Integer> integers =newSinglyLinkedList<>();integers.addFirst(4);integers.addFirst(3);integers.addFirst(2);integers.addFirst(1);integers.print();System.out.println("Remove first and last elements..");integers.removeFirst();integers.removeLast();integers.print();System.out.println("add elements at last ");integers.addLast(5);integers.addLast(6);integers.addLast(7);integers.print();SinglyLinkedList<String> stringLinkedList =newSinglyLinkedList<>();stringLinkedList.addFirst("abcd");stringLinkedList.addFirst("efgh");stringLinkedList.addFirst("ijkl");stringLinkedList.addFirst("mnop");stringLinkedList.addFirst("qrst");stringLinkedList.print(); } }
Output:
Total elements : 4 - 1 ,2 ,3 ,4 , Remove first and last elements.. Total elements : 2 - 2 ,3 , add elements at last Total elements : 5 - 2 ,3 ,5 ,6 ,7 , Total elements : 5 - qrst ,mnop ,ijkl ,efgh ,abcd ,