আমরা জাভা-এর টাইপ সিস্টেম সর্ম্পকে জানি। আমরা জানি জাভাতে কোন প্রোগ্রাম লিখতে হলে আমাদের কে টাইপ বলে দিতে হয় । যেমন আমরা যদি একটি মেথড লিখি তাহলে মেথডটি কি টাইপ প্যারামিটার এক্সেপ্ট করবে তা বলে দিতে হয়।
তবে জাভাতে একটি চমৎকার ফিচার আছে যাতে করে আমরা অনেক সময় টাইপ না বলে দিয়েই কোড লিখতে পারি। আমরা জেনেরিকস শুরু করার আগে একটি গুরুত্বপূর্ণ তথ্য জেনে নিই- জাভা প্রোগ্রামিং ল্যাংগুয়েজ এ সব ক্লাস java.lang.Object ক্লাসটিকে ইনহেরিট করে। আমরা এটি নিয়ে অন্য কোন চ্যাপ্টারে আলোচনা করবো, তবে এখন আমাদের শুধু এই তথ্যটুকু মনে রাখলেই চলবে।
সহজ কথায় যদি বলি, তাহলে জেনেরিকস দিয়ে আমরা যখন অবজেক্ট তৈরি করবো তখন টাইপ প্যারিমিটারাইজ করতে পারি। অর্থাৎ আমরা যখন new অপারেটর দিয়ে অবজেক্ট তৈরি করবো তখন আসলে সিন্ধান্ত নেবো এটির টাইপ কি হবে। এর আগে আমরা এমন ভাবে একটা ক্লাস বা মেথড লিখে ফেলতে পারি যাতে করে এটি যে কোন টাইপ এর জন্যে কাজ করে।
বরং একটা উদাহরণ দেখা যাক-
//একটি সিম্পল ক্লাস , এখানে T হচ্ছে টাইপ প্যারামিটার যা অবজেক্ট তৈরি করার সময় রিয়েল টাইপ দিয়ে রিপ্লেস হবেpublicclassGeneric<T>{T obj; // একটা টাইপ ভ্যারিয়বল ডিক্লেয়ার করা হলো // কনস্ট্রাকটর – যে একটি রিয়েল অবজেক্ট আর্গুমেন্ট হিসেবে নেয় publicGeneric(Tobj){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
আউটপুট গুলো থেকে বুঝা যাচ্ছে যে , আমাদের প্রোগ্রামটি সঠিক ভাবে কাজ করছে এবং একটি জেনেরিক ক্লাসে একটি ইন্টিজার এবং একটি স্ট্রিং প্যারামিটারাইজড করতে পেরেছি।
এভাবে আমরা আরও অন্যান্য টাইপ-ও প্যারামিটারাইজড করে পারি।
এবার আরও ভালভাবে এই প্রোগ্রামটি খেয়াল করা যাক-
এখানেT হচ্ছে টাইপ প্যারামিটার। এটি মূলত একটি প্লেস হোল্ডার।
লক্ষ্য করুন – এর T কিন্তু <> এর মধ্যে থাকে।
আমরা সাধারণত যেভাবে ভ্যারিয়েবল ডিক্লেয়ার করি, সেভাবেই আমরা জেনেরিক্স-এ ভ্যারিয়েবল ডিক্লেয়ার করতে পারি। এর জন্যে আলাদা কোন নিয়ম নেই।
এখানে T অবজেক্ট তৈরি করার সময় একটি রিয়েল অবজেক্ট অর্থাৎ আমরা যে অবজেক্ট প্যারিমিটারাইড করবো তা দ্বারা প্রতিস্থাপিত(replaced) হবে ।
আমরা জানি যে জাভা একটি স্ট্যাটিক টাইপ অর্থাৎ টাইপ সেইফ ল্যাংগুয়েজ। অর্থাৎ জাভা কোড কম্পাইল করার সময় এর টাইপ ইনফরমেশন ঠিক ঠাক আছে কিনা তা চেক করে নেয়।
অর্থাৎ -
এখানে iObj একটি ইন্টিজার প্যারমিটাইরজড অবজেক্ট রেফারেন্স ।
এখন অবজেক্ট তৈরি করার সময় যদি ডাবল প্যারমিটাইরজড করি এবং iObj তে এসাইন করি, তাহলে
কম্পাইল করার সময় উপরের ইররটি দেখতে পাবো।
জেনেরিকস শুধুমাত্র অজজেক্ট নিয়ে কাজ করে-
আমারা জানি যে, জাভা দুই ধরণের টাইপ সাপোর্ট করে- PrimitiveType এবং ReferenceType। জেনেরিকস শুধুমাত্র ReferenceType অর্থাৎ শুধু মাত্র অবজেক্ট নিয়ে কাজ রে।
তাই-
এই স্ট্যাটমেন্ট টি ভ্যালিড নয়। অর্থাৎ প্রিমিটিভ টাই এর ক্ষেত্রে জেনেরিকস কাজ করবে না।
জেনেরিক ক্লাস এর সিনট্যাক্স-
জেনেরিক ক্লাস ইনসটেনসিয়েট করার সিনটেক্স-
আমরা চাইলে একাধিক জেনেরিক টাইপ প্যারমিটাইরজড করতে পারি।
এবার তাহলে দুটি টাইপ প্যারামিটার নিয়ে একটি উদাহরণ দেখা যাক-
এই প্রোগ্রামটি রান করলে নিচের আউটপুট-টি পাওয়া যাবে –
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
একটি টাপলের মধ্যে আমরা চাইলে আরেকটি টাপল রাখে পারি - নিচের উদাহরণটি চমৎকার-
তবে আমরা যদি জাভা ৭ অথবা ৮ ব্যবহার করি তাহলে উপরের লাইনটি সংক্ষিপ্তভাবে লিখতে পারি –
জাভা ৭ এ একটি নতুন অপারেটর সংযুক্ত হয়েছে যাকে বলা হয় ডায়মন্ড অপারেটর। এটি ব্যবহার করে আমরা জেনেরকস এ verbosity কিছুটা কমানো যায়। অর্থাৎ
এই স্ট্যাটমেন্ট-টি অনেকটাই বড়। এটি আমরা এভাবে লিখতে পারি –
অর্থাৎ জেনেরিকস লেখার সময় বাম পাশে টাইপ প্যারামিটার ইনফরমেশন গুলো লিখলে ডান পাশে লিখতে হয় না । এটি অটোম্যাটিক্যালী ইনফার করতে পারে।
Bounded Types
আমরা উপরে দুটি উদাহরণ দেখেছি যেগুলোতে আমরা যে কোন ধরণের টাইপ প্যারামিটারাইউজড করতে পারি। কিন্তু কখনো কখনো আমাদের টাইপ restrict করতে হয়। যেমন- আমরা একটি জেনেরিক ক্লাস লিখতে চাই যা কিনা একটি এরে-তে রাখা কতগুলো নাম্বার-এর গড়(average) রিটার্ন করবে এবং আমরা চাই, এই এরে তে যে কোন ধরণের নাম্বার থাকতে পারে, যেমন- ইন্টিজার, ফ্লোটিং পয়েন্ট, ডাবল ইত্যাদি। আমরা টাইপ প্যারামিটার দিয়ে বলে দিতে চাই কখন কোনটা থাকবে। উদারহরণ দেখা যাক-
এভারেজ ক্যালকুলেট করার জন্য আমাদের এভারেজ মেথড সবসময় এরে থেকে ডাবল ভ্যালু এক্সেপেক্ট করে। কিন্তু আমাদের এরে-এর টাইপ যেহেতু যে কোন রকম হতে পারে, সুতরাং সব অবজেক্ট থেকে ডাবল ভ্যালু পাওয়ার উপায় নেই।
ইনফ্যাক্ট এই ক্লাসটি কিন্তু কম্পাইল হবে না।
এই ক্লাসটিতে আমরা একটি restriction এড করতে পারি যাতে করে এই টাইপ প্যারামিটার শুধুমাত্র নাম্বার(ইন্টিজার, ফ্লোটিং পয়েন্ট,ডাবল) হবে, নতুবা এটি কাজ করবে না।
আমরা জানি যে সব নিউমেরিক অবজেক্ট গুলোর সুপার ক্লাস হচ্ছে Number. এবং Number এ doubleValue() মেথড ডিফাইন করা আছে। সুতরাং আমাদের ক্লাসটিকে একটু পরিবর্তন করি।
একটু লক্ষ্য করুন-
আমরা ক্লাস ডেফিনেশনে আমাদের টাইপ প্লেসহোল্ডার T নাম্বারকে extend করে। এটি আমাদের টাইপ প্যারামিটার পাস করতে restrict করে । অর্থাৎ আমরা শুধু মাত্র সেসব টাইপ পাস করতে পারবো যারা Number এর সাব টাইপ।
সুতরাং আমাদের এই Stats ক্লাস এখন Integer, Double, Float, Long, Short, BigInteger, BigDecimal, Byte ইত্যাদি অবজেক্ট এর জন্যে কাজ করবে।
সুতরাং দেখা যাচ্ছে যে, জেনেরিকস এর সুবিধা ব্যবহার করে আমরা এই স্ট্যাট ক্লাসটি আলাদা আলাদা করে অনেকগুলো না লিখে একটি দিয়েই কাজ করে ফেলা সম্ভব হল।
Wildcard Arguments
নিচের উদাহরণটি লক্ষ্য করি-
এটি যদি কম্পাইল করতে চেষ্টা করি, তাহলে কম্পাইলার incompatible types ইরর দেবে। কিন্তু আমরা জানি যে, সকল অবজেক্ট এর সুপার ক্লাস Object। তাছাড়া আমরা polymorphism থেকে জানি যে
আমরা সাব ক্লাসের রেফারেন্স কে সুপার ক্লাসের রেফারেন্স এ এসাইন করতে পারি। সুতরাং উপরের স্ট্যাটমেন্ট-টি কাজ করার কথা।
নিচের উদাহরণ দুটি লক্ষ্য করি -
২ নাম্বার লাইনটি কাজ করছে না । যদিও বা এটি কাজ করে এবং আর্বিট্রারি কোন একটি অবজেক্ট যদি objLst এড করা হয় তাহলে কিন্তু strList করাপ্টেড হয়ে যাবে এবং সেটি আর স্ট্রিং থাকবে না।
ধরা যাক, আমরা একটা print মেথড লিখতে চাই যা কিনা একটি লিস্ট এর ইলিমেন্ট গুলো প্রিন্ট করে।
এটি কিন্তু শুধুমাত্র List<Object> একসেপ্ট করবে , List<String> অথবা List<Integer> করবে না।
উদাহরণ-
এই সমস্যা দূর করার জন্যে জাভাতে একটি একটি অপারেটর ব্যবহার করা হয় – যার নাম wildcard (?)।
আমরা যদি আমাদের print() মেথডটি নিচের মতো করে লিখি, তাহলে কিন্তু আমাদের সমস্যা দূর হয়ে যাবে।
List<?> lst এর মানে হচ্ছে আমরা এর টাইপ আমাদের জানা নেই, এটি যে কোন টাইপ হতে পারে। যেহেতু সব টাইপ এর সুপার ক্লাস Object সুতরাং এটি যেকোন টাইপ এর জন্যে কাজ করবে।
Bounded Types এর মতো আমরা Wildcard Arguments কেও Bounded করে ফেলতে পারি ।
উদাহরণ -
এটি শুধু মাত্রে Foo এর সাব ক্লাস গুলো কে প্রসেস করতে পারবে। একে Upper Bounded Wildcards বলে ।
আমরা যদি এমন কোন মেথড লিখতে চাই যা শুধু মাত্র Integer, Number, and Object প্রসেস করবে অর্থাৎ Integerএবং এর সুপার ক্লাস প্রসেস করবে তাহলে -
একে Lower Bounded Wildcards বলে।
Generic Methods
আমরা মূলত এতোক্ষণ জেনেরিক ক্লাস নিয়ে কথা বলেছি। আমরা একটি ক্লাসকে জেনেরিক না করে শুধুমাত্রে এর একটি বা একাধিক মেথড কে জেনেরিক করে লিখতে পারি।
উদহরণ-
এটি একটি জেনেরিকম মেথড।
জেনেরিক মেথড-এ রিটার্নটাইপ এর আগে টাইপ-প্লেস হোল্ডার <> লিখতে হয়।
আমরা এবার চেষ্টা করবো কিভাবে আমরা একটি জেনেরিক সিংগলি লিংকলিস্ট লিখতে পারি --
এবার আমরা এটিকে রান করে দেখি-
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 ,
Error:(24, 16) java: incompatible types: Generic<java.lang.Double> cannot be converted to Generic<java.lang.Integer>
`
Generic<int> intObj = new Generic<int>(50);
`
class class-name<type-param-list > {}
`
class-name<type-arg-list > var-name = new class-name<type-arg-list >(cons-arg-list);
`
public class Tuple<X, Y> {
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<String, String> tuple = new Tuple<String, String>("Hello", "world");
tuple.showTypes();
Tuple<String, Integer> person = new Tuple<>("Rahim", 45);
person.showTypes();
}
}
Tuple<String, Tuple<Integer, Integer>> tupleInsideTuple = new Tuple<String, Tuple<Integer, Integer>>("Tuple", new Tuple<Integer, Integer>(45, 89));
Tuple<String, Tuple<Integer, Integer>> tupleInsideTuple = new Tuple<>("Tuple", new Tuple<>(45, 89));
`
Map<String, List<String>> anagrams = new HashMap<String, List<String>>();
Map<String, List<String>> anagrams = new HashMap<>();
public class Stats<T> {
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;
}
}
`
public class Stats<T extends Number> {
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;
}
}
public static void print(List<Object> lst) { // accept List of Objects only,
// not List of subclasses of object
for (Object o : lst) {
System.out.println(o);
}
}
public static void main(String[] args) {
List<Object> objLst = new ArrayList<Object>();
objLst.add(new Integer(55));
printList(objLst); // matches
List<String> strLst = new ArrayList<String>();
strLst.add("one");
printList(strLst); // compilation error
}
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);
}
}