مدرس: پارسا کاویانپور
جنریک در JDK5 به پلت فرم جاوا اضافه شد.استفاده از جنریک ها این امکان را فراهم میکند که کلاس ها, واسط ها و توابعی ایجاد کنید که بتوانند با هر نوع داده ای کار کنند.در بسیاری از الگوریتم های منطقی, نوع داده ی به کار رفته اهمیت چندانی ندارد. بعنوان مثال برای الگوریتمی که یک Stack را پیاده سازی میکند اهمیتی ندارد که داده ی ذخیره شده در آن عدد صحیح باشد یا رشته, شی یا نخ .جنریک ها میتوانند گزینه ی مناسبی برای پیاده سازی چنین دست الگوریتم هایی باشد.
میدانید که به واسطه ی اینکه در جاوا تمام کلاس ها بصورت مستقیم و غیر مستقیم فرزند Object هستند میتوانیم متد ها و کلاس هایی بسازیم که با نوع داده ای Object سر و کار داشته باشند بدین شکل با دیگر نوع داده ها نیز کار خواهند کرد.درواقع قبل از Jdk 5 برنامه نویسان بدین شکل متد های عمومی ایجاد میکردند.اما این روش مشکلاتی را مربوط به type casting در کد بوجود میاورد و با امدن جنریک از فرایند کستینگ جلوگیری شد (حداقل جلوی چشم کاربر. اما در سطح کامپایلر همان casting انجام میگیرد)
برای مثال کد زیر را در نظر بگیرید:
public static Object doSomething(Object obj){
return null;
}
public static<T> T doSomething(T t){
return null;
}
|
هنگام کار با doSomething نسخه ی جنریک خبری از تبدیل نوع یا کست نیست.
;int i=1 ;int i2= doSomething(i) |
اما اگر از متدی که با کلاس Object سر و کار دارد استفاده کنیم:
;int i=1 ;int i2=(int) doSomething(i) |
از کجا معلوم که در نسخه ی Object واقعا همان نوعی سازگار با نوع ورودی ریترن شود؟
باید این مسئله را مورد بررسی قرار دهیم که به ClassCastException
بر نخوریم.
اما با جنریک از تمام این گرفتاری ها خلاص میشویم.
} <class Stats<T ; T[] items
} Stats(T[] o) ;items[]=o { {
|
فرض کنید میخواهیم به کد بالا متدی اضافه کنیم که میانگین داده های nums را محاسبه کند.در نگاه اول چنین کدی به نظر ما خواهد رسید :
} <class Stats<T ; T[] items
} Stats(T[] o) ;items[]=o { } ()double average double sum=0.0 } for(int i=0;i<nums.length;i++) ;sum+=nums[i] { ; return su {
|
اما کد فوق کامپایل نمیشود. از کجا معلوم پارامتر T یک داده ی عددی باشد؟ شاید String پاس داده شود:
;()<>Stats<String> s = new Stats ;()s.average |
در چنین مواقعی باید از جنریک های کران دار (bounded types) استفاده کنیم.
میدانیم که تمام کلاس های Short ,Byte,Double,Float,Long,Integer از یک کلاسی به نام Number ارث بری میکنند. باید به نوعی مشخص کنیم تمام T هایی که برای ما میآیند زیر کلاسی از Number باشند. چگونه ؟به کد زیر توجه کنید:
class Stats<T extends Number>{ T[] nums; Stats(T[] o){ nums=o; } double average(){ double sum=0.0; for(int i=0;i<nums.length;i++) sum+=nums[i].doubleValue(); return sum; } |
نکته: متد doubleValue در کلاس Number تعریف شده است. وقتی میگوییم T حتما کلاسی باشد که از Number ارث بری کرده است طبعا تمام متد های کلاس Number را دارد. بنابراین ما میتوانیم از متد های کلاس Number استفاده کنیم.
نکته : جنریک ها میتوانند کراندار به اینترفیس هم باشند:
class ClassName<T extends ClassOrInterface>{ } |
و یا هردو با هم :
class ClassName<T extends Class & Interface>{ } |
حال فرض کنید متدی میخواهیم اضافه کنیم به نام sameAverage که بررسی میکند ایا دو تا کلاس Stats مقادیر average یکسانی دارند یا خیر. در ابتدا شاید پیاده سازی زیر به نظرتان معقول بیاید:
class Stats<T extends Number>{ T[] nums; Stats(T[] o){ nums=o; } double average(){ double sum=0.0; for(int i=0;i<nums.length;i++) sum+=nums[i].doubleValue(); return sum; boolean sameAverage(Stats<T> ob){ return average()==ob.average(); } |
هنگام کامپایل کد فوق خطایی مشاهده نخواهید کرد. تنها یک ایراد وجود دارد. متد sameAverage تنها میتواند با نوعی از Stats کار کند که پارامتر T ان با پارامتر T خودش یکسان باشد(فرضا هردو double باشند) و این یک راهکار عمومی نیست چرا که اگر Stats اولی double باشد و حاوی 2.0 و 3.0 باشد و Stats دومی Integer باشد و حاوی 2و 3 باشد باید average یکسانی داشته باشند.
راه حل : استفاده از Wildcard
در وضعیت هایی مشابه کد فوق wildcard مورد استفاده قرار میگیرد. wildcard با استفاده از علامت سوال <?> مشخص میشود و نشاندهنده ی یک نوع ناشناخته است. بنابراین کد فوق را به شکل زیر بازنویسی میکنیم:
class Stats<T extends Number>{ T[] nums; Stats(T[] o){ nums=o; } double average(){ double sum=0.0; for(int i=0;i<nums.length;i++) sum+=nums[i].doubleValue(); return sum; boolean sameAverage(Stats<?> ob){ return average()==ob.average(); } |
مشابه همان روش محدود کردن پارامتر هاست :
methodOrClass<? extends superclass> |
اما میتوانیم کران پایین هم تعریف کنیم:
methodOrClass<? super subclass> |
یعنی تنها کلاس هایی مورد پذیرش هستند که superclass ای برای subclass باشند.
مثال دیگر از wildcard ها:
فرض کنید متدی بر مبنای الگوریتم insert ionSort برای مرتب سازی داریم که جنریک است:
public static <T extends Comparable> void sort(List<T> list) { for (int i = 0; i < list.size(); i++) { T key = list.get(i); int z = i; while (z > 0 && list.get(z - 1).compareTo( key) > 0) { list.set(z, list.get(z - 1)); z--; } list.set(z, key); } } |
در مقاله ای جدا به بررسی عملکرد مرتب سازی درجی میپردازیم.
کد بالا درواقع عالی است و کار میکند اما یک مشکل ریز دارد. ما نگفتیم Comparable چه چیزی باشد (زیر خود جنریک است). وقتی نمیگوییم مجددا داریم از Object استفاده میکنیم. هدف ما از Generic استفاده نکردن از کلاس Object و درگیر نشدن با مشکلات casting است.درواقع کد بالا معادل کد زیر است:
public static <T extends Comparable<Object>> void sort(List<T> list) { |
شاید راه حلی که اول به ذهنمان میرسد عاقلانه باشد. به جای Object همان T بگذاریم:
public static <T extends Comparable<T>> void sort(List<T> list) { for (int i = 0; i < list.size(); i++) { T key = list.get(i); int z = i; while (z > 0 && list.get(z - 1).compareTo( key) > 0) { list.set(z, list.get(z - 1)); z--; } list.set(z, key); } } |
اما این نیز کاملا Generic نیست. در ارث بری ما میدانیم متد های کلاس پدر باید بتوانند
بر روی اشیایی از کلاس فرزند فراخوانی شده و رفتار مناسبی انجام دهند. با در نظر گرفتن این حالت اگر برای T سوپر کلاسی داشتیم و آن super class اینترفیس Comparable را پیاده سازی کرده بود. باید بتوانیم از آن پیاده سازی برای T که sub class است نیز استفاده نماییم:
public static <T extends Comparable<? super T>> void sort(List<T> list) { for (int i = 0; i < list.size(); i++) { T key = list.get(i); int z = i; while (z > 0 && list.get(z - 1).compareTo( key) > 0) { list.set(z, list.get(z - 1)); z--; } list.set(z, key); } } |
کد فوق یک کد کاملا Generic است.
به منظور جلوگیری از عدم سازگاری کد های قدیمی با جدید (میدانید که کد های jdk های پایینتر روی jdk های بالاتر قابل اجرا هستند) جاوا از Erasure استفاده میکند.
به هنگام کامپایل کردن کد های جاوا تمام اطلاعات مربوط به جنریک ها حذف میشود (erase میشود).
مثال :
class Stats<T>{ T[] nums; Stats(T[] o){ nums=o; } } |
کلاس فوق درواقع پس از کامپایل با کلاس زیر جایگزین میشود:
class Stats{ Object[] nums; Stats(Object[] o){ nums=o; } } |
البته اگر T کراندار به کلاسی باشد به جای ابجکت ان کلاس مینشیند:
class Stats<T extends Number>{ T[] nums; Stats(T[] o){ nums=o; } } |
به شکل زیر در میآید:
class Stats{ Number[] nums; Stats(Number[] o){ nums=o; } } |
و اما بریم سراغ محدودیت های Generic:
نمیتوان پارامتر نوع را new کرد:
class Stats<T>{ T[] nums; Stats(T[] o){ nums=new T();// Illegal!!! } } |
دلیل ان ساده است .هنگام new شدن یک شی باید اطلاعاتی از اینکه این شی واقعا چه شی ای است در دست باشد اما وقتی توسط Erasure با Object جایگزین میشود دیگر اطلاعاتی از این دست که T واقعا چی بوده از بین میرود و بنابراین نمیتوان انرا new کرد و همچنین نمیتوان از ان آرایه نیز ساخت!به مثال زیر توجه کنید:
class Stats<T>{ T[] nums= new T[5]; } |
دلیل: اطلاعات مربوط به نوع T در زمان اجرا توسط Erasure از بین میرود درحالی که ایجاد یک آرایه در جاوا نیازمند دانستن اطلاعی درمورد نوع ان داده است.
راه حل: استفاده از ارایه ای از Object
راه حل دوم : استفاده از رفلکشن. کد زیر مثالی در این باره است:
class Array<E> { private final E[] arr; public final int length; public Array(Class<E> type, int length) { this.arr = (E[]) java.lang.reflect.Array.newInstance(type, length); this.length = length; } } |
ورود مفهوم جنریک به دنیای جاوا با خود نوع جدیدی از خطاها با عنوان Ambiguity به همراه اورد. این نوع خطا مربوط به ویژگی erasure در جاواست :
هنگام کامپایل کد فوق با خطا مواجه خواهید شد:
class Stats<T,V>{ public void set(T t){ |
دلیل ان ساده است...هنگام حذف پارامتر نوع در فرایند Erasure هر دوی V و T با کلاس Object جایگزین میشوند و در متد های set ابهام بوجود میاید(متد های overload شده نباید پارامتر از نوع یکسان داشته باشند).
چرا Generic ها با داده های primitive نمیتوانند کار کنند ؟
چون Erasure پارامتر را با Object جایگزین میکند (با این استدلال که پدر تمام کلاس هاست) اما primitive type ها اصلا کلاس نیستند !بنابراین در bounded types ها به مشکل خواهند خورد.
موفق باشید.
بستن *نام و نام خانوادگی * پست الکترونیک * متن پیام |
دوره های آموزشی برنامه نویسی
انجام پروژه های برنامه نویسی
تدریس خصوصی برنامه نویسی
بیش از 7 سال از فعالیت جاواپرو میگذرد
جاواپرو دارای مجوز نشر دیجیتال از وزارت فرهنگ و ارشاد اسلامی است
جهت ارتباط مستقیم با جاواپرو در واتساپ و تلگرام :
09301904690
بستن دیگر باز نشو! |