نحوه استفاده از Generics در جاوا – با مثال کد توضیح داده شده است
![](https://khabarkaav.ir/wp-content/uploads/2024/07/safety.png)
![](https://www.freecodecamp.org/news/content/images/2024/06/safety.png)
در برنامه جاوا خود، ممکن است هنگام کار با انواع مختلفی از اشیاء مانند Integer، String و غیره ClassCastException
مخوف در زمان اجرا مواجه شده باشید. این خطا بیشتر به دلیل ارسال یک شی به نوع داده اشتباه است.
در این مقاله، با ژنریک ها آشنا می شوید و خواهید دید که چگونه می توانند به رفع این مشکل کمک کنند.
چرا به ژنریک نیاز داریم؟
بیایید با یک مثال ساده شروع کنیم. ابتدا انواع مختلفی از اشیاء را به یک ArrayList
اضافه می کنیم. در مرحله بعد، سعی می کنیم آنها را بازیابی کنیم و مقادیر آنها را چاپ کنیم.
List list = new ArrayList(); list.add("Hello"); String str = (String) list.get(0); System.out.println("String: " + str);
همانطور که می بینید، ما یک شی String
را به ArrayList
اضافه کرده ایم. از آنجایی که ما کسانی هستیم که کد را نوشته ایم، می دانیم که عنصر چه نوع شی را نشان می دهد، اما کامپایلر آن را نمی داند. پس ، هنگامی که سعی می کنیم مقدار را از فهرست بازیابی کنیم، یک Object
برمی گردانیم و باید یک Casting صریح انجام دهیم.
list.add(123); String number = (String) list.get(1); System.out.println("Number: " + number);
اگر یک Integer
را به همان فهرست اضافه کنیم و سعی کنیم مقدار را واکشی کنیم، یک ClassCastException
دریافت می کنیم زیرا یک شی عدد صحیح را نمی توان به یک رشته فرستاد.
با استفاده از Generics، میتوانیم هر دو مشکلی را که در بالا توضیح داده شد، حل کنیم. بیایید ببینیم چگونه.
ابتدا باید از عملگر الماس استفاده کنیم و نوع شیء موجود در این فهرست را محدود کنیم. ما باید نوع شی را به صراحت در عملگر الماس ذکر کنیم. این یک تحلیل زمان کامپایل را اعمال می کند، پس دیگر مجبور نیستید ارسال صریح را انجام دهید. همچنین می توانید ClassCastException
حذف کنید.
List<String> list = new ArrayList(); list.add("Hello"); String str = list.get(0); // No need for explicit casting System.out.println("String: " + str); list.add(123); // Throws compile-time error
قراردادهای نامگذاری برای پارامترهای نوع
در مثال قبلی، مشاهده کردید که استفاده از List<String>
نوع شیئی را که فهرست می تواند در خود جای دهد محدود می کند. مثال زیر از یک کلاس Box
و نحوه عملکرد آن با انواع مختلف داده را تحلیل کنید.
public class Box<T> { private T value; public void setValue(T value) { this.value = value; } public T getValue() { return value; } public static void main(String[] args) { Box<String> stringBox = new Box<>(); stringBox.setValue("Hello, world!"); System.out.println(stringBox.getValue()); Box<Integer> integerBox = new Box<>(); integerBox.setValue(123); System.out.println(integerBox.getValue()); } }
به نحوه اعلان کلاس Box<T>
توجه کنید. در اینجا، T
یک پارامتر نوع است، که نشان می دهد کلاس Box
می تواند با هر شی از آن نوع کار کند. همین امر در روش اصلی نشان داده شده است که در آن نمونه ای از Box<String>
و Box<Integer>
هر دو مجاز به ایجاد هستند، پس ایمنی نوع را تضمین می کند.
طبق اسناد رسمی :
طبق قرارداد، نام پارامترهای نوع حروف تک و بزرگ هستند.
متداول ترین نام های پارامتر نوع عبارتند از:
E - عنصر (به طور گسترده توسط Java Collections Framework استفاده می شود)
ک - کلید
ن - شماره
T - نوع
V - ارزش
S، U، V و غیره - انواع 2، 3، 4
بیایید نگاهی به نحوه نوشتن یک روش عمومی بیندازیم. در زیر این کنوانسیون آمده است:
public static <T> void printArray(T[] inputArr) { for (T element : inputArr) { System.out.print(element + " "); } System.out.println(); }
در اینجا، یک آرایه از هر نوع را می گیریم و عناصر آن را چاپ می کنیم. توجه داشته باشید که قبل از نوع برگشتی متد، باید پارامتر نوع عمومی T
در براکت های زاویه <>
مشخص کنید. بدنه متد روی آرایه ای که به عنوان پارامتر ارسال کرده ایم، از هر نوع T
، تکرار می شود و هر عنصر را چاپ می کند.
public static void main(String[] args) { // Create different arrays of type Integer, Double and Character Integer[] intArr = {1, 2, 3, 4, 5}; Double[] doubleArr = {1.1, 2.2, 3.3, 4.4, 5.5}; Character[] charArr = {'H', 'E', 'L', 'L', 'O'}; System.out.println("Integer array contains:"); printArray(intArr); // pass an Integer array System.out.println("Double array contains:"); printArray(doubleArr); // pass a Double array System.out.println("Character array contains:"); printArray(charArr); // pass a Character array }
ما می توانیم این روش عمومی را با عبور از انواع مختلف آرایه ها ( Integer
، Double
، Character
) فراخوانی کنیم و خواهید دید که برنامه شما عناصر هر یک از این آرایه ها را چاپ می کند.
محدودیت های ژنریک
در Generics، ما از کرانها برای محدود کردن انواعی که یک کلاس، رابط یا متد عمومی میتواند بپذیرد، استفاده میکنیم. دو نوع وجود دارد:
1. مرزهای بالایی
این برای محدود کردن نوع عمومی به حد بالایی استفاده می شود. برای تعریف کران بالا، از کلمه کلیدی extends
استفاده می کنید. با تعیین یک کران بالا، مطمئن می شوید که کلاس، رابط یا متد، نوع مشخص شده و همه زیر کلاس های آن را می پذیرد.
نحو به صورت زیر خواهد بود: <T extends SuperClass>
.
اگر همان کلاس Box
را در نظر بگیرید که قبلاً استفاده می کردیم، می توان آن را به صورت زیر تغییر داد:
class Box<T extends Number> { private T value; public void setValue(T value) { this.value = value; } public T getValue() { return value; } }
در این مثال، T می تواند هر نوعی باشد که Number
گسترش دهد، مانند Integer
، Double
یا Float
.
2. مرزهای پایین تر
این برای محدود کردن نوع عمومی به حد پایین تر استفاده می شود. برای تعریف کران پایین، از کلمه کلیدی super
استفاده می کنید. با تعیین یک کران پایین، مطمئن می شوید که کلاس، رابط یا متد، نوع مشخص شده و همه سوپرکلاس های آن را می پذیرد.
نحو به صورت زیر خواهد بود: <T super SubClass>
.
برای نشان دادن استفاده از کران پایین، اجازه دهید به مثال زیر نگاهی بیندازیم:
public static void printList(List<? super Integer> list) { for (Object element : list) { System.out.print(element + " "); } System.out.println(); }
استفاده از کران پایین <? super Integer>
تضمین میکند که میتوانید نوع مشخص شده و همه سوپرکلاسهای آن را که در این مورد فهرستی از Integer
، Number
یا Object
خواهد بود را به متد printList
منتقل کنید.
Wildcards چیست؟
?
که در مثال قبل دیدید Wildcard نامیده می شود. می توانید از آنها برای اشاره به یک نوع ناشناخته استفاده کنید.
می توانید از یک علامت عام با کران بالایی استفاده کنید، در این صورت چیزی شبیه به این خواهد بود: <? extends Number>
. همچنین می توان آن را با کران پایین تر مانند <? super Integer>
.
پاک کردن را تایپ کنید
نوع عمومی که در کلاس، رابط یا متد خود استفاده می کنیم، فقط در زمان کامپایل در دسترس است و در زمان اجرا حذف می شود. این کار برای اطمینان از سازگاری به عقب انجام می شود، زیرا نسخه های قدیمی جاوا (قبل از جاوا 1.5) از آن پشتیبانی نمی کنند.
کامپایلر از اطلاعات نوع عمومی که در دسترس است برای اطمینان از ایمنی نوع استفاده می کند. در فرآیند پاک کردن نوع:
اگر نوع نامحدود باشد، پارامترها با کران یا نوع Object
خود جایگزین می شوند
اگر نوع محدود باشد، پارامترها با کران اول جایگزین می شوند و اطلاعات نوع عمومی پس از کامپایل حذف می شوند.
اگر به مثال کلاس Box
نگاهی بیندازیم:
public class Box<T> { private T value; //getters and setters }
public class Box { private Object value; //getters and setters }
نتیجه
در این مقاله، مفهوم ژنریک در جاوا و نحوه استفاده از آنها را با چند مثال اساسی تحلیل کردیم. درک و استفاده از ژنریک ایمنی نوع در برنامه شما را افزایش می دهد. آنها همچنین نیاز به ارسال صریح را از بین می برند و کد شما را قابل استفاده مجدد و قابل نگهداری می کنند.
بیایید در لینکدین وصل شویم.
ارسال نظر