> For the complete documentation index, see [llms.txt](https://java.howtocode.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://java.howtocode.dev/8.md). # পাঠ ৮: জেনেরিকস ## জেনেরিকস ইন জাভা (Generics in Java) আমরা জাভা-এর টাইপ সিস্টেম সর্ম্পকে জানি। আমরা জানি জাভাতে কোন প্রোগ্রাম লিখতে হলে আমাদের কে টাইপ বলে দিতে হয় । যেমন আমরা যদি একটি মেথড লিখি তাহলে মেথডটি কি টাইপ প্যারামিটার এক্সেপ্ট করবে তা বলে দিতে হয়। তবে জাভাতে একটি চমৎকার ফিচার আছে যাতে করে আমরা অনেক সময় টাইপ না বলে দিয়েই কোড লিখতে পারি। আমরা জেনেরিকস শুরু করার আগে একটি গুরুত্বপূর্ণ তথ্য জেনে নিই- জাভা প্রোগ্রামিং ল্যাংগুয়েজ এ সব ক্লাস **java.lang.Object** ক্লাসটিকে ইনহেরিট করে। আমরা এটি নিয়ে অন্য কোন চ্যাপ্টারে আলোচনা করবো, তবে এখন আমাদের শুধু এই তথ্যটুকু মনে রাখলেই চলবে। সহজ কথায় যদি বলি, তাহলে জেনেরিকস দিয়ে আমরা যখন অবজেক্ট তৈরি করবো তখন টাইপ প্যারিমিটারাইজ করতে পারি। অর্থাৎ আমরা যখন `new` অপারেটর দিয়ে অবজেক্ট তৈরি করবো তখন আসলে সিন্ধান্ত নেবো এটির টাইপ কি হবে। এর আগে আমরা এমন ভাবে একটা ক্লাস বা মেথড লিখে ফেলতে পারি যাতে করে এটি যে কোন টাইপ এর জন্যে কাজ করে। বরং একটা উদাহরণ দেখা যাক- ```java //একটি সিম্পল ক্লাস , এখানে T হচ্ছে টাইপ প্যারামিটার যা অবজেক্ট তৈরি করার সময় রিয়েল টাইপ দিয়ে রিপ্লেস হবে public class Generic { T obj; // একটা টাইপ ভ্যারিয়বল ডিক্লেয়ার করা হলো // কনস্ট্রাকটর – যে একটি রিয়েল অবজেক্ট আর্গুমেন্ট হিসেবে নেয় public Generic(T obj) { this.obj = obj; } // অবজেক্টটি একসেস করার জন্যে একটি মেথড public T getObj() { return obj; } // রানটাইমে অবজেক্ট-এর টাইপ আসলে কি , তা প্রিণ্ট করে দেখি public void showType() { System.out.println("Type of T is: " + obj.getClass().getName()); } public static void main(String[] args) { // একটি ইন্টিজার এর রেফারেন্স Generic iObj; // অবজেক্ট তৈরি করি এবং iObj রেফারেন্স এ এসাইন করি এবং কনস্ট্রাকটর আর্গুমেন্ট হিসেবে 88 পাস করি iObj = new Generic(88); // রানটাইম-এ তাহলে জেনেরিক ক্লাসটিতে T obj একটি ইন্টিজার হয়ে যাওয়ার কথা, প্রিন্ট করে দেখা যাক iObj.showType(); int v = iObj.getObj(); // ইন্টিজার ভ্যালুটি এর ভ্যালু একসেস ককরে v তে রাখা হল System.out.println("value: " + v); // প্রিন্ট করি, যেখা যাক, আমরা এর ভ্যালু ঠিক ঠাক মতো পাওয়া যায় কিনা //এভাবে আমরা একটি স্ট্রিং টাইপ দিয়েও পরীক্ষা করতে পারি। Generic strOb = new Generic("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 আউটপুট গুলো থেকে বুঝা যাচ্ছে যে , আমাদের প্রোগ্রামটি সঠিক ভাবে কাজ করছে এবং একটি জেনেরিক ক্লাসে একটি ইন্টিজার এবং একটি স্ট্রিং প্যারামিটারাইজড করতে পেরেছি। এভাবে আমরা আরও অন্যান্য টাইপ-ও প্যারামিটারাইজড করে পারি। এবার আরও ভালভাবে এই প্রোগ্রামটি খেয়াল করা যাক- ```java public class Generic { } ` ``` এখানে`T` হচ্ছে টাইপ প্যারামিটার। এটি মূলত একটি প্লেস হোল্ডার। লক্ষ্য করুন – এর `T` কিন্তু `<>` এর মধ্যে থাকে। আমরা সাধারণত যেভাবে ভ্যারিয়েবল ডিক্লেয়ার করি, সেভাবেই আমরা জেনেরিক্স-এ ভ্যারিয়েবল ডিক্লেয়ার করতে পারি। এর জন্যে আলাদা কোন নিয়ম নেই। ```java T obj; ``` এখানে T অবজেক্ট তৈরি করার সময় একটি রিয়েল অবজেক্ট অর্থাৎ আমরা যে অবজেক্ট প্যারিমিটারাইড করবো তা দ্বারা প্রতিস্থাপিত(replaced) হবে । আমরা জানি যে জাভা একটি স্ট্যাটিক টাইপ অর্থাৎ টাইপ সেইফ ল্যাংগুয়েজ। অর্থাৎ জাভা কোড কম্পাইল করার সময় এর টাইপ ইনফরমেশন ঠিক ঠাক আছে কিনা তা চেক করে নেয়। অর্থাৎ - ```java Generic iObj; ``` এখানে `iObj` একটি ইন্টিজার প্যারমিটাইরজড অবজেক্ট রেফারেন্স । ```java iObj = new Generic(88.0); // Error! ` ``` এখন অবজেক্ট তৈরি করার সময় যদি ডাবল প্যারমিটাইরজড করি এবং `iObj` তে এসাইন করি, তাহলে ```java Error:(24, 16) java: incompatible types: Generic cannot be converted to Generic ` ``` কম্পাইল করার সময় উপরের ইররটি দেখতে পাবো। ## জেনেরিকস শুধুমাত্র অজজেক্ট নিয়ে কাজ করে- আমারা জানি যে, জাভা দুই ধরণের টাইপ সাপোর্ট করে- `PrimitiveType` এবং `ReferenceType`। জেনেরিকস শুধুমাত্র ReferenceType অর্থাৎ শুধু মাত্র অবজেক্ট নিয়ে কাজ রে। তাই- ```java Generic intObj = new Generic(50); ` ``` এই স্ট্যাটমেন্ট টি ভ্যালিড নয়। অর্থাৎ প্রিমিটিভ টাই এর ক্ষেত্রে জেনেরিকস কাজ করবে না। জেনেরিক ক্লাস এর সিনট্যাক্স- ```java class class-name {} ` ``` জেনেরিক ক্লাস ইনসটেনসিয়েট করার সিনটেক্স- ```java class-name var-name = new class-name(cons-arg-list); ` ``` আমরা চাইলে একাধিক জেনেরিক টাইপ প্যারমিটাইরজড করতে পারি। এবার তাহলে দুটি টাইপ প্যারামিটার নিয়ে একটি উদাহরণ দেখা যাক- ```java public class Tuple { private X x; private Y y; public Tuple(X x, Y y) { this.x = x; this.y = y; } public X getX() { return x; } public Y getY() { return y; } public void showTypes() { 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); } public static void main(String[] args) { Tuple tuple = new Tuple("Hello", "world"); tuple.showTypes(); Tuple person = new Tuple<>("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 একটি টাপলের মধ্যে আমরা চাইলে আরেকটি টাপল রাখে পারি - নিচের উদাহরণটি চমৎকার- ```java Tuple> tupleInsideTuple = new Tuple>("Tuple", new Tuple(45, 89)); ``` তবে আমরা যদি জাভা ৭ অথবা ৮ ব্যবহার করি তাহলে উপরের লাইনটি সংক্ষিপ্তভাবে লিখতে পারি – ```java Tuple> tupleInsideTuple = new Tuple<>("Tuple", new Tuple<>(45, 89)); ` ``` `জাভা ৭` এ একটি নতুন অপারেটর সংযুক্ত হয়েছে যাকে বলা হয় ডায়মন্ড অপারেটর। এটি ব্যবহার করে আমরা জেনেরকস এ verbosity কিছুটা কমানো যায়। অর্থাৎ ```java Map> anagrams = new HashMap>(); ``` এই স্ট্যাটমেন্ট-টি অনেকটাই বড়। এটি আমরা এভাবে লিখতে পারি – ```java Map> anagrams = new HashMap<>(); ``` অর্থাৎ জেনেরিকস লেখার সময় বাম পাশে টাইপ প্যারামিটার ইনফরমেশন গুলো লিখলে ডান পাশে লিখতে হয় না । এটি অটোম্যাটিক্যালী ইনফার করতে পারে। ## Bounded Types আমরা উপরে দুটি উদাহরণ দেখেছি যেগুলোতে আমরা যে কোন ধরণের টাইপ প্যারামিটারাইউজড করতে পারি। কিন্তু কখনো কখনো আমাদের টাইপ restrict করতে হয়। যেমন- আমরা একটি জেনেরিক ক্লাস লিখতে চাই যা কিনা একটি এরে-তে রাখা কতগুলো নাম্বার-এর গড়(average) রিটার্ন করবে এবং আমরা চাই, এই এরে তে যে কোন ধরণের নাম্বার থাকতে পারে, যেমন- ইন্টিজার, ফ্লোটিং পয়েন্ট, ডাবল ইত্যাদি। আমরা টাইপ প্যারামিটার দিয়ে বলে দিতে চাই কখন কোনটা থাকবে। উদারহরণ দেখা যাক- ```java public class Stats { T[] nums; public Stats(T[] nums) { this.nums = nums; } // Return type double in all cases. double average() { double sum = 0.0; for (T num : nums) { sum += num.doubleValue(); // Error!!! } return sum / nums.length; } } ` ``` এভারেজ ক্যালকুলেট করার জন্য আমাদের এভারেজ মেথড সবসময় এরে থেকে ডাবল ভ্যালু এক্সেপেক্ট করে। কিন্তু আমাদের এরে-এর টাইপ যেহেতু যে কোন রকম হতে পারে, সুতরাং সব অবজেক্ট থেকে ডাবল ভ্যালু পাওয়ার উপায় নেই। ইনফ্যাক্ট এই ক্লাসটি কিন্তু কম্পাইল হবে না। এই ক্লাসটিতে আমরা একটি restriction এড করতে পারি যাতে করে এই টাইপ প্যারামিটার শুধুমাত্র নাম্বার(ইন্টিজার, ফ্লোটিং পয়েন্ট,ডাবল) হবে, নতুবা এটি কাজ করবে না। আমরা জানি যে সব নিউমেরিক অবজেক্ট গুলোর সুপার ক্লাস হচ্ছে `Number`. এবং `Number` এ `doubleValue()` মেথড ডিফাইন করা আছে। সুতরাং আমাদের ক্লাসটিকে একটু পরিবর্তন করি। ```java public class Stats { T[] nums; public Stats(T[] nums) { this.nums = nums; } // Return type double in all cases. double average() { double sum = 0.0; for (T num : nums) { sum += num.doubleValue(); // Error!!! } return sum / nums.length; } } ``` একটু লক্ষ্য করুন- ```java public class Stats{ } ``` আমরা ক্লাস ডেফিনেশনে আমাদের টাইপ প্লেসহোল্ডার `T` নাম্বারকে extend করে। এটি আমাদের টাইপ প্যারামিটার পাস করতে restrict করে । অর্থাৎ আমরা শুধু মাত্র সেসব টাইপ পাস করতে পারবো যারা `Number` এর সাব টাইপ। সুতরাং আমাদের এই `Stats` ক্লাস এখন `Integer`, `Double`, `Float`, `Long`, `Short`, `BigInteger`, `BigDecimal`, `Byte` ইত্যাদি অবজেক্ট এর জন্যে কাজ করবে। সুতরাং দেখা যাচ্ছে যে, জেনেরিকস এর সুবিধা ব্যবহার করে আমরা এই স্ট্যাট ক্লাসটি আলাদা আলাদা করে অনেকগুলো না লিখে একটি দিয়েই কাজ করে ফেলা সম্ভব হল। ## Wildcard Arguments নিচের উদাহরণটি লক্ষ্য করি- ```java ArrayList lst = new ArrayList(); ``` এটি যদি কম্পাইল করতে চেষ্টা করি, তাহলে কম্পাইলার incompatible types ইরর দেবে। কিন্তু আমরা জানি যে, সকল অবজেক্ট এর সুপার ক্লাস `Object`। তাছাড়া আমরা polymorphism থেকে জানি যে\ আমরা সাব ক্লাসের রেফারেন্স কে সুপার ক্লাসের রেফারেন্স এ এসাইন করতে পারি। সুতরাং উপরের স্ট্যাটমেন্ট-টি কাজ করার কথা। নিচের উদাহরণ দুটি লক্ষ্য করি - ```java List strLst = new ArrayList(); // 1 List objLst = strList; // 2 - Compilation Error ` ``` ২ নাম্বার লাইনটি কাজ করছে না । যদিও বা এটি কাজ করে এবং আর্বিট্রারি কোন একটি অবজেক্ট যদি `objLst` এড করা হয় তাহলে কিন্তু `strList` করাপ্টেড হয়ে যাবে এবং সেটি আর স্ট্রিং থাকবে না। ধরা যাক, আমরা একটা print মেথড লিখতে চাই যা কিনা একটি লিস্ট এর ইলিমেন্ট গুলো প্রিন্ট করে। ```java public static void print(List lst) { // accept List of Objects only, // not List of subclasses of object for (Object o : lst) { System.out.println(o); } } ``` এটি কিন্তু শুধুমাত্র `List` একসেপ্ট করবে , `List` অথবা `List` করবে না। উদাহরণ- ```java public static void main(String[] args) { List objLst = new ArrayList(); objLst.add(new Integer(55)); printList(objLst); // matches List strLst = new ArrayList(); strLst.add("one"); printList(strLst); // compilation error } ``` এই সমস্যা দূর করার জন্যে জাভাতে একটি একটি অপারেটর ব্যবহার করা হয় – যার নাম wildcard (?)। আমরা যদি আমাদের `print()` মেথডটি নিচের মতো করে লিখি, তাহলে কিন্তু আমাদের সমস্যা দূর হয়ে যাবে। ```java public static void print(List lst) { // accept List of Objects only, // not List of subclasses of object for (Object o : lst) { System.out.println(o); } } ``` `List lst` এর মানে হচ্ছে আমরা এর টাইপ আমাদের জানা নেই, এটি যে কোন টাইপ হতে পারে। যেহেতু সব টাইপ এর সুপার ক্লাস Object সুতরাং এটি যেকোন টাইপ এর জন্যে কাজ করবে। Bounded Types এর মতো আমরা Wildcard Arguments কেও Bounded করে ফেলতে পারি । উদাহরণ - ```java public static void process(List list) { /* ... */ } ``` এটি শুধু মাত্রে `Foo` এর সাব ক্লাস গুলো কে প্রসেস করতে পারবে। একে Upper Bounded Wildcards বলে । আমরা যদি এমন কোন মেথড লিখতে চাই যা শুধু মাত্র Integer, Number, and Object প্রসেস করবে অর্থাৎ Integerএবং এর সুপার ক্লাস প্রসেস করবে তাহলে - ```java public static void addNumbers(List list) { } ``` একে Lower Bounded Wildcards বলে। ## Generic Methods আমরা মূলত এতোক্ষণ জেনেরিক ক্লাস নিয়ে কথা বলেছি। আমরা একটি ক্লাসকে জেনেরিক না করে শুধুমাত্রে এর একটি বা একাধিক মেথড কে জেনেরিক করে লিখতে পারি। উদহরণ- ```java public class Util { // Generic static method public static boolean compare(Pair p1, Pair p2) { return p1.getKey().equals(p2.getKey()) && p1.getValue().equals(p2.getValue()); } } ``` এটি একটি জেনেরিকম মেথড। জেনেরিক মেথড-এ রিটার্নটাইপ এর আগে টাইপ-প্লেস হোল্ডার `<>` লিখতে হয়। আমরা এবার চেষ্টা করবো কিভাবে আমরা একটি জেনেরিক সিংগলি লিংকলিস্ট লিখতে পারি -- ‌ ```java /** * @author Bazlur Rahman Rokon * @date 2/4/15. */ public class SinglyLinkedList { private long size; private Node head; private Node tail; public void addFirst(Type value) { addFirst(new Node<>(value)); } public void addLast(Type value) { addLast(new Node<>(value)); } private void addLast(Node node) { if (size == 0) { head = node; } else { tail.setNext(node); } tail = node; size++; } public void addFirst(Node node) { Node temp = head; head = node; head.setNext(temp); size++; if (size == 1) { tail = head; } } public Node getHead() { return head; } public Node getTail() { return tail; } public void removeFirst() { if (size != 0) { head = head.getNext(); size--; } if (size == 0) { tail = null; } } public void removeLast() { if (size != 0) { if (size == 1) { head = null; tail = null; } else { Node current = head; while (current.getNext() != tail) { current = current.getNext(); } current.setNext(null); tail = current; } size--; } } public Type getFirst() { return getHead().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 last public boolean remove(Type type) { Node prev = null; Node current = head; while (current != null) { if (current.getValue().equals(type)) { if (prev != null) { // just skip the current node. it works fine prev.setNext(current.getNext()); if (current.getNext() == null) { tail = prev; } size--; } else { removeFirst(); } return true; } prev = current; current = current.getNext(); } return false; } public long getSize() { return size; } public void print() { System.out.print("Total elements : " + size + " -> "); Node node = head; while (node != null) { System.out.print(node.getValue().toString() + " ,"); node = node.getNext(); } System.out.println(); } public void clear() { for (Node x = head; x != null; ) { Node next = x.next; x.next = null; x.value = null; x = next; } head = tail = null; size = 0; } private class Node { private Type value; private Node next; public Node(Type value) { this.value = value; } public Type getValue() { return value; } public void setValue(Type value) { this.value = value; } public Node getNext() { return next; } public void setNext(Node next) { this.next = next; } } } ``` এবার আমরা এটিকে রান করে দেখি- ```java /** * @author Bazlur Rahman Rokon * @date 2/4/15. */ public class LinkedListDemo { public static void main(String[] args) { SinglyLinkedList integers = new SinglyLinkedList<>(); 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 stringLinkedList = new SinglyLinkedList<>(); 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 ,