پرش به محتوای اصلی
مسیر تخصصی جاوا و Spring
درخواست پروژهتدریس خصوصیارتباط با ماراهنما
جاواپرو v1.2.19یاد بگیر، تمرین کن، حرفه‌ای بساز
خانهدوره‌هاآموزش رایگانوبلاگمدرس‌هاخدمات
خانهمعرفی کلاس های Generic در زبان برنامه نویسی جاوا
وبلاگ »

معرفی کلاس های Generic در زبان برنامه نویسی جاوا

جنریک در زبان برنامه نویسی جاوا چیست و در چه شرایطی از آن استفاده میشود؟

۷ دقیقه مطالعهوبلاگ »آموزش جاوا و انجام پروژه های برنامه نویسی
معرفی کلاس های Generic در زبان برنامه نویسی جاوا
محتوای منتخب جاواپرو

خلاصه مطلب

جنریک در زبان برنامه نویسی جاوا چیست و در چه شرایطی از آن استفاده میشود؟
نوع محتوامقاله آموزشی
موضوعوبلاگ »
زمان مطالعه۷ دقیقه
آخرین ویرایش۳۰ اردیبهشت ۱۴۰۴
Generic
Generic

مدرس: پارسا کاویانپور

آموزش Generic در دوره پیشرفته زبان برنامه نویسی جاوا
آموزش Generic در دوره پیشرفته زبان برنامه نویسی جاوا

لیست دوره های آکادمی جاوا پرو

دوره پرتاب | آموزش پیش نیازهای قبل از شروع برنامه نویسیدوره مقدماتی زبان برنامه نویسی جاوادوره جامع شاهکار پیشرفته جاوادوره جامع طلایی Spring Coreدوره آموزش فریمورک Spring Bootدوره پروژه محور Spring Boot - سیستم دانشگاهدوره جامع دژبان Spring securityدوره جامع نخبگان معماری میکروسرویس با Java و Spring Boot

جنریک در JDK5 به پلت فرم جاوا اضافه شد.استفاده از جنریک ها این امکان را فراهم میکند که کلاس ها , واسط ها و توابعی ایجاد کنید که بتوانند با هر نوع داده ای کار کنند.در بسیاری از الگوریتم های منطقی , نوع داده ی به کار رفته اهمیت چندانی ندارد. بعنوان مثال برای الگوریتمی که یک Stack را پیاده سازی میکند اهمیتی ندارد که داده ی ذخیره شده در آن عدد صحیح باشد یا رشته , شی یا نخ .جنریک ها میتوانند گزینه ی مناسبی برای پیاده سازی چنین دست الگوریتم هایی باشد.

فرق استفاده از Object با جنریک چیست؟

میدانید که به واسطه ی اینکه در جاوا تمام کلاس ها بصورت مستقیم و غیر مستقیم فرزند Object هستند میتوانیم متد ها و کلاس هایی بسازیم که با نوع داده ای Object سر و کار داشته باشند بدین شکل با دیگر نوع داده ها نیز کار خواهند کرد.درواقع قبل از Jdk 5 برنامه نویسان بدین شکل متد های عمومی ایجاد میکردند.اما این روش مشکلاتی را مربوط به type casting در کد بوجود می اورد و با امدن جنریک از فرایند کستینگ جلوگیری شد (حداقل جلوی چشم کاربر. اما در سطح کامپایلر همان casting انجام میگیرد)

برای مثال کد زیر را در نظر بگیرید:

public static Object doSomething(Object obj){ return null; } public static 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 بر نخوریم.

اما با جنریک از تمام این گرفتاری ها خلاص میشویم.

}

فرض کنید میخواهیم به کد بالا متدی اضافه کنیم که میانگین داده های nums را محاسبه کند.در نگاه اول چنین کدی به نظر ما خواهد رسید :

}

اما کد فوق کامپایل نمیشود . از کجا معلوم پارامتر T یک داده ی عددی باشد؟ شاید String پاس داده شود:

;()Stats s = new Stats;()s.average

در چنین مواقعی باید از جنریک های کران دار (bounded types) استفاده کنیم.

میدانیم که تمام کلاس های Short ,Byte,Double,Float,Long,Integer از یک کلاسی به نام Number ارث بری میکنند. باید به نوعی مشخص کنیم تمام T هایی که برای ما می آیند زیر کلاسی از Number باشند. چگونه ؟به کد زیر توجه کنید:

class Stats{ T[] nums; Stats(T[] o){ nums=o; } double average(){ double sum=0.0; for(int i=0;i

نکته: متد doubleValue در کلاس Number تعریف شده است. وقتی میگوییم T حتما کلاسی باشد که از Number ارث بری کرده است طبعا تمام متد های کلاس Number را دارد . بنابراین ما میتوانیم از متد های کلاس Number استفاده کنیم.

نکته : جنریک ها میتوانند کراندار به اینترفیس هم باشند:

class ClassName{}

و یا هردو با هم :

class ClassName{}

حال فرض کنید متدی میخواهیم اضافه کنیم به نام sameAverage که بررسی میکند ایا دو تا کلاس Stats مقادیر average یکسانی دارند یا خیر. در ابتدا شاید پیاده سازی زیر به نظرتان معقول بیاید:

class Stats{ T[] nums; Stats(T[] o){ nums=o; } double average(){ double sum=0.0; for(int i=0;i

هنگام کامپایل کد فوق خطایی مشاهده نخواهید کرد . تنها یک ایراد وجود دارد. متد sameAverage تنها میتواند با نوعی از Stats کار کند که پارامتر T ان با پارامتر T خودش یکسان باشد(فرضا هردو double باشند) و این یک راهکار عمومی نیست چرا که اگر Stats اولی double باشد و حاوی 2.0 و 3.0 باشد و Stats دومی Integer باشد و حاوی 2و 3 باشد باید average یکسانی داشته باشند.

راه حل : استفاده از Wildcard

آرگومان wildcard چیست؟

در وضعیت هایی مشابه کد فوق wildcard مورد استفاده قرار میگیرد. wildcard با استفاده از علامت سوال مشخص میشود و نشاندهنده ی یک نوع ناشناخته است. بنابراین کد فوق را به شکل زیر بازنویسی میکنیم:

class Stats{ T[] nums; Stats(T[] o){ nums=o; } double average(){ double sum=0.0; for(int i=0;i

کراندار کردن Wildcard ها

مشابه همان روش محدود کردن پارامتر هاست :

methodOrClass

اما میتوانیم کران پایین هم تعریف کنیم:

methodOrClass

یعنی تنها کلاس هایی مورد پذیرش هستند که superclass ای برای subclass باشند.

مثال دیگر از wildcard ها:

فرض کنید متدی بر مبنای الگوریتم insertionSort برای مرتب سازی داریم که جنریک است:

public static void sort(List 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 void sort(List 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); } }

شاید راه حلی که اول به ذهنمان میرسد عاقلانه باشد. به جای Object همان T بگذاریم:

public static void sort(List 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

کد فوق یک کد کاملا Generic است.

Erasure

به منظور جلوگیری از عدم سازگاری کد های قدیمی با جدید (میدانید که کد های jdk های پایینتر روی jdk های بالاتر قابل اجرا هستند) جاوا از Erasure استفاده میکند.

به هنگام کامپایل کردن کد های جاوا تمام اطلاعات مربوط به جنریک ها حذف میشود ( erase میشود).

مثال :

class Stats{ T[] nums; Stats(T[] o){ nums=o; } }

کلاس فوق درواقع پس از کامپایل با کلاس زیر جایگزین میشود:

class Stats{ Object[] nums; Stats(Object[] o){ nums=o; } }

البته اگر T کراندار به کلاسی باشد به جای ابجکت ان کلاس مینشیند:

class Stats{ T[] nums; Stats(T[] o){ nums=o; } }

به شکل زیر در می آید:

class Stats{ Number[] nums; Stats(Number[] o){ nums=o; } }

و اما بریم سراغ محدودیت های Generic :

نمیتوان پارامتر نوع را new کرد:

class Stats{ T[] nums; Stats(T[] o){ nums=new T();// Illegal!!! } }

دلیل ان ساده است .هنگام new شدن یک شی باید اطلاعاتی از اینکه این شی واقعا چه شی ای است در دست باشد اما وقتی توسط Erasure با Object جایگزین میشود دیگر اطلاعاتی از این دست که T واقعا چی بوده از بین میرود و بنابراین نمیتوان انرا new کرد و همچنین نمیتوان از ان آرایه نیز ساخت!به مثال زیر توجه کنید:

class Stats{ T[] nums= new T[5]; }

دلیل: اطلاعات مربوط به نوع T در زمان اجرا توسط Erasure از بین میرود درحالی که ایجاد یک آرایه در جاوا نیازمند دانستن اطلاعی درمورد نوع ان داده است .

راه حل: استفاده از ارایه ای از Object

راه حل دوم : استفاده از رفلکشن. کد زیر مثالی در این باره است:

class Array { private final E[] arr; public final int length; public Array(Class type, int length) { this.arr = (E[]) java.lang.reflect.Array.newInstance(type, length); this.length = length; } }

خطاهای مبهم (Ambiguity Error)

ورود مفهوم جنریک به دنیای جاوا با خود نوع جدیدی از خطاها با عنوان Ambiguity به همراه اورد . این نوع خطا مربوط به ویژگی erasure در جاواست :

هنگام کامپایل کد فوق با خطا مواجه خواهید شد:

class Stats{ T t; V v; public void set(T t){ this.t=t; } public void set(V v){ this.v=v; } }

دلیل ان ساده است...هنگام حذف پارامتر نوع در فرایند Erasure هر دوی V و T با کلاس Object جایگزین میشوند و در متد های set ابهام بوجود می اید(متد های overload شده نباید پارامتر از نوع یکسان داشته باشند).

چرا Generic ها با داده های primitive نمیتوانند کار کنند ؟

چون Erasure پارامتر را با Object جایگزین میکند (با این استدلال که پدر تمام کلاس هاست) اما primitive type ها اصلا کلاس نیستند !بنابراین در bounded types ها به مشکل خواهند خورد.

موفق باشید.

برچسب‌هاوبلاگ »مقالات جاوا »Generic چیست؟تبدیل نوع چیست؟متد های جنریک چیست؟کلاس جنریک چیست؟type erasure چیست؟wildcard چیست؟جنریک در جاوا چیستجنریکجنریک در جاواجنریک چیستمتدهای جنریکآموزش جنریک
دسترسی سریع

فهرست مطلب

۱فرق استفاده از Object با جنریک چیست؟۲آرگومان wildcard چیست؟۳کراندار کردن Wildcard ها۴Erasure۵خطاهای مبهم (Ambiguity Error)
ادامه مسیر

پیشنهاد جاواپرو

موضوع را با آموزش‌های مرتبط ادامه دهید و نکته‌های کاربردی را در مسیر خود نگه دارید

مشاهده دوره‌ها
مطالب پیشنهادی

ادامه مطالعه

xدسته‌بندی نشده
۱۱ دی ۱۳۹۸ مطالعه کوتاه

x

پادکست شماره 3 – راه های کسب درآمد از برنامه نویسی

مطالعه مطلب
پادکست شماره 3 - راه های کسب درآمد از برنامه نویسیپادکست
۲۹ اردیبهشت ۱۴۰۴ مطالعه کوتاه

پادکست شماره 3 - راه های کسب درآمد از برنامه نویسی

در این پادکست برنامه نویسی به شیوه ها و راه های کسب درآمد از برنامه نویسی پرداخته ایم. یک کاغذ و قلم بردارید و به پادکست گوش بدید.

مطالعه مطلب
پادکست شماره 2 - چگونه وارد فضای برنامه نویسی شویم؟پادکست
۲۹ اردیبهشت ۱۴۰۴ مطالعه کوتاه

پادکست شماره 2 - چگونه وارد فضای برنامه نویسی شویم؟

در این پادکست برنامه نویسی یاد میگیریم که چگونه وارد فضای برنامه نویسی شویم و تخصص برنامه نویسی را به دست آوریم.

مطالعه مطلب
جاواپرومرجع تخصصی یادگیری و توسعهٔ جاوا

تجربه‌ای منظم، ساده و حرفه‌ای برای یادگیری عمیق جاوا، Spring و مهندسی نرم‌افزار؛ قابل استفاده در موبایل، تبلت و دسکتاپ

یادگیری

همه دوره‌هاآموزش رایگانمسیر یادگیریمدرس‌ها

جاواپرو

درباره ماخدمات تخصصیدرخواست پروژهتدریس خصوصیبلاگ فنی

پشتیبانی

تماس با ماپرسش‌های متداولقوانین و حریم خصوصیبروزرسانی‌های جاواپروثبت تیکت
خرید امنپرداخت فقط در درگاه رسمیپشتیبانی آموزشیمسیر ارتباط شفاف و پیگیری‌پذیردسترسی پایداروب‌اپ واکنش‌گرا روی همه دستگاه‌ها
© ۱۴۰۵ جاواپرو؛ همه حقوق محفوظ استتوسعه توسط پایاپینگ
خانهدوره‌هاسبد