ہائے! ہم جاوا جنرک کے بارے میں بات کرنے جا رہے ہیں۔ مجھے یہ کہنا ضروری ہے کہ آپ بہت کچھ سیکھیں گے! نہ صرف یہ سبق، بلکہ اگلے چند اسباق بھی جنرک کے لیے وقف کیے جائیں گے۔ لہذا، اگر آپ جنرکس میں دلچسپی رکھتے ہیں، تو آج آپ کا خوش قسمت دن ہے: آپ جنرک کی خصوصیات کے بارے میں بہت کچھ سیکھیں گے۔ اور اگر نہیں تو خود استعفیٰ دیں اور آرام کریں! :) یہ ایک بہت اہم موضوع ہے، اور آپ کو اسے جاننے کی ضرورت ہے۔ آئیے سادہ سے شروع کریں: "کیا" اور "کیوں"۔

جاوا جنرک کیا ہیں؟

جنرک وہ قسمیں ہیں جن کا پیرامیٹر ہوتا ہے۔ ایک عام قسم بناتے وقت، آپ نہ صرف ایک قسم، بلکہ ڈیٹا کی قسم بھی بتاتے ہیں جس کے ساتھ یہ کام کرے گا۔ میں اندازہ لگا رہا ہوں کہ سب سے واضح مثال آپ کے ذہن میں آ چکی ہے: ArrayList! اس طرح ہم عام طور پر ایک پروگرام میں ایک بناتے ہیں:
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<String> myList1 = new ArrayList<>();
       myList1.add("Test String 1");
       myList1.add("Test String 2");
   }
}
جیسا کہ آپ اندازہ لگا سکتے ہیں، اس فہرست کی ایک خصوصیت یہ ہے کہ ہم اس میں سب کچھ نہیں ڈال سکتے: یہ خصوصی طور پر String اشیاء کے ساتھ کام کرتی ہے۔ اب آئیے جاوا کی تاریخ میں تھوڑا سا غور کریں اور اس سوال کا جواب دینے کی کوشش کریں "کیوں؟" ایسا کرنے کے لیے، ہم ArrayList کلاس کا اپنا آسان ورژن لکھیں گے۔ ہماری فہرست صرف یہ جانتی ہے کہ داخلی صف سے ڈیٹا کو کیسے شامل کرنا اور بازیافت کرنا ہے۔
public class MyListClass {

   private Object[] data;
   private int count;

   public MyListClass() {
       this.data = new Object[10];
       this.count = 0;
   }

   public void add(Object o) {
       this.data[count] = o;
       count++;
   }

   public Object[] getData() {
       return data;
   }
}
فرض کریں کہ ہم چاہتے ہیں کہ ہماری فہرست صرف Integer s کو اسٹور کرے۔ ہم ایک عام قسم کا استعمال نہیں کر رہے ہیں۔ ہم add() طریقہ میں واضح "Instanceof Integer " چیک شامل نہیں کرنا چاہتے ۔ اگر ہم ایسا کرتے ہیں، تو ہماری پوری کلاس صرف Integer کے لیے موزوں ہوگی ، اور ہمیں دنیا کے ہر دوسرے ڈیٹا ٹائپ کے لیے اسی طرح کی کلاس لکھنی ہوگی۔ ہم اپنے پروگرامرز پر بھروسہ کریں گے، اور کوڈ میں صرف ایک تبصرہ چھوڑیں گے تاکہ یہ یقینی بنایا جا سکے کہ وہ ایسی کوئی چیز شامل نہیں کریں گے جو ہم نہیں چاہتے ہیں:
// Use this class ONLY with the Integer data type
public void add(Object o) {
   this.data[count] = o;
   count++;
}
پروگرامرز میں سے ایک نے یہ تبصرہ چھوڑ دیا اور نادانستہ طور پر نمبروں کی فہرست میں کئی سٹرنگز ڈال دیے اور پھر ان کا حساب لگایا:
public class Main {

   public static void main(String[] args) {

       MyListClass list = new MyListClass();
       list.add(100);
       list.add(200);
       list.add("Lolkek");
       list.add("Shalala");

       Integer sum1 = (Integer) list.getData()[0] + (Integer) list.getData()[1];
       System.out.println(sum1);

       Integer sum2 = (Integer) list.getData()[2] + (Integer) list.getData()[3];
       System.out.println(sum2);
   }
}
کنسول آؤٹ پٹ:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
      at Main.main (Main.java:14)
اس صورت حال کا سب سے برا حصہ کیا ہے؟ یقیناً پروگرامر کی لاپرواہی نہیں۔ سب سے بری بات یہ ہے کہ غلط کوڈ ہمارے پروگرام میں ایک اہم جگہ پر ختم ہوا اور کامیابی سے مرتب ہوا۔ اب ہم کوڈ لکھتے وقت نہیں بلکہ صرف جانچ کے دوران بگ کا سامنا کریں گے (اور یہ بہترین صورت حال ہے!) ترقی کے بعد کے مراحل میں کیڑے ٹھیک کرنے پر بہت زیادہ لاگت آتی ہے — پیسے اور وقت دونوں کے لحاظ سے۔ یہ بالکل وہی ہے جہاں عامیات سے ہمیں فائدہ ہوتا ہے: ایک عام کلاس بدقسمت پروگرامر کو فوری طور پر غلطی کا پتہ لگانے دیتی ہے۔ پروگرام صرف مرتب نہیں کرے گا!
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Integer> myList1 = new ArrayList<>();

       myList1.add(100);
       myList1.add(100);
       myList1.add ("Lolkek"); // Error!
       myList1.add("Shalala"); // Error!
   }
}
پروگرامر کو فوری طور پر اپنی غلطی کا احساس ہوتا ہے اور وہ فوری طور پر بہتر ہو جاتا ہے۔ ویسے، اس قسم کی غلطی کو دیکھنے کے لیے ہمیں اپنی لسٹ کلاس بنانے کی ضرورت نہیں تھی۔ بس زاویہ بریکٹ کو ہٹا دیں اور ایک عام ArrayList سے ( <Integer> ) ٹائپ کریں!
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

      List list = new ArrayList();

      list.add(100);
      list.add(200);
      list.add("Lolkek");
      list.add("Shalala");

       System.out.println((Integer) list.get(0) + (Integer) list.get(1));
       System.out.println((Integer) list.get(2) + (Integer) list.get(3));
   }
}
کنسول آؤٹ پٹ:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
     at Main.main(Main.java:16)
دوسرے لفظوں میں، جاوا کے "مقامی" میکانزم کا استعمال کرتے ہوئے بھی، ہم اس طرح کی غلطی کر سکتے ہیں اور ایک غیر محفوظ مجموعہ بنا سکتے ہیں۔ تاہم، اگر ہم اس کوڈ کو IDE میں چسپاں کرتے ہیں، تو ہمیں ایک انتباہ ملتا ہے: "java.util.List کی خام قسم کے ممبر کے طور پر شامل کرنے کے لیے غیر چیک شدہ کال" ہمیں بتایا جاتا ہے کہ کسی چیز کو شامل کرتے وقت کچھ غلط ہو سکتا ہے۔ ایک ایسے مجموعے کے لیے جس میں عام قسم کا فقدان ہو۔ لیکن جملے "کچی قسم" کا کیا مطلب ہے؟ خام قسم ایک عام کلاس ہے جس کی قسم کو ہٹا دیا گیا ہے۔ دوسرے الفاظ میں، List myList1 ایک خام قسم ہے ۔ خام قسم کا مخالف ایک عام قسم ہے — ایک عام کلاس جس میں پیرامیٹرائزڈ قسم(s) کا اشارہ ہوتا ہے ۔ مثال کے طور پر، List<String> myList1 ۔ آپ پوچھ سکتے ہیں کہ زبان خام اقسام کے استعمال کی اجازت کیوں دیتی ہے ؟ وجہ سادہ ہے۔ جاوا کے تخلیق کاروں نے مطابقت کے مسائل پیدا کرنے سے بچنے کے لیے زبان میں خام قسموں کی حمایت چھوڑ دی۔ جب تک Java 5.0 جاری کیا گیا تھا (اس ورژن میں عام طور پر پہلی بار ظاہر ہوا تھا)، raw اقسام کا استعمال کرتے ہوئے بہت سارے کوڈ پہلے ہی لکھے جا چکے تھے ۔ نتیجے کے طور پر، یہ طریقہ کار آج بھی معاون ہے۔ ہم نے اسباق میں جوشوا بلوچ کی کلاسک کتاب "Effective Java" کا بار بار ذکر کیا ہے۔ زبان کے تخلیق کاروں میں سے ایک کے طور پر، اس نے اپنی کتاب میں خام اقسام اور عام اقسام کو نہیں چھوڑا۔ جاوا میں عام کیا ہیں؟  - 2کتاب کے باب 23 کا ایک بہت ہی فصیح عنوان ہے: "نئے کوڈ میں خام اقسام کا استعمال نہ کریں" یہ وہی ہے جو آپ کو یاد رکھنے کی ضرورت ہے۔ عام کلاسز کا استعمال کرتے وقت، کبھی بھی عام قسم کو خام قسم میں تبدیل نہ کریں ۔

عام طریقے

جاوا آپ کو نام نہاد عام طریقے بنا کر انفرادی طریقوں کو پیرامیٹرائز کرنے دیتا ہے۔ ایسے طریقے کیسے مددگار ہیں؟ سب سے بڑھ کر، وہ اس میں مددگار ہیں کہ وہ آپ کو مختلف قسم کے طریقہ کار کے پیرامیٹرز کے ساتھ کام کرنے دیتے ہیں۔ اگر ایک ہی منطق کو مختلف اقسام پر محفوظ طریقے سے لاگو کیا جا سکتا ہے، تو ایک عام طریقہ ایک بہترین حل ہو سکتا ہے۔ اس کو ایک بہت ہی آسان مثال پر غور کریں: فرض کریں کہ ہمارے پاس ایک فہرست ہے myList1 ۔ ہم فہرست سے تمام اقدار کو ہٹانا چاہتے ہیں اور تمام خالی جگہوں کو نئی اقدار سے بھرنا چاہتے ہیں۔ عام طریقہ کے ساتھ ہماری کلاس کیسی دکھتی ہے وہ یہ ہے:
public class TestClass {

   public static <T> void fill(List<T> list, T val) {
       for (int i = 0; i < list.size(); i++)
           list.set(i, val);
   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       strings.add("Old String 1");
       strings.add("Old String 2");
       strings.add("Old String 3");

       fill(strings, "New String");

       System.out.println(strings);

       List<Integer> numbers = new ArrayList<>();
       numbers.add(1);
       numbers.add(2);
       numbers.add(3);

       fill(numbers, 888);
       System.out.println(numbers);
   }
}
نحو پر توجہ دیں۔ یہ تھوڑا سا غیر معمولی لگتا ہے:
public static <T> void fill(List<T> list, T val)
ہم واپسی کی قسم سے پہلے <T> لکھتے ہیں۔ اس سے ظاہر ہوتا ہے کہ ہم ایک عام طریقہ سے کام کر رہے ہیں۔ اس صورت میں، طریقہ 2 پیرامیٹرز کو بطور ان پٹ قبول کرتا ہے: T آبجیکٹ کی فہرست اور ایک اور علیحدہ T آبجیکٹ۔ <T> کا استعمال کرتے ہوئے، ہم طریقہ کے پیرامیٹر کی اقسام کو پیرامیٹرائز کرتے ہیں: ہم سٹرنگز اور انٹیجر کی فہرست میں پاس نہیں ہو سکتے۔ سٹرنگز اور ایک سٹرنگ کی فہرست، انٹیجرز اور انٹیجر کی ایک فہرست، ہماری اپنی کیٹ آبجیکٹ اور ایک اور کیٹ آبجیکٹ کی فہرست — ہمیں یہی کرنے کی ضرورت ہے۔ main () طریقہ یہ بتاتا ہے کہ کس طرح fill() طریقہ کو مختلف قسم کے ڈیٹا کے ساتھ کام کرنے کے لیے آسانی سے استعمال کیا جا سکتا ہے۔ سب سے پہلے، ہم طریقہ استعمال کرتے ہیں سٹرنگز کی فہرست اور ایک سٹرنگ بطور ان پٹ، اور پھر انٹیجرز اور انٹیجر کی فہرست کے ساتھ۔ کنسول آؤٹ پٹ:

[New String, New String, New String] [888, 888, 888]
تصور کریں کہ کیا ہمارے پاس عام طریقے نہیں ہیں اور 30 ​​مختلف کلاسوں کے لیے فل() طریقہ کی منطق کی ضرورت ہے۔ ہمیں مختلف ڈیٹا کی اقسام کے لیے 30 بار ایک ہی طریقہ لکھنا پڑے گا! لیکن عام طریقوں کی بدولت، ہم اپنے کوڈ کو دوبارہ استعمال کر سکتے ہیں! :)

عام کلاسز

آپ معیاری جاوا لائبریریوں میں فراہم کردہ عام کلاسوں تک محدود نہیں ہیں — آپ اپنی تخلیق کر سکتے ہیں! یہاں ایک سادہ مثال ہے:
public class Box<T> {

   private T t;

   public void set(T t) {
       this.t = t;
   }

   public T get() {
       return t;
   }

   public static void main(String[] args) {

       Box<String> stringBox = new Box<>();

       stringBox.set("Old String");
       System.out.println(stringBox.get());
       stringBox.set("New String");

       System.out.println(stringBox.get());

       stringBox.set(12345); // Compilation error!
   }
}
ہمارا باکس<T> کلاس ایک عام کلاس ہے۔ ایک بار جب ہم تخلیق کے دوران ڈیٹا کی قسم ( <T> ) تفویض کر دیتے ہیں، تو ہم اس میں دوسری قسم کی اشیاء رکھنے کے قابل نہیں رہتے ہیں۔ یہ مثال میں دیکھا جا سکتا ہے۔ ہمارا اعتراض بناتے وقت، ہم نے اشارہ کیا کہ یہ Strings کے ساتھ کام کرے گا:
Box<String> stringBox = new Box<>();
اور کوڈ کی آخری لائن میں، جب ہم نمبر 12345 کو باکس کے اندر ڈالنے کی کوشش کرتے ہیں، تو ہمیں ایک کمپلیشن ایرر ملتا ہے! یہ اتنا آسان ہے! ہم نے اپنی عام کلاس بنائی ہے! :) اس کے ساتھ ہی آج کا سبق اختتام کو پہنچا۔ لیکن ہم جنرک کو الوداع نہیں کہہ رہے ہیں! اگلے اسباق میں، ہم مزید جدید خصوصیات کے بارے میں بات کریں گے، تو کیا آپ دور نہ ہوں! ) جو کچھ آپ نے سیکھا ہے اس کو تقویت دینے کے لیے، ہم تجویز کرتے ہیں کہ آپ ہمارے جاوا کورس سے ایک ویڈیو سبق دیکھیں
اپنی پڑھائی میں بہترین کامیابی! :)