ওহে! আসুন জেনেরিক সম্পর্কে আমাদের অধ্যয়ন চালিয়ে যাই। আপনি ইতিমধ্যেই পূর্ববর্তী পাঠগুলি থেকে তাদের সম্পর্কে যথেষ্ট জ্ঞান অর্জন করেছেন ( জেনারিকের সাথে কাজ করার সময় এবং টাইপ ইরেজার সম্পর্কে ) তবে একটি গুরুত্বপূর্ণ বিষয় রয়েছে যা আমরা এখনও বিবেচনা করিনি — ওয়াইল্ডকার্ড । এটি জেনেরিকের খুব গুরুত্বপূর্ণ বৈশিষ্ট্য। এত বেশি যে আমরা এটির জন্য একটি পৃথক পাঠ উত্সর্গ করেছি! এটি বলেছে, ওয়াইল্ডকার্ড সম্পর্কে বিশেষভাবে জটিল কিছু নেই। আপনি এখনই এটি দেখতে পাবেন :) আসুন একটি উদাহরণ দেখি:
public class Main {
public static void main(String[] args) {
String str = new String("Test!");
// No problem
Object obj = str;
List<String> strings = new ArrayList<String>();
// Compilation error!
List<Object> objects = strings;
}
}
এখানে কি হচ্ছে? আমরা দুটি খুব অনুরূপ পরিস্থিতি দেখতে. ক্ষেত্রে, আমরা একটি String
বস্তু একটি Object
বস্তু নিক্ষেপ. এখানে কোন সমস্যা নেই - সবকিছু প্রত্যাশিত হিসাবে কাজ করে। কিন্তু দ্বিতীয় পরিস্থিতিতে, কম্পাইলার একটি ত্রুটি তৈরি করে। কিন্তু আমরা একই জিনিস করছি, তাই না? এই সময় আমরা কেবল কিছু বস্তুর সংগ্রহ ব্যবহার করছি। কিন্তু কেন ত্রুটি ঘটবে? পার্থক্য কি? আমরা কি একটি String
বস্তুকে একটি Object
বা 20টি বস্তুতে নিক্ষেপ করছি? একটি বস্তু এবং বস্তুর সংগ্রহের মধ্যে একটি গুরুত্বপূর্ণ পার্থক্য রয়েছে । ক্লাস যদি B
ক্লাসের বাচ্চা হয় A
, তাহলে Collection<B>
সে ক্লাসের বাচ্চা নয় Collection<A>
। List<String>
এই কারণেই আমরা আমাদের এ কাস্ট করতে পারিনিList<Object>
. String
এর সন্তান Object
, কিন্তু List<String>
এর সন্তান নয় List<Object>
। এই সুপার স্বজ্ঞাত মনে হতে পারে না. ভাষার স্রষ্টারা কেন এভাবে তৈরি করলেন? আসুন কল্পনা করি যে কম্পাইলার আমাদের একটি ত্রুটি দেয় না:
List<String> strings = new ArrayList<String>();
List<Object> objects = strings;
এই ক্ষেত্রে, আমরা, উদাহরণস্বরূপ, নিম্নলিখিতগুলি করতে পারি:
objects.add(new Object());
String s = strings.get(0);
যেহেতু কম্পাইলার আমাদের কোন ত্রুটি দেয়নি এবং আমাদেরকে একটি List<Object>
রেফারেন্স তৈরি করার অনুমতি দেয় যা নির্দেশ করে strings
, আমরা সংগ্রহে যেকোন পুরানো Object
বস্তু যোগ করতে পারি strings
! এইভাবে, আমরা গ্যারান্টি হারিয়ে ফেলেছি যে আমাদের সংগ্রহে শুধুমাত্র String
জেনেরিক টাইপ ইনভোকেশনের টাইপ আর্গুমেন্ট দ্বারা নির্দিষ্ট করা বস্তু রয়েছে । অন্য কথায়, আমরা জেনেরিকের প্রধান সুবিধা হারিয়ে ফেলেছি — টাইপ নিরাপত্তা। এবং যেহেতু কম্পাইলার আমাদের এটি করা থেকে বিরত করেনি, আমরা শুধুমাত্র রান টাইমে একটি ত্রুটি পাব, যা সবসময় একটি সংকলন ত্রুটির চেয়ে অনেক খারাপ। এই ধরনের পরিস্থিতি প্রতিরোধ করতে, কম্পাইলার আমাদের একটি ত্রুটি দেয়:
// Compilation error
List<Object> objects = strings;
...এবং আমাদের মনে করিয়ে দেয় যে List<String>
এর বংশধর নয় List<Object>
। এটি জেনেরিকের জন্য একটি আয়রনক্ল্যাড নিয়ম, এবং তাদের সাথে কাজ করার সময় এটি অবশ্যই মনে রাখতে হবে। চল এগোই. ধরুন আমাদের একটি ছোট শ্রেণী অনুক্রম আছে:
public class Animal {
public void feed() {
System.out.println("Animal.feed()");
}
}
public class Pet extends Animal {
public void call() {
System.out.println("Pet.call()");
}
}
public class Cat extends Pet {
public void meow() {
System.out.println("Cat.meow()");
}
}
শ্রেণিবিন্যাসের শীর্ষে রয়েছে একটি সাধারণ প্রাণী শ্রেণি, যা পোষ্যদের দ্বারা উত্তরাধিকারসূত্রে পাওয়া যায়। পোষা প্রাণীর 2টি উপশ্রেণী রয়েছে: কুকুর এবং বিড়াল। এখন ধরুন আমাদের একটি সহজ iterateAnimals()
পদ্ধতি তৈরি করতে হবে। পদ্ধতিটি যেকোন প্রাণীর সংগ্রহ ( Animal
, Pet
, Cat
, Dog
), সমস্ত উপাদানের উপর পুনরাবৃত্তি করে এবং প্রতিটি পুনরাবৃত্তির সময় কনসোলে একটি বার্তা প্রদর্শন করা উচিত। আসুন এমন একটি পদ্ধতি লেখার চেষ্টা করি:
public static void iterateAnimals(Collection<Animal> animals) {
for(Animal animal: animals) {
System.out.println("Another iteration in the loop!");
}
}
মনে হয় সমস্যা মিটে গেছে! যাইহোক, আমরা সম্প্রতি শিখেছি, List<Cat>
এবং List<Dog>
এর List<Pet>
বংশধর নয় List<Animal>
! iterateAnimals()
এর মানে হল যে যখন আমরা বিড়ালের তালিকা সহ পদ্ধতিটি কল করার চেষ্টা করি , তখন আমরা একটি সংকলন ত্রুটি পাই:
import java.util.*;
public class Main3 {
public static void iterateAnimals(Collection<Animal> animals) {
for(Animal animal: animals) {
System.out.println("Another iteration in the loop!");
}
}
public static void main(String[] args) {
List<Cat> cats = new ArrayList<>();
cats.add(new Cat());
cats.add(new Cat());
cats.add(new Cat());
cats.add(new Cat());
// Compilation error!
iterateAnimals(cats);
}
}
আমাদের অবস্থা খুব একটা ভালো লাগছে না! আমাদের কি প্রতিটি ধরণের প্রাণীর গণনার জন্য আলাদা পদ্ধতি লিখতে হবে? প্রকৃতপক্ষে, না, আমরা করি না :) এবং এটি যেমন ঘটে, ওয়াইল্ডকার্ড আমাদের এতে সহায়তা করে! আমরা নিম্নলিখিত নির্মাণ ব্যবহার করে একটি সহজ পদ্ধতির সাথে সমস্যার সমাধান করতে পারি:
public static void iterateAnimals(Collection<? extends Animal> animals) {
for(Animal animal: animals) {
System.out.println("Another iteration in the loop!");
}
}
এটি একটি ওয়াইল্ডকার্ড। আরও স্পষ্টভাবে, এটি বিভিন্ন ধরণের ওয়াইল্ডকার্ডের মধ্যে প্রথম। এটি একটি উপরের-বাউন্ডেড ওয়াইল্ডকার্ড হিসাবে পরিচিত এবং দ্বারা প্রকাশ করা হয় ? প্রসারিত _ এই গঠন আমাদের কি বলে? এর মানে হল যে পদ্ধতিটি বস্তুর একটি সংগ্রহ বা (? প্রসারিত প্রাণী) Animal
থেকে নেমে আসা যেকোনো শ্রেণীর বস্তুর সংগ্রহ গ্রহণ করে। অন্য কথায়, পদ্ধতিটি , , , বা বস্তুর Animal
সংগ্রহ গ্রহণ করতে পারে — এতে কোনো পার্থক্য নেই। আসুন নিজেদেরকে বোঝাই যে এটি কাজ করে: Animal
Pet
Dog
Cat
public static void main(String[] args) {
List<Animal> animals = new ArrayList<>();
animals.add(new Animal());
animals.add(new Animal());
List<Pet> pets = new ArrayList<>();
pets.add(new Pet());
pets.add(new Pet());
List<Cat> cats = new ArrayList<>();
cats.add(new Cat());
cats.add(new Cat());
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
dogs.add(new Dog());
iterateAnimals(animals);
iterateAnimals(pets);
iterateAnimals(cats);
iterateAnimals(dogs);
}
কনসোল আউটপুট:
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
আমরা মোট 4টি সংগ্রহ এবং 8টি অবজেক্ট তৈরি করেছি এবং কনসোলে ঠিক 8টি এন্ট্রি রয়েছে৷ সবকিছু মহান কাজ করে! :) ওয়াইল্ডকার্ড আমাদের সহজে একটি একক পদ্ধতিতে নির্দিষ্ট ধরনের সাথে আবদ্ধ প্রয়োজনীয় যুক্তি মাপসই করার অনুমতি দিয়েছে। আমরা প্রতিটি ধরণের প্রাণীর জন্য একটি পৃথক পদ্ধতি লেখার প্রয়োজনীয়তা দূর করেছি। কল্পনা করুন যে আমাদের অ্যাপ্লিকেশনটি চিড়িয়াখানা বা পশুচিকিত্সা অফিস ব্যবহার করলে আমাদের কতগুলি পদ্ধতির প্রয়োজন হত :) কিন্তু এখন আসুন একটি ভিন্ন পরিস্থিতি দেখা যাক। আমাদের উত্তরাধিকারের শ্রেণিবিন্যাস অপরিবর্তিত রয়েছে: শীর্ষ-স্তরের শ্রেণী হল Animal
, Pet
নীচের শ্রেণী সহ, এবং পরবর্তী স্তরে Cat
এবং শ্রেণীগুলি। Dog
এখন আপনাকে পদ্ধতিটি পুনরায় লিখতে হবে iterateAnimals()
যাতে কুকুর ব্যতীত যে কোনও ধরণের প্রাণীর সাথে কাজ করা যায় । অর্থাৎ মেনে নিতে হবে Collection<Animal>
,Collection<Pet>
বা Collection<Car>
, কিন্তু এটির সাথে কাজ করা উচিত নয় Collection<Dog>
। কিভাবে আমরা এই অর্জন করতে পারেন? মনে হচ্ছে আমরা আবার প্রতিটি প্রকারের জন্য একটি পৃথক পদ্ধতি লেখার সম্ভাবনার মুখোমুখি হচ্ছি :/ আমরা কী ঘটতে চাই তা কম্পাইলারকে কীভাবে ব্যাখ্যা করব? এটা আসলে বেশ সহজ! আবার, ওয়াইল্ডকার্ড এখানে আমাদের সাহায্যে আসে। কিন্তু এবার আমরা অন্য ধরনের ওয়াইল্ডকার্ড ব্যবহার করব — একটি নিম্ন-সীমাবদ্ধ ওয়াইল্ডকার্ড , যা সুপার ব্যবহার করে প্রকাশ করা হয় ।
public static void iterateAnimals(Collection<? super Cat> animals) {
for(int i = 0; i < animals.size(); i++) {
System.out.println("Another iteration in the loop!");
}
}
এখানে নীতি অনুরূপ। কনস্ট্রাক্ট <? super Cat>
কম্পাইলারকে বলে যে iterateAnimals()
পদ্ধতিটি ইনপুট হিসাবে বস্তুর একটি সংগ্রহ Cat
বা ক্লাসের কোনো পূর্বপুরুষকে Cat
ইনপুট হিসাবে গ্রহণ করতে পারে। এই ক্ষেত্রে, Cat
ক্লাস, এর পিতামাতা Pet
এবং তার পিতামাতার পিতা, Animal
, সবই এই বর্ণনার সাথে মিলে যায়৷ ক্লাসটি Dog
আমাদের সীমাবদ্ধতার সাথে মেলে না, তাই একটি List<Dog>
যুক্তি সহ পদ্ধতিটি ব্যবহার করার প্রচেষ্টার ফলে একটি সংকলন ত্রুটি হবে:
public static void main(String[] args) {
List<Animal> animals = new ArrayList<>();
animals.add(new Animal());
animals.add(new Animal());
List<Pet> pets = new ArrayList<>();
pets.add(new Pet());
pets.add(new Pet());
List<Cat> cats = new ArrayList<>();
cats.add(new Cat());
cats.add(new Cat());
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
dogs.add(new Dog());
iterateAnimals(animals);
iterateAnimals(pets);
iterateAnimals(cats);
// Compilation error!
iterateAnimals(dogs);
}
আমরা আমাদের সমস্যার সমাধান করেছি, এবং আবার ওয়াইল্ডকার্ডগুলি অত্যন্ত দরকারী হতে দেখা গেছে :) এর সাথে, পাঠটি শেষ হয়ে গেছে। এখন আপনি দেখতে পাচ্ছেন যে আপনার জাভা অধ্যয়নে জেনেরিক কতটা গুরুত্বপূর্ণ — আমরা সেগুলি সম্পর্কে 4টি সম্পূর্ণ পাঠ পেয়েছি! কিন্তু এখন আপনি বিষয়টিতে পারদর্শী এবং আপনি চাকরির ইন্টারভিউতে আপনার দক্ষতা প্রমাণ করতে পারেন :) এবং এখন, কাজগুলিতে ফিরে আসার সময়! আপনার পড়াশোনায় সাফল্যের সেরা! :)