JavaRush /جاوا بلاگ /Random-UR /بلیوں کے لیے جنرک
Viacheslav
سطح

بلیوں کے لیے جنرک

گروپ میں شائع ہوا۔
بلیوں کے لیے عمومیات - 1

تعارف

آج ہم جاوا کے بارے میں جو کچھ جانتے ہیں اسے یاد رکھنے کا ایک بہترین دن ہے۔ سب سے اہم دستاویز کے مطابق، یعنی Java Language Specification (JLS - Java Language Specifiaction)، جاوا ایک مضبوطی سے ٹائپ کی گئی زبان ہے، جیسا کہ باب " باب 4. اقسام، قدریں، اور متغیرات " میں بیان کیا گیا ہے۔ اس کا کیا مطلب ہے؟ ہم کہتے ہیں کہ ہمارے پاس ایک اہم طریقہ ہے:
public static void main(String[] args) {
String text = "Hello world!";
System.out.println(text);
}
مضبوط ٹائپنگ اس بات کو یقینی بناتی ہے کہ جب یہ کوڈ مرتب کیا جائے گا، تو مرتب کرنے والا یہ چیک کرے گا کہ اگر ہم نے متن کے متغیر کی قسم کو String کے طور پر متعین کیا ہے، تو ہم اسے کسی دوسری قسم کے متغیر کے طور پر کہیں بھی استعمال کرنے کی کوشش نہیں کر رہے ہیں (مثال کے طور پر، بطور عدد) . مثال کے طور پر، اگر ہم ٹیکسٹ کی بجائے کسی قدر کو بچانے کی کوشش کرتے ہیں 2L(یعنی String کی بجائے لمبی) تو ہمیں کمپائل کے وقت ایک ایرر ملے گا:

Main.java:3: error: incompatible types: long cannot be converted to String
String text = 2L;
وہ. مضبوط ٹائپنگ آپ کو اس بات کو یقینی بنانے کی اجازت دیتی ہے کہ اشیاء پر کارروائیاں صرف اس صورت میں کی جاتی ہیں جب وہ کارروائیاں ان اشیاء کے لیے قانونی ہوں۔ اسے قسم کی حفاظت بھی کہا جاتا ہے۔ جیسا کہ JLS میں بتایا گیا ہے، جاوا میں اقسام کی دو قسمیں ہیں: قدیم قسمیں اور حوالہ کی اقسام۔ آپ جائزہ کے مضمون سے قدیم اقسام کے بارے میں یاد رکھ سکتے ہیں: " جاوا میں قدیم قسمیں: وہ اتنی قدیم نہیں ہیں ۔" حوالہ کی اقسام کی نمائندگی کلاس، انٹرفیس، یا صف سے کی جا سکتی ہے۔ اور آج ہم حوالہ کی اقسام میں دلچسپی لیں گے۔ اور آئیے صفوں کے ساتھ شروع کریں:
class Main {
  public static void main(String[] args) {
    String[] text = new String[5];
    text[0] = "Hello";
  }
}
یہ کوڈ غلطی کے بغیر چلتا ہے۔ جیسا کہ ہم جانتے ہیں (مثال کے طور پر، " Oracle Java Tutorial: Arrays " سے)، ایک سرنی ایک کنٹینر ہے جو صرف ایک قسم کا ڈیٹا ذخیرہ کرتا ہے۔ اس صورت میں - صرف لائنیں. آئیے اسٹرنگ کے بجائے ارے میں لانگ شامل کرنے کی کوشش کریں:
text[1] = 4L;
آئیے اس کوڈ کو چلاتے ہیں (مثال کے طور پر Repl.it آن لائن جاوا کمپائلر میں ) اور ایک ایرر حاصل کریں:
error: incompatible types: long cannot be converted to String
زبان کی صف اور قسم کی حفاظت نے ہمیں اس صف میں محفوظ کرنے کی اجازت نہیں دی جو قسم کے مطابق نہیں تھی۔ یہ قسم کی حفاظت کا مظہر ہے۔ ہمیں بتایا گیا: "غلطی کو ٹھیک کریں، لیکن تب تک میں کوڈ کو مرتب نہیں کروں گا۔" اور اس کے بارے میں سب سے اہم بات یہ ہے کہ یہ تالیف کے وقت ہوتا ہے، نہ کہ جب پروگرام شروع ہوتا ہے۔ یعنی، ہمیں فوری طور پر غلطیاں نظر آتی ہیں، نہ کہ "کسی دن"۔ اور چونکہ ہمیں صفوں کے بارے میں یاد ہے، آئیے جاوا کلیکشن فریم ورک کے بارے میں بھی یاد رکھیں ۔ ہمارے وہاں مختلف ڈھانچے تھے۔ مثال کے طور پر، فہرستیں. آئیے مثال کو دوبارہ لکھتے ہیں:
import java.util.*;
class Main {
  public static void main(String[] args) {
    List text = new ArrayList(5);
    text.add("Hello");
    text.add(4L);
    String test = text.get(0);
  }
}
testاسے مرتب کرتے وقت، ہمیں متغیر ابتدائی لائن پر ایک غلطی موصول ہوگی :
incompatible types: Object cannot be converted to String
ہمارے معاملے میں، فہرست کسی بھی شے (یعنی قسم کی آبجیکٹ) کو ذخیرہ کر سکتی ہے۔ اس لیے مرتب کرنے والے کا کہنا ہے کہ وہ اتنی ذمہ داری کا بوجھ نہیں اٹھا سکتا۔ لہذا، ہمیں واضح طور پر اس قسم کی وضاحت کرنے کی ضرورت ہے جو ہم فہرست سے حاصل کریں گے:
String test = (String) text.get(0);
اس اشارے کو ٹائپ کنورژن یا ٹائپ کاسٹنگ کہا جاتا ہے۔ اور اب سب کچھ ٹھیک کام کرے گا جب تک کہ ہم عنصر کو انڈیکس 1 پر حاصل کرنے کی کوشش نہ کریں، کیونکہ یہ لمبی قسم کا ہے۔ اور ہمیں ایک مناسب غلطی ملے گی، لیکن پہلے ہی پروگرام کے چلنے کے دوران (رن ٹائم میں):

type conversion, typecasting
Exception in thread "main" java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String
جیسا کہ ہم دیکھ سکتے ہیں، یہاں کئی اہم نقصانات ہیں۔ سب سے پہلے، ہمیں فہرست سے حاصل کردہ قدر کو سٹرنگ کلاس میں "کاسٹ" کرنے پر مجبور کیا جاتا ہے۔ متفق ہوں، یہ بدصورت ہے۔ دوم، کسی غلطی کی صورت میں، ہم اسے صرف اس وقت دیکھیں گے جب پروگرام پر عمل کیا جائے گا۔ اگر ہمارا کوڈ زیادہ پیچیدہ ہوتا، تو ہو سکتا ہے کہ ہم فوری طور پر ایسی غلطی کا پتہ نہ لگا سکیں۔ اور ڈویلپرز نے سوچنا شروع کیا کہ ایسے حالات میں کام کو کس طرح آسان اور کوڈ کو مزید واضح کیا جائے۔ اور وہ پیدا ہوئے - جنرک۔
بلیوں کے لیے عمومیات - 2

جنرک

تو، عام. یہ کیا ہے؟ عام استعمال شدہ اقسام کو بیان کرنے کا ایک خاص طریقہ ہے، جسے کوڈ مرتب کرنے والا اپنے کام میں قسم کی حفاظت کو یقینی بنانے کے لیے استعمال کر سکتا ہے۔ یہ کچھ اس طرح لگتا ہے:
بلیوں کے لیے عمومیات - 3
یہاں ایک مختصر مثال اور وضاحت ہے:
import java.util.*;
class Main {
  public static void main(String[] args) {
    List<String> text = new ArrayList<String>(5);
    text.add("Hello");
    text.add(4L);
    String test = text.get(1);
  }
}
اس مثال میں، ہم کہتے ہیں کہ ہمارے پاس صرف نہیں ہے List، لیکن List، جو صرف String قسم کی اشیاء کے ساتھ کام کرتا ہے۔ اور کوئی اور نہیں۔ جو صرف بریکٹ میں اشارہ کیا گیا ہے، ہم اسے محفوظ کر سکتے ہیں۔ ایسے "بریکٹ" کو "زاویہ بریکٹ" کہا جاتا ہے، یعنی زاویہ بریکٹ. مرتب کرنے والا برائے مہربانی ہمارے لیے چیک کرے گا کہ آیا ہم نے تاروں کی فہرست کے ساتھ کام کرتے ہوئے کوئی غلطی کی ہے (فہرست کو متن کا نام دیا گیا ہے)۔ مرتب کرنے والا دیکھے گا کہ ہم ڈھٹائی سے لانگ کو سٹرنگ لسٹ میں ڈالنے کی کوشش کر رہے ہیں۔ اور تالیف کے وقت یہ ایک غلطی دے گا:
error: no suitable method found for add(long)
آپ کو یاد ہوگا کہ String CharSequence کی اولاد ہے۔ اور کچھ ایسا کرنے کا فیصلہ کریں:
public static void main(String[] args) {
	ArrayList<CharSequence> text = new ArrayList<String>(5);
	text.add("Hello");
	String test = text.get(0);
}
لیکن یہ ممکن نہیں ہے اور ہمیں غلطی ہو جائے گی: error: incompatible types: ArrayList<String> cannot be converted to ArrayList<CharSequence> یہ عجیب لگتا ہے، کیونکہ۔ لائن CharSequence sec = "test";میں کوئی غلطی نہیں ہے۔ آئیے اس کا پتہ لگائیں۔ وہ اس رویے کے بارے میں کہتے ہیں: "Generics invariant ہیں۔" "غیر متزلزل" کیا ہے؟ مجھے پسند ہے کہ ویکیپیڈیا پر اس کے بارے میں مضمون " Covariance and contravariance " میں کیسے کہا گیا ہے:
بلیوں کے لیے عمومیات - 4
اس طرح، Invariance اخذ کردہ اقسام کے درمیان وراثت کی عدم موجودگی ہے۔ اگر بلی جانوروں کی ذیلی قسم ہے، تو سیٹ<Cats> Set<Animals> کی ذیلی قسم نہیں ہے اور Set<جانور> سیٹ<Cats> کی ذیلی قسم نہیں ہے۔ ویسے، یہ کہنے کے قابل ہے کہ جاوا SE 7 سے شروع کرتے ہوئے، نام نہاد " ڈائمنڈ آپریٹر " ظاہر ہوا۔ کیونکہ دو زاویہ بریکٹ <> ایک ہیرے کی طرح ہیں۔ یہ ہمیں اس طرح کی جنرک استعمال کرنے کی اجازت دیتا ہے:
public static void main(String[] args) {
  List<String> lines = new ArrayList<>();
  lines.add("Hello world!");
  System.out.println(lines);
}
اس کوڈ کی بنیاد پر، مرتب کرنے والا سمجھتا ہے کہ اگر ہم بائیں جانب اشارہ کرتے ہیں کہ اس میں ListString قسم کی اشیاء شامل ہوں گی، تو دائیں جانب ہمارا مطلب ہے کہ ہم linesایک نئی ArrayList کو ایک متغیر میں محفوظ کرنا چاہتے ہیں، جو ایک آبجیکٹ کو بھی ذخیرہ کرے گا۔ بائیں طرف بیان کردہ قسم کا۔ تو بائیں طرف سے مرتب کرنے والا دائیں طرف کی قسم کو سمجھتا ہے یا اس کا اندازہ لگاتا ہے۔ یہی وجہ ہے کہ اس رویے کو انگریزی میں ٹائپ انفرنس یا "Type Inference" کہا جاتا ہے۔ ایک اور دلچسپ چیز قابل توجہ ہے RAW Types یا "raw type"۔ کیونکہ جنرک ہمیشہ سے نہیں ہوتے ہیں، اور جاوا جب بھی ممکن ہو پسماندہ مطابقت کو برقرار رکھنے کی کوشش کرتا ہے، پھر جنرک کو کسی نہ کسی طرح کوڈ کے ساتھ کام کرنے پر مجبور کیا جاتا ہے جہاں کوئی عام وضاحت نہیں کی جاتی ہے۔ آئیے ایک مثال دیکھتے ہیں:
List<CharSequence> lines = new ArrayList<String>();
جیسا کہ ہمیں یاد ہے، اس طرح کی لائن generics کے تغیر کی وجہ سے مرتب نہیں ہوگی۔
List<Object> lines = new ArrayList<String>();
اور یہ بھی اسی وجہ سے مرتب نہیں کرے گا۔
List lines = new ArrayList<String>();
List<String> lines2 = new ArrayList();
ایسی لائنیں مرتب ہوں گی اور کام کریں گی۔ یہ ان میں ہے کہ Raw Types کا استعمال کیا جاتا ہے، یعنی غیر متعینہ اقسام ایک بار پھر، یہ بتانے کے قابل ہے کہ Raw Types کو جدید کوڈ میں استعمال نہیں کیا جانا چاہیے۔
بلیوں کے لیے عمومیات - 5

ٹائپ شدہ کلاسز

تو، ٹائپ شدہ کلاسز۔ آئیے دیکھتے ہیں کہ ہم اپنی ٹائپ شدہ کلاس کیسے لکھ سکتے ہیں۔ مثال کے طور پر، ہمارے پاس کلاس کا درجہ بندی ہے:
public static abstract class Animal {
  public abstract void voice();
}

public static class Cat extends Animal {
  public void voice(){
    System.out.println("Meow meow");
  }
}

public static class Dog extends Animal {
  public void voice(){
    System.out.println("Woof woof");
  }
}
ہم ایک ایسی کلاس بنانا چاہتے ہیں جو جانوروں کے کنٹینر کو لاگو کرے۔ ایک کلاس لکھنا ممکن ہو گا جس میں کوئی بھی ہو گا Animal۔ یہ سادہ، قابل فہم ہے، لیکن... کتوں اور بلیوں کو ملانا برا ہے، وہ ایک دوسرے کے دوست نہیں ہیں۔ اس کے علاوہ، اگر کسی کو ایسا کنٹینر ملتا ہے، تو وہ غلطی سے اس کنٹینر سے بلیوں کو کتوں کے پیکٹ میں ڈال سکتا ہے... اور اس سے کوئی فائدہ نہیں ہوگا۔ اور یہاں جنرک ہماری مدد کریں گے۔ مثال کے طور پر، عمل درآمد کو اس طرح لکھیں:
public static class Box<T> {
  List<T> slots = new ArrayList<>();
  public List<T> getSlots() {
    return slots;
  }
}
ہماری کلاس ٹی نامی ایک عام کی طرف سے مخصوص قسم کی اشیاء کے ساتھ کام کرے گی۔ یہ ایک قسم کا عرف ہے۔ کیونکہ عام کو کلاس کے نام میں بیان کیا گیا ہے، پھر ہم اسے کلاس کا اعلان کرتے وقت وصول کریں گے:
public static void main(String[] args) {
  Box<Cat> catBox = new Box<>();
  Cat murzik = new Cat();
  catBox.getSlots().add(murzik);
}
جیسا کہ ہم دیکھ سکتے ہیں، ہم نے اشارہ کیا کہ ہمارے پاس ہے Box، جو صرف کے ساتھ کام کرتا ہے Cat۔ مرتب کرنے والے نے محسوس کیا کہ catBoxعام کی بجائے، Tآپ کو اس قسم کو تبدیل کرنے کی ضرورت ہے Catجہاں بھی عام کا نام بیان کیا گیا ہے T:
بلیوں کے لیے عمومیات - 6
وہ. Box<Cat>یہ کمپائلر کی بدولت ہے کہ وہ سمجھتا ہے کہ slotsاسے اصل میں کیا ہونا چاہیے List<Cat>۔ کے لئے Box<Dog>اندر ہو گا slots, پر مشتمل List<Dog>. ایک قسم کے اعلامیے میں کئی جنرک ہو سکتے ہیں، مثال کے طور پر:
public static class Box<T, V> {
عام نام کچھ بھی ہو سکتا ہے، حالانکہ کچھ غیر کہے گئے اصولوں پر عمل کرنے کی سفارش کی جاتی ہے - "ٹائپ پیرامیٹر نام سازی کنونشنز": عنصر کی قسم - E، کلیدی قسم - K، نمبر کی قسم - N، T - قسم کے لیے، V - قدر کی قسم کے لیے . ویسے، یاد رہے کہ ہم نے کہا تھا کہ generics invariant ہیں، یعنی وراثت کے درجہ بندی کو محفوظ نہ رکھیں۔ اصل میں، ہم اس پر اثر انداز کر سکتے ہیں. یعنی، ہمارے پاس generics COvariant بنانے کا موقع ہے، یعنی وراثت کو اسی ترتیب میں رکھنا۔ اس رویے کو "Bounded Type" کہا جاتا ہے، یعنی محدود اقسام. مثال کے طور پر، ہماری کلاس میں Boxتمام جانور شامل ہو سکتے ہیں، پھر ہم اس طرح کا عام اعلان کریں گے:
public static class Box<T extends Animal> {
یعنی ہم نے کلاس کے لیے اوپری حد مقرر کی ہے Animal۔ ہم مطلوبہ الفاظ کے بعد کئی اقسام کی بھی وضاحت کر سکتے ہیں extends۔ اس کا مطلب یہ ہوگا کہ جس قسم کے ساتھ ہم کام کریں گے وہ کسی نہ کسی طبقے کی نسل سے ہونی چاہیے اور ساتھ ہی ساتھ کچھ انٹرفیس کو بھی نافذ کریں۔ مثال کے طور پر:
public static class Box<T extends Animal & Comparable> {
اس صورت میں، اگر ہم Boxکسی ایسی چیز کو ڈالنے کی کوشش کرتے ہیں جو وارث نہیں ہے Animalاور اس پر عمل درآمد نہیں کرتا ہے Comparable، تو تالیف کے دوران ہمیں ایک غلطی موصول ہوگی:
error: type argument Cat is not within bounds of type-variable T
بلیوں کے لیے عمومیات - 7

ٹائپنگ کا طریقہ

جنرکس نہ صرف اقسام میں بلکہ انفرادی طریقوں میں بھی استعمال ہوتے ہیں۔ طریقوں کا اطلاق سرکاری ٹیوٹوریل میں دیکھا جا سکتا ہے: " Generic Methods

پس منظر:

بلیوں کے لیے عمومیات - 8
آئیے اس تصویر کو دیکھتے ہیں۔ جیسا کہ آپ دیکھ سکتے ہیں، مرتب کرنے والا طریقہ کے دستخط کو دیکھتا ہے اور دیکھتا ہے کہ ہم کچھ غیر متعینہ کلاس ان پٹ کے طور پر لے رہے ہیں۔ یہ دستخط کے ذریعہ اس بات کا تعین نہیں کرتا ہے کہ ہم کسی قسم کی چیز کو واپس کر رہے ہیں، یعنی چیز. لہذا، اگر ہم ایک ArrayList بنانا چاہتے ہیں، تو ہمیں یہ کرنا ہوگا:
ArrayList<String> object = (ArrayList<String>) createObject(ArrayList.class);
آپ کو واضح طور پر لکھنا ہوگا کہ آؤٹ پٹ ایک ArrayList ہوگی، جو بدصورت ہے اور غلطی کرنے کا موقع بڑھاتی ہے۔ مثال کے طور پر، ہم ایسی بکواس لکھ سکتے ہیں اور یہ مرتب کرے گا:
ArrayList object = (ArrayList) createObject(LinkedList.class);
کیا ہم مرتب کرنے والے کی مدد کر سکتے ہیں؟ ہاں، عمومیات ہمیں ایسا کرنے کی اجازت دیتی ہیں۔ آئیے اسی مثال کو دیکھتے ہیں:
بلیوں کے لیے عمومیات - 9
پھر، ہم اس طرح ایک چیز بنا سکتے ہیں:
ArrayList<String> object = createObject(ArrayList.class);
بلیوں کے لیے عمومیات - 10

وائلڈ کارڈ

جینرکس پر اوریکل کے ٹیوٹوریل کے مطابق، خاص طور پر " وائلڈ کارڈز " سیکشن، ہم سوالیہ نشان کے ساتھ "نامعلوم قسم" کی وضاحت کر سکتے ہیں۔ وائلڈ کارڈ جنرک کی کچھ حدود کو کم کرنے کے لیے ایک آسان ٹول ہے۔ مثال کے طور پر، جیسا کہ ہم نے پہلے بات کی، generics invariant ہیں۔ اس کا مطلب یہ ہے کہ اگرچہ تمام کلاسیں آبجیکٹ کی قسم کی اولاد (ذیلی قسمیں) ہیں، لیکن یہ List<любой тип>ذیلی قسم نہیں ہے List<Object>۔ لیکن، List<любой тип>یہ ایک ذیلی قسم ہے List<?>۔ تو ہم درج ذیل کوڈ لکھ سکتے ہیں:
public static void printList(List<?> list) {
  for (Object elem: list) {
    System.out.print(elem + " ");
  }
  System.out.println();
}
ریگولر جنرکس کی طرح (یعنی وائلڈ کارڈ کے استعمال کے بغیر)، وائلڈ کارڈ کے ساتھ جنرک محدود ہو سکتے ہیں۔ اوپری باؤنڈڈ وائلڈ کارڈ واقف نظر آتا ہے:
public static void printCatList(List<? extends Cat> list) {
  for (Cat cat: list) {
    System.out.print(cat + " ");
  }
  System.out.println();
}
لیکن آپ اسے لوئر باؤنڈ وائلڈ کارڈ کے ذریعے بھی محدود کر سکتے ہیں:
public static void printCatList(List<? super Cat> list) {
اس طرح، طریقہ تمام بلیوں کو قبول کرنا شروع کر دے گا، اور ساتھ ہی درجہ بندی میں اعلیٰ (آبجیکٹ تک)۔
بلیوں کے لیے عمومیات - 11

Erasure ٹائپ کریں۔

جنرک کے بارے میں بات کرتے ہوئے، "ٹائپ ایریزنگ" کے بارے میں جاننا ضروری ہے۔ درحقیقت، ٹائپ ایریزر اس حقیقت کے بارے میں ہے کہ جنرک کمپائلر کے لیے معلومات ہیں۔ پروگرام کے عمل کے دوران، جنرک کے بارے میں مزید معلومات نہیں ہوتی ہیں، اسے "مٹانے" کہا جاتا ہے۔ اس مٹانے کا اثر ہوتا ہے کہ عام قسم کو مخصوص قسم سے بدل دیا جاتا ہے۔ اگر عام کی کوئی حد نہیں تھی، تو آبجیکٹ کی قسم کو بدل دیا جائے گا۔ اگر سرحد کی وضاحت کی گئی تھی (مثال کے طور پر <T extends Comparable>)، تو اسے بدل دیا جائے گا۔ یہاں اوریکل کے ٹیوٹوریل سے ایک مثال ہے: " عمومی اقسام کا مٹاؤ ":
بلیوں کے لیے عمومیات - 12
جیسا کہ اوپر کہا گیا ہے، اس مثال میں عام کو Tاس کی سرحد پر مٹا دیا گیا ہے، یعنی پہلے Comparable_
بلیوں کے لیے عمومیات - 13

نتیجہ

جنرک ایک بہت دلچسپ موضوع ہے۔ مجھے امید ہے کہ یہ موضوع آپ کے لیے دلچسپی کا باعث ہوگا۔ خلاصہ کرنے کے لیے، ہم کہہ سکتے ہیں کہ جنرکس ایک بہترین ٹول ہے جو ڈویلپرز کو کمپائلر کو اضافی معلومات کے ساتھ فوری طور پر موصول ہوا ہے تاکہ ایک طرف قسم کی حفاظت اور دوسری طرف لچک کو یقینی بنایا جا سکے۔ اور اگر آپ دلچسپی رکھتے ہیں، تو میں تجویز کرتا ہوں کہ آپ ان وسائل کو دیکھیں جو مجھے پسند ہیں: #ویاچسلاو
تبصرے
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION