JavaRush /وبلاگ جاوا /Random-FA /ژنریک در جاوا چیست؟

ژنریک در جاوا چیست؟

در گروه منتشر شد
سلام! امروز در مورد ژنریک صحبت خواهیم کرد. باید بگویم که چیزهای جدید زیادی یاد خواهید گرفت! نه تنها این، بلکه چند سخنرانی بعدی نیز به ژنریک اختصاص داده خواهد شد. ژنریک ها در جاوا چیست - 1 بنابراین، اگر این موضوع برای شما جالب است، خوش شانس هستید: امروز چیزهای زیادی در مورد ویژگی های ژنریک ها خواهید آموخت. خوب، اگر نه، آرام باشید و استراحت کنید! :) این موضوع بسیار مهمی است و باید آن را بدانید. بیایید با یک مورد ساده شروع کنیم: "چی" و "چرا". ژنریک چیست؟ ژنریک ها انواعی با یک پارامتر هستند. هنگام ایجاد یک ژنریک، نه تنها نوع آن، بلکه نوع داده ای که باید با آن کار کند را نیز مشخص می کنید. من فکر می کنم واضح ترین مثال قبلاً به ذهن شما رسیده است - این ArrayList است! در اینجا نحوه ایجاد آن در برنامه به شرح زیر است:
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<String> myList1 = new ArrayList<>();
       myList1.add("Test String 1");
       myList1.add("Test String 2");
   }
}
همانطور که ممکن است حدس بزنید ، ویژگی لیست این است که نمی توان همه چیز را در آن "پر کردن" کرد: منحصراً با اشیاء کار می کند String. حالا بیایید یک گشت و گذار کوتاه در تاریخ جاوا داشته باشیم و سعی کنیم به این سوال پاسخ دهیم: "چرا؟" برای انجام این کار، ما خودمان یک نسخه ساده شده از کلاس ArrayList خواهیم نوشت. لیست ما فقط می تواند داده ها را به آرایه داخلی اضافه کند و این داده ها را دریافت کند:
public class MyListClass {

   private Object[] data;
   private int count;

   public MyListClass() {
       this.data = new Object[10];
       this.count = 0;
   }

   public void add(Object o) {
       this.data[count] = o;
       count++;
   }

   public Object[] getData() {
       return data;
   }
}
فرض کنید می خواهیم لیست ما فقط اعداد را ذخیره کند Integer. ما ژنریک نداریم ما نمی توانیم به صراحت نمونه o از چک را Integerدر add(). سپس کل کلاس ما فقط برای مناسب خواهد بود Integer، و ما باید همان کلاس را برای تمام انواع داده های موجود در جهان بنویسیم! ما تصمیم می گیریم به برنامه نویسان خود تکیه کنیم و به سادگی یک نظر در کد بگذاریم تا آنها چیز غیر ضروری را در آنجا اضافه نکنند:
//use it ONLY with Integer data type
public void add(Object o) {
   this.data[count] = o;
   count++;
}
یکی از برنامه نویسان این نظر را از دست داده و به طور ناخواسته سعی می کند اعداد ترکیب شده با رشته ها را در لیست قرار دهد و سپس مجموع آنها را محاسبه کند:
public class Main {

   public static void main(String[] args) {

       MyListClass list = new MyListClass();
       list.add(100);
       list.add(200);
       list.add("Lolkek");
       list.add("Shalala");

       Integer sum1 = (Integer) list.getData()[0] + (Integer) list.getData()[1];
       System.out.println(sum1);

       Integer sum2 = (Integer) list.getData()[2] + (Integer) list.getData()[3];
       System.out.println(sum2);
   }
}
خروجی کنسول: 300 استثنا در thread "main" java.lang.ClassCastException: java.lang.String را نمی توان به java.lang.Integer در Main.main فرستاد (Main.java:14) بدترین حالت در این وضعیت چیست؟ به دور از بی توجهی یک برنامه نویس. بدترین چیز این است که کد اشتباه در یک مکان مهم در برنامه ما قرار گرفت و با موفقیت کامپایل شد . اکنون خطا را نه در مرحله کدنویسی، بلکه فقط در مرحله تست خواهیم دید (و این در بهترین حالت است!). رفع اشکالات در مراحل بعدی توسعه هزینه بسیار بیشتری دارد - هم پول و هم زمان. این دقیقاً مزیت ژنریک است: یک کلاس عمومی به برنامه نویس بدشانس اجازه می دهد تا فوراً یک خطا را تشخیص دهد. کد به سادگی کامپایل نمی شود!
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Integer> myList1 = new ArrayList<>();

       myList1.add(100);
       myList1.add(100);
       myList1.add("Lolkek");//error!
       myList1.add("Shalala");//error!
   }
}
برنامه نویس بلافاصله "به خود می آید" و فورا خود را اصلاح می کند. ضمناً لازم نبود کلاس خودمان را Listبرای دیدن این نوع خطا ایجاد کنیم. به سادگی براکت های نوع ( <Integer>) را از یک ArrayList معمولی حذف کنید!
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

      List list = new ArrayList();

      list.add(100);
      list.add(200);
      list.add("Lolkek");
      list.add("Shalala");

       System.out.println((Integer) list.get(0) + (Integer) list.get(1));
       System.out.println((Integer) list.get(2) + (Integer) list.get(3));
   }
}
خروجی کنسول: 300 استثنا در thread "main" java.lang.ClassCastException: java.lang.String را نمی توان به java.lang.Integer در Main.main فرستاد (Main.java:16) یعنی حتی با استفاده از ابزارهای "Native" جاوا، شما می توانید این اشتباه را انجام دهید و یک مجموعه ناامن ایجاد کنید. با این حال، اگر این کد را در IDEa بچسبانیم، یک اخطار می بینیم: " تماس بدون علامت برای افزودن(E) به عنوان عضوی از نوع خام java.util.List " این به ما می گوید که ممکن است هنگام افزودن یک عنصر به یک عنصر مشکلی پیش بیاید. مجموعه بدون ژنریک به این صورت نیست. اما عبارت «نوع خام» به چه معناست؟ ترجمه تحت اللفظی کاملاً دقیق خواهد بود - " نوع خام " یا " نوع کثیف ". Raw typeیک کلاس عمومی است که نوع آن حذف شده است. به عبارت دیگر، List myList1این است Raw type. نقطه مقابل یک کلاس عمومی (همچنین به عنوان یک کلاس شناخته می شود ) raw typeاست که به درستی ایجاد شده است، با مشخصات نوع. مثلا، . ممکن است این سوال برای شما پیش بیاید: چرا حتی استفاده از آن مجاز است ؟ دلیلش هم ساده است. سازندگان جاوا پشتیبانی را در زبان گذاشتند تا مشکل سازگاری ایجاد نشود. زمانی که جاوا 5.0 منتشر شد (عمومی برای اولین بار در این نسخه ظاهر شد)، بسیاری از کدها قبلاً با استفاده از . بنابراین، این امکان هنوز هم وجود دارد. ما قبلاً بیش از یک بار در سخنرانی ها به کتاب کلاسیک جاشوا بلوخ "جاوای موثر" اشاره کرده ایم. وی به عنوان یکی از پدیدآورندگان زبان، موضوع استفاده و در کتاب را نادیده نگرفت . فصل 23 این کتاب یک عنوان بسیار شیوا دارد: «از انواع خام در کدهای جدید استفاده نکنید.» این چیزی است که باید به خاطر بسپارید. هنگام استفاده از کلاس های عمومی، هرگز آنها را به . generic typeparameterized typeList<String> myList1raw typesraw typesraw typesraw typesgeneric typesژنریک ها در جاوا چیست - 2generic typeraw type

روش های تایپ شده

جاوا به شما اجازه می دهد تا روش های فردی را تایپ کنید و به اصطلاح روش های عمومی ایجاد کنید. چرا چنین روش هایی راحت هستند؟ اول از همه، زیرا آنها به شما اجازه می دهند با انواع مختلفی از پارامترها کار کنید. اگر منطق یکسانی را بتوان با خیال راحت برای انواع مختلف اعمال کرد، یک روش عمومی یک راه حل عالی است. بیایید به یک مثال نگاه کنیم. بیایید بگوییم که ما نوعی لیست داریم myList1. ما می خواهیم همه مقادیر را از آن حذف کنیم و تمام فضاهای خالی را با یک مقدار جدید پر کنیم. کلاس ما با یک متد عمومی به این صورت خواهد بود:
public class TestClass {

   public static <T> void fill(List<T> list, T val) {
       for (int i = 0; i < list.size(); i++)
           list.set(i, val);
   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       strings.add("Старая строка 1");
       strings.add("Старая строка 2");
       strings.add("Старая строка 3");

       fill(strings, "Новая строка");

       System.out.println(strings);

       List<Integer> numbers = new ArrayList<>();
       numbers.add(1);
       numbers.add(2);
       numbers.add(3);

       fill(numbers, 888);
       System.out.println(numbers);
   }
}
به نحو توجه کنید، کمی غیر معمول به نظر می رسد:
public static <T> void fill(List<T> list, T val)
قبل از نوع بازگشتی <T> آمده است که نشان دهنده یک روش عمومی است. در این حالت، متد 2 پارامتر را به عنوان ورودی می گیرد: یک لیست از اشیاء T و یک شی جداگانه دیگر T. با استفاده از <T>، تایپ متد حاصل می شود: ما نمی توانیم لیستی از رشته ها و یک عدد را در آنجا ارسال کنیم. یک لیست از رشته ها و یک رشته، یک لیست از اعداد و یک عدد، یک لیست از اشیاء ما Catو یک شی دیگر Cat- این تنها راه است. این روش main()به وضوح نشان می دهد که روش fill()به راحتی با انواع مختلف داده کار می کند. ابتدا لیستی از رشته ها و یک رشته و سپس لیستی از اعداد و یک عدد را به عنوان ورودی می گیرد. خروجی کنسول: [Newline, Newline, Newline] [888, 888, 888] تصور کنید اگر fill()برای 30 کلاس مختلف به منطق متد نیاز داشتیم و متدهای عمومی نداشتیم. ما مجبور می شویم یک روش را 30 بار بنویسیم، فقط برای انواع داده های مختلف! اما به لطف روش‌های عمومی، می‌توانیم از کد خود دوباره استفاده کنیم! :)

کلاس های تایپ شده

شما نه تنها می توانید از کلاس های عمومی ارائه شده در جاوا استفاده کنید، بلکه می توانید کلاس های خود را نیز بسازید! در اینجا یک مثال ساده آورده شده است:
public class Box<T> {

   private T t;

   public void set(T t) {
       this.t = t;
   }

   public T get() {
       return t;
   }

   public static void main(String[] args) {

       Box<String> stringBox = new Box<>();

       stringBox.set("Старая строка");
       System.out.println(stringBox.get());
       stringBox.set("Новая строка");

       System.out.println(stringBox.get());

       stringBox.set(12345);//ошибка компиляции!
   }
}
کلاس ما Box<T>("جعبه") تایپ شده است. با اختصاص یک نوع داده ( ) به آن در حین ایجاد <T>، دیگر نمی توانیم اشیایی از انواع دیگر را در آن قرار دهیم. این را می توان در مثال مشاهده کرد. هنگام ایجاد، ما مشخص کردیم که شی ما با رشته ها کار کند:
Box<String> stringBox = new Box<>();
و وقتی در خط آخر کد سعی می کنیم عدد 12345 را داخل کادر قرار دهیم، با خطای کامپایل مواجه می شویم! درست مثل آن، ما کلاس عمومی خود را ایجاد کردیم! :) این سخنرانی امروز ما را به پایان می رساند. اما ما با ژنریک ها خداحافظی نمی کنیم! در سخنرانی های بعدی در مورد ویژگی های پیشرفته تر صحبت خواهیم کرد، پس خداحافظی نکنید! ) امیدوارم در مطالعه موفق باشی! :)
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION