ہائے! ہم generics پر اپنے اسباق کا سلسلہ جاری رکھتے ہیں۔ ہمیں پہلے
ایک عام خیال ملا کہ وہ کیا ہیں اور ان کی ضرورت کیوں ہے۔ آج ہم جنرکس کی کچھ خصوصیات اور ان کے ساتھ کام کرنے کے بارے میں مزید جانیں گے۔ چلو! پچھلے سبق
میں ، ہم نے عام اقسام اور خام اقسام کے درمیان فرق کے بارے میں بات کی ۔ خام قسم ایک عام کلاس ہے جس کی قسم کو ہٹا دیا گیا ہے۔
دستاویزات میں کہا گیا ہے، "T - اس کلاس آبجیکٹ کے ذریعہ تیار کردہ کلاس کی قسم۔" دستاویز کی زبان سے سادہ تقریر میں اس کا ترجمہ کرتے ہوئے، ہم سمجھتے ہیں کہ آبجیکٹ کی کلاس

List list = new ArrayList();
یہاں ایک مثال ہے۔ یہاں ہم اس بات کی نشاندہی نہیں کرتے ہیں کہ ہمارے میں کس قسم کی اشیاء رکھی جائیں گی List
۔ اگر ہم ایسا بنانے کی کوشش کرتے ہیں List
اور اس میں کچھ اشیاء شامل کرتے ہیں، تو ہمیں IDEA میں ایک انتباہ نظر آئے گا:
"Unchecked call to add(E) as a member of raw type of java.util.List".
لیکن ہم نے اس حقیقت کے بارے میں بھی بات کی کہ جنرک صرف جاوا 5 میں نمودار ہوئے۔ جب تک یہ ورژن جاری ہوا، پروگرامرز پہلے ہی خام اقسام کا استعمال کرتے ہوئے کوڈ کا ایک گروپ لکھ چکے تھے، اس لیے زبان کی یہ خصوصیت کام کرنا بند نہیں کر سکتی تھی، اور جاوا میں خام قسمیں بنانا محفوظ تھا۔ تاہم، مسئلہ زیادہ وسیع ہو گیا. جیسا کہ آپ جانتے ہیں، جاوا کوڈ کو ایک خاص مرتب شدہ فارمیٹ میں تبدیل کیا جاتا ہے جسے بائٹ کوڈ کہتے ہیں، جسے جاوا ورچوئل مشین کے ذریعے عمل میں لایا جاتا ہے۔ لیکن اگر ہم تبادلوں کے عمل کے دوران بائیک کوڈ میں ٹائپ پیرامیٹرز کے بارے میں معلومات ڈالتے ہیں، تو یہ پہلے سے لکھے گئے تمام کوڈ کو توڑ دے گا، کیونکہ جاوا 5 سے پہلے کوئی قسم کے پیرامیٹرز نہیں تھے! جنریکس کے ساتھ کام کرتے وقت، ایک بہت اہم تصور ہے جو آپ کو یاد رکھنے کی ضرورت ہے۔ اسے ٹائپ ایریزر کہتے ہیں ۔ اس کا مطلب ہے کہ کلاس میں ٹائپ پیرامیٹر کے بارے میں کوئی معلومات نہیں ہوتی ہیں۔ یہ معلومات صرف تالیف کے دوران دستیاب ہوتی ہے اور رن ٹائم سے پہلے مٹ جاتی ہے (ناقابل رسائی ہو جاتی ہے)۔ اگر آپ اپنے میں غلط قسم کی آبجیکٹ ڈالنے کی کوشش کرتے ہیں List<String>
تو کمپائلر ایک غلطی پیدا کرے گا۔ یہ بالکل وہی ہے جو زبان کے تخلیق کار اس وقت حاصل کرنا چاہتے ہیں جب انہوں نے generics: compile-time checks بنائے۔ لیکن جب آپ کا تمام جاوا کوڈ بائیک کوڈ میں بدل جاتا ہے، تو اس میں ٹائپ پیرامیٹرز کے بارے میں مزید معلومات نہیں ہوتی ہیں۔ بائیک کوڈ میں، آپ کی بلیوں کی فہرست تاروں List<Cat>
سے مختلف نہیں ہے ۔ List<String>
بائیک کوڈ میں، کچھ بھی نہیں کہتا کہ یہ اشیاء cats
کی فہرست ہے ۔ Cat
اس طرح کی معلومات کو تالیف کے دوران مٹا دیا جاتا ہے - صرف یہ حقیقت کہ آپ کے پاس List<Object> cats
فہرست ہے پروگرام کے بائیک کوڈ میں ختم ہو جائے گی۔ آئیے دیکھتے ہیں کہ یہ کیسے کام کرتا ہے:
public class TestClass<T> {
private T value1;
private T value2;
public void printValues() {
System.out.println(value1);
System.out.println(value2);
}
public static <T> TestClass<T> createAndAdd2Values(Object o1, Object o2) {
TestClass<T> result = new TestClass<>();
result.value1 = (T) o1;
result.value2 = (T) o2;
return result;
}
public static void main(String[] args) {
Double d = 22.111;
String s = "Test String";
TestClass<Integer> test = createAndAdd2Values(d, s);
test.printValues();
}
}
ہم نے اپنی عام TestClass
کلاس بنائی۔ یہ بہت آسان ہے: یہ دراصل 2 اشیاء کا ایک چھوٹا سا "مجموعہ" ہے، جو آبجیکٹ بننے کے فوراً بعد محفوظ ہو جاتا ہے۔ اس میں 2 T
فیلڈز ہیں۔ جب createAndAdd2Values()
طریقہ کار پر عمل درآمد کیا جاتا ہے تو، دو پاس شدہ آبجیکٹ ( Object a
اور ٹائپ پر کاسٹ کرنا اور پھر آبجیکٹ میں شامل کرنا Object b
ضروری ہے ۔ طریقہ کار میں ، ہم ایک بناتے ہیں ، یعنی قسم کی دلیل ٹائپ پیرامیٹر کی جگہ لے لیتی ہے۔ ہم ایک اور ایک کو بھی پاس کر رہے ہیں۔ طریقہ ۔ کیا آپ کو لگتا ہے کہ ہمارا پروگرام کام کرے گا؟ آخر کار، ہم نے قسم کی دلیل کے طور پر بیان کیا ہے، لیکن یقینی طور پر ایک کو کاسٹ نہیں کیا جا سکتا ! آئیے طریقہ کو چلائیں اور چیک کریں۔ کنسول آؤٹ پٹ: T
TestClass
main()
TestClass<Integer>
Integer
Integer
Double
String
createAndAdd2Values()
Integer
String
Integer
main()
22.111
Test String
یہ غیر متوقع تھا! ایسا کیوں ہوا؟ یہ قسم مٹانے کا نتیجہ ہے۔ Integer
ہمارے اعتراض کو تیز کرنے کے لیے استعمال ہونے والی قسم کی دلیل کے بارے میں معلومات کو TestClass<Integer> test
کوڈ کے مرتب ہونے پر مٹا دیا گیا تھا۔ میدان بن جاتا ہے TestClass<Object> test
۔ ہمارے Double
اور String
دلائل کو آسانی سے Object
اشیاء میں تبدیل کر دیا گیا (وہ اشیاء میں تبدیل نہیں ہوئے Integer
جیسا کہ ہم نے توقع کی تھی!) اور خاموشی سے TestClass
. قسم مٹانے کی ایک اور سادہ لیکن انتہائی واضح مثال یہ ہے:
import java.util.ArrayList;
import java.util.List;
public class Main {
private class Cat {
}
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
List<Integer> numbers = new ArrayList<>();
List<Cat> cats = new ArrayList<>();
System.out.println(strings.getClass() == numbers.getClass());
System.out.println(numbers.getClass() == cats.getClass());
}
}
کنسول آؤٹ پٹ:
true
true
ایسا لگتا ہے کہ ہم نے تین مختلف قسم کے دلائل — String
, Integer
اور اپنی اپنی Cat
کلاس کے ساتھ مجموعے بنائے ہیں۔ لیکن بائیک کوڈ میں تبدیلی کے دوران، تینوں فہرستیں بن جاتی ہیں List<Object>
، لہذا جب پروگرام چلتا ہے تو یہ ہمیں بتاتا ہے کہ ہم تینوں صورتوں میں ایک ہی کلاس استعمال کر رہے ہیں۔
صفوں اور جنرکس کے ساتھ کام کرتے وقت ایریزر ٹائپ کریں۔
ایک بہت اہم نکتہ ہے جسے صفوں اور عام کلاسوں (جیسےList
) کے ساتھ کام کرتے وقت واضح طور پر سمجھنا ضروری ہے۔ اپنے پروگرام کے لیے ڈیٹا ڈھانچے کا انتخاب کرتے وقت آپ کو اسے بھی مدنظر رکھنا چاہیے۔ جنرک قسم مٹانے کے تابع ہیں۔ قسم کے پیرامیٹرز کے بارے میں معلومات رن ٹائم پر دستیاب نہیں ہے۔ اس کے برعکس، ارے پروگرام کے چلنے پر اپنے ڈیٹا کی قسم کے بارے میں جانتے ہیں اور استعمال کر سکتے ہیں۔ کسی صف میں غلط قسم ڈالنے کی کوشش ایک استثناء کا سبب بنے گی:
public class Main2 {
public static void main(String[] args) {
Object x[] = new String[3];
x[0] = new Integer(222);
}
}
کنسول آؤٹ پٹ:
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
چونکہ صفوں اور جنرک کے درمیان اتنا بڑا فرق ہے، ان میں مطابقت کے مسائل ہو سکتے ہیں۔ سب سے بڑھ کر، آپ عام اشیاء کی ایک صف یا یہاں تک کہ صرف ایک پیرامیٹرائزڈ صف نہیں بنا سکتے۔ کیا یہ تھوڑا سا الجھا ہوا لگتا ہے؟ آئیے ایک نظر ڈالتے ہیں۔ مثال کے طور پر، آپ جاوا میں اس میں سے کچھ نہیں کر سکتے ہیں۔
new List<T>[]
new List<String>[]
new T[]
اگر ہم اشیاء کی ایک صف بنانے کی کوشش کرتے ہیں List<String>
، تو ہمیں ایک تالیف کی غلطی ملتی ہے جو عام صف کی تخلیق کے بارے میں شکایت کرتی ہے:
import java.util.List;
public class Main2 {
public static void main(String[] args) {
// Compilation error! Generic array creation
List<String>[] stringLists = new List<String>[1];
}
}
لیکن ایسا کیوں کیا جاتا ہے؟ ایسی صفوں کی تخلیق کی اجازت کیوں نہیں ہے؟ یہ سب قسم کی حفاظت فراہم کرنے کے لیے ہے۔ اگر مرتب کرنے والا ہمیں عام اشیاء کی ایسی صفیں بنانے دیتا ہے، تو ہم اپنے لیے بہت ساری پریشانیاں پیدا کر سکتے ہیں۔ یہاں جوشوا بلوچ کی کتاب "Effective Java" سے ایک سادہ سی مثال ہے۔
public static void main(String[] args) {
List<String>[] stringLists = new List<String>[1]; // (1)
List<Integer> intList = Arrays.asList(42, 65, 44); // (2)
Object[] objects = stringLists; // (3)
objects[0] = intList; // (4)
String s = stringLists[0].get(0); // (5)
}
آئیے تصور کریں کہ اس طرح کی صف بنانے کی List<String>[] stringLists
اجازت ہے اور اس سے تالیف کی خرابی پیدا نہیں ہوگی۔ اگر یہ سچ تھا تو، یہاں کچھ چیزیں ہیں جو ہم کر سکتے ہیں: لائن 1 میں، ہم فہرستوں کی ایک صف بناتے ہیں: List<String>[] stringLists
۔ ہماری صف ایک پر مشتمل ہے List<String>
۔ لائن 2 میں، ہم نمبروں کی ایک فہرست بناتے ہیں: List<Integer>
۔ لائن 3 میں، ہم اپنے متغیر List<String>[]
کو تفویض کرتے ہیں Object[] objects
۔ جاوا زبان اس کی اجازت دیتی ہے: اشیاء کی ایک صف تمام ذیلی طبقات کی اشیاء اور اشیاء کو X
ذخیرہ کر سکتی ہے ۔ اس کے مطابق، آپ ایک صف میں کچھ بھی ڈال سکتے ہیں ۔ لائن 4 میں، ہم صف کے واحد عنصر (a ) کو a سے بدل دیتے ہیں ۔ اس طرح، ہم نے ایک صف میں ڈالا جس کا مقصد صرف اشیاء کو ذخیرہ کرنا تھا! ہمیں ایک غلطی کا سامنا صرف اس وقت ہوگا جب ہم لائن 5 پر عمل کریں گے۔ A کو رن ٹائم پر پھینک دیا جائے گا۔ اس کے مطابق، جاوا میں ایسی صفوں کی تخلیق پر پابندی شامل کی گئی تھی۔ یہ ہمیں ایسے حالات سے بچنے دیتا ہے۔ X
X
Object
objects()
List<String>
List<Integer>
List<Integer>
List<String>
ClassCastException
میں ٹائپ ایریزر کے ارد گرد کیسے حاصل کر سکتا ہوں؟
ٹھیک ہے، ہم نے ٹائپ ایریزر کے بارے میں سیکھا۔ آئیے سسٹم کو چکمہ دینے کی کوشش کریں! :) ٹاسک: ہمارے پاس ایک عامTestClass<T>
کلاس ہے۔ ہم اس کلاس کے لیے ایک طریقہ لکھنا چاہتے ہیں جو ایک نیا آبجیکٹ createNewT()
بنائے اور واپس کرے ۔ T
لیکن یہ ناممکن ہے، ٹھیک ہے؟ قسم کے بارے میں تمام معلومات T
تالیف کے دوران مٹ جاتی ہیں، اور رن ٹائم کے وقت ہم یہ تعین نہیں کر سکتے کہ ہمیں کس قسم کی آبجیکٹ بنانے کی ضرورت ہے۔ ایسا کرنے کا اصل میں ایک مشکل طریقہ ہے۔ آپ کو شاید یاد ہوگا کہ جاوا کی Class
کلاس ہے۔ ہم اسے اپنی کسی بھی چیز کی کلاس کا تعین کرنے کے لیے استعمال کر سکتے ہیں:
public class Main2 {
public static void main(String[] args) {
Class classInt = Integer.class;
Class classString = String.class;
System.out.println(classInt);
System.out.println(classString);
}
}
کنسول آؤٹ پٹ:
class java.lang.Integer
class java.lang.String
لیکن یہاں ایک پہلو ہے جس کے بارے میں ہم نے بات نہیں کی۔ اوریکل دستاویزات میں، آپ دیکھیں گے کہ کلاس کلاس عام ہے! 
https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
Integer.class
صرف نہیں ہے Class
، بلکہ Class<Integer>
۔ آبجیکٹ کی قسم String.class
صرف نہیں ہے Class
، بلکہ Class<String>
، وغیرہ۔ اگر یہ اب بھی واضح نہیں ہے، تو پچھلی مثال میں ٹائپ پیرامیٹر شامل کرنے کی کوشش کریں:
public class Main2 {
public static void main(String[] args) {
Class<Integer> classInt = Integer.class;
// Compilation error!
Class<String> classInt2 = Integer.class;
Class<String> classString = String.class;
// Compilation error!
Class<Double> classString2 = String.class;
}
}
اور اب، اس علم کا استعمال کرتے ہوئے، ہم ٹائپ ایریزر کو نظرانداز کر کے اپنا کام پورا کر سکتے ہیں! آئیے ٹائپ پیرامیٹر کے بارے میں معلومات حاصل کرنے کی کوشش کرتے ہیں۔ ہماری قسم کی دلیل یہ ہوگی MySecretClass
:
public class MySecretClass {
public MySecretClass() {
System.out.println("A MySecretClass object was created successfully!");
}
}
اور یہاں یہ ہے کہ ہم اپنے حل کو عملی طور پر کیسے استعمال کرتے ہیں:
public class TestClass<T> {
Class<T> typeParameterClass;
public TestClass(Class<T> typeParameterClass) {
this.typeParameterClass = typeParameterClass;
}
public T createNewT() throws IllegalAccessException, InstantiationException {
T t = typeParameterClass.newInstance();
return t;
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
MySecretClass secret = testString.createNewT();
}
}
کنسول آؤٹ پٹ:
A MySecretClass object was created successfully!
ہم نے ابھی مطلوبہ کلاس آرگومنٹ کو اپنی عام کلاس کے کنسٹرکٹر کو دیا ہے۔
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
اس نے ہمیں قسم کی دلیل کے بارے میں معلومات کو محفوظ کرنے کی اجازت دی، اسے مکمل طور پر مٹ جانے سے روک دیا۔ نتیجے کے طور پر، ہم ایک T
چیز بنانے کے قابل تھے! :) اس کے ساتھ ہی آج کا سبق اختتام کو پہنچا۔ جنرک کے ساتھ کام کرتے وقت آپ کو ہمیشہ ٹائپ ایریزر یاد رکھنا چاہیے۔ یہ کام بہت آسان نہیں لگتا ہے، لیکن آپ کو سمجھنا چاہیے کہ جنرک جاوا زبان کا حصہ نہیں تھے جب اسے بنایا گیا تھا۔ یہ خصوصیت، جو ہمیں پیرامیٹرائزڈ مجموعے بنانے اور تالیف کے دوران غلطیوں کو پکڑنے میں مدد کرتی ہے، بعد میں اس سے نمٹا گیا۔ کچھ دوسری زبانوں میں جن میں پہلے ورژن سے جنرک شامل ہیں، کوئی قسم مٹانے والا نہیں ہے (مثال کے طور پر، C# میں)۔ ویسے، ہم نے جنرک کا مطالعہ نہیں کیا ہے! اگلے سبق میں، آپ جنرک کی چند مزید خصوصیات سے واقف ہوں گے۔ ابھی کے لئے، یہ ایک دو کاموں کو حل کرنے کے لئے اچھا ہو گا! :)
GO TO FULL VERSION