جشنواره تخفیف های جمعه سیاه

Generic در جاوا چیست؟ آموزش Generic در Java

جاوا با هدف کمک به برنامه نویسان برای پیاده سازی الگوریتم های مستقل از نوع (Type) و در عین حال اعمال کنترل نوع قوی تر (Stronger Type Check) در زمان ترجمه (Compile) از قابلیتی به نام Generic بهره می گیرد. تلفیق دو ویژگی استقلال کد از نوع و کنترل نوع قوی تر در زمان ترجمه ، افزایش پایداری و بهبود خوانایی کد نوشته شده را برای برنامه نویسان به ارمغان می آورد .

دوره های شبکه، برنامه نویسی، مجازی سازی، امنیت، نفوذ و ... با برترین های ایران

در ساده ترین بیان برای توصیف این ویژگی ارزشمند زبان جاوا می توان گفت Generic قابلیتی است که با استفاده از آن می توان کلاس (Class) ، رابط(Interface) و یا شگردی (Method) را پیاده سازی کرد که با بکارگیری یک الگوریتم یکسان بتواند بیش از یک نوع را پشتیبانی کند.

در حقیقت تا پیش از ورود مفهوم Generic به دنیای جاوا ، برنامه نویسان برای استفاده از انواع گوناگون داده در یک کلاس ، رابط و یا شگرد چاره ای جز : بهره گیری از نوع داده Object ، گرانبارکردن شگردها (Method Overloading) و یا تکرار کد پیاده کننده الگوریتم برای هر نوع مورد نظر نداشتند.

از این رو برنامه نویسان ناگزیر از پذیرش افزایش حجم و کاهش خوانایی کد نوشته شده و نیز دست و پنجه نرم کردن با مشکلات و خطای تبدیل نوع (Type Casting) بودند. برای مثال فرض کنید در حال کد نویسی ساختمان داده ساده ای چون پشته (Stack) هستید که می تواند نوع داده Integer را در پشته ذخیره (Push) و یا از پشته واکشی (Pop) کند. کدی که خواهید نوشت چیزی شبیه کد زیر خواهد بود :

public class IntegerStack {
   private int maxSize;
   private long[] stackArray;
   private int top;
   
   public IntegerStack(int i) {
      maxSize = i;
      stackArray = new long[maxSize];
      top = -1;
   }
   public void push(long j) {
      stackArray[++top] = j;
   }
   public long pop() {
      return stackArray[top--];
   }
   public long peek() {
      return stackArray[top];
   }
   public boolean isEmpty() {
      return (top == -1);
   }
   public boolean isFull() {
      return (top == maxSize - 1);
   }
}

پشته پیاده سازی شده با کد بالا ، تنها می تواند از نوع داده Integer پشتیبانی کند و چنانچه در برنامه نیاز به ذخیره و بازیابی (Push & Pop) نوع داده String در پشته را داشته باشید ناگزیر از کد نویسی دوباره خواهید بود برای مثال نیاز به نوشتن کلاس StringStack است که تفاوت آن با کلاس IntegerStack تنها در نوع داده ای است که می تواند در پشته ذخیره و یا از آن واکشی گردد .

public class StringStack {
   private int maxSize;
   private String[] stackArray;
   private int top;
   
   public StringStack(int s) {
      maxSize = s;
      stackArray = new String[maxSize];
      top = -1;
   }
   public void push(String str) {
      stackArray[++top] = str;
   }
   public String pop() {
      return stackArray[top--];
   }
   public long peek() {
      return stackArray[top];
   }
   public boolean isEmpty() {
      return (top == -1);
   }
   public boolean isFull() {
      return (top == maxSize - 1);
   }
}

شیوه تعریف کلاس عام (Generic Class)

الگوی تعریف یک کلاس عام به شکل زیر است :

public class  name<T1,T2, … Tn>{/*     */}

همانگونه که پیداست تعریف یک کلاس عام همانند یک کلاس عادی انجام می شود با این تفاوت که پس از نام کلاس بخش مربوط به پارامتر نوع (Type Parameter) قرار می گیرد که با نماد کوچکتربزرگتر(<>) مشخص شده است. نماد <> را ((کمانک شکسته)) یا عملگر لوزی (Diamond Operator) نیز می نامند . پارامتر نوع ، درون این کمانک شکسته تعریف شده و چنانچه بیش از یک پارامتر نوع مورد نیاز باشد ، با استفاده از علامت کاما (,) از هم جدا می شوند. برای مثال ، کلاس معمولی Example را در نظر بگیرید این کلاس تنها می تواند با مقادیری از نوع String کارکند و امکان کار با نوع دیگری راندارد.

public class Example{
		private String var;
		public void set(String v){var=v;}
		public String get(){return var;}
	}

حال چنانچه بخواهیم این کلاس را به صورت عام (Generic) بنویسیم باید کد بالا را به صورت زیر بازنویسی کنیم :

 
public class Example<T>{
		private T var;
		public void set(T v){var=v;}
		public T get(){return var;}
	}

همانگونه که می بینید در کلاس عام Example نوع داده String موجود در کلاس معمولی Example با پارامتر نوع T جایگزین شده است در خصوص پارامتر نوع (Type Parameter) که متغیر نوع (Type Variable) نیز نامیده می شودذکر چند نکته ضروریست :

1- روش پذیرفته در نام گذاری پارامترهای نوع ، استفاده از تک حروف بزرگ انگلیسی است ، برای مثال ازحرف E به طور گسترده ای در مجموعه ها ( ( Collections استفاده می گردد به هرحال معمول ترین نامهای مورد استفاده عبارتند از :

  • E سرواژه Element
  • K سرواژه Key
  • N سرواژه Number
  • T سرواژه Type
  • V سرواژه Value

2- تنها انواع داده ارجاعی ( Reference Data Types ) شامل کلاس ، رابط ، آرایه و... می توانند جایگزین پارامتر نوع شوند و انواع داده اولیه (byte،char،short،int،long،float،double،boolean) هرگز نمی توانند جایگزین متغیر نوع شوند.برای آنکه بتوان از نوع داده‌ اولیه (primitive type) در Generic ها استفاده کنید باید از کلاسهای بسته بندی کننده یا پوشش دهنده ( Wrapper Class) متناظر با نوع داده اولیه استفاده کنید. در جدول زیر انواع داده اولیه و کلاس پوششی ( Wrapper) متناظری که نوع داده اولیه را پوشش می دهد آورده شده است. این کلاس‌ها در بسته java.lang قرار دارند بنابراین برای استفاده ، نیازی به import کردن آنها وجود ندارد.

Primitive Type-------Wrapper Class

---------------------------------

boolean------------Boolean

char-------------Character

byte------------------Byte

short----------------Short

int-----------------Integer

long-----------------Long

float-----------------Float

double-------------Double

پارامتر های نوع ، راهی را فراهم می آورند تا بتوان یک کد واحد را با ورودی های متفاوتی بکار گرفت. در مقایسه با پارامترهای عادی می توان گفت که ورودی یک پارامتر معمولی یعنی آنچه که می تواند به هنگام فراخوانی شگرد و یا نمونه سازی از یک کلاس جایگزین پارامتر عادی شود از جنس مقدار (Value) است حال آنکه ورودی برای یک پارامتر نوع تنها می تواند از جنس نوع داده مرجع (Refrence Data Type) باشد. به بیان ساده تر یک پارامتر نوع (برای مثال T در کلاس Example) بر اساس نیاز برنامه نویس تنها می تواند با یک نوع مرجع (برای مثال String ، Double ، کلاس ، رابط ، آرایه و ...) جایگزین گردد. به طور کلی در جاوا چهار نوع داده ارجاعی وجود دارد که عبارتند از :

Class Types

Interface Types

Array Types

Type variable

3- با وجود آنکه بسیاری از برنامه نویسان واژه های پارامتر نوع (Type Parameter) و آرگومان نوع(Type Argument) را یکسان فرض کرده و این دو اصطلاح را به جای یکدیگر بکار می برند اما لازم است بدانیم که هر یک از این دو متفاوت با دیگری است. و بکار گیری این دو مفهوم در معنای یکسان شایسته یک برنامه نویس خبره نیست.

در حقیقت در زمان تعریف یک عنصر Generic برای مثال یک کلاس عام ، متغیری که درون کمانک شکسته (< >) قرار می گیرد پارامتر نوع نامیده می شود اما نوع واقعی (برای مثال String) که در زمان نمونه سازی (Instantiation) و یا فراخوانی عنصر Generic جانشین پارامتر نوع شده و درون کمانک شکسته (< >) خواهد نشست را آرگومان نوع می نامند.

برای نمونه در مثال زیر T پارامتر نوع است و Integer آرگومان نوع :

private class Box<T>{/*   */}

Box<Integer> box=new Box<Integer>();

انواع عام (Generic Type)

کلاس های عام (Generic Class) و رابط های عام (Generic Interface)را در اصطلاح انواع عام (Generic Types) یا انواع پارامتری شده (Parameterized Types) می گویند.فراخوانی (Invoking) و نمونه سازی (Instantiating) از انواع عام :

فراخوانی و نمونه سازی انواع عام همانند کلاسهای عادی است با این تفاوت که نیاز دارید تا پارامتر نوع را با یک آرگومان نوع مناسب جایگزین کنید برای نمونه به مثال زیر اشاره می شود :

public class Box<K,V>{
		private K val1;
		private V val2;
		
		public Box(K t1,V t2){
			this.val1=t1;
			this.val2=t2;
					}
		
	}
	
	Box<String,Integer> box=new Box<String,Integer>("Arash",2253);
	

در Java SE نسخه 7 و پس از آن ، برنامه نویس می تواند آرگومان های نوع را در سازنده کلاس مشخص نسازند و کمانک شکسته (< >) بخش سازنده کلاس را خالی رها کند این قابلیت که سبب کوتاهتر شدن کد می شود را عملگر لوزی(Diamond Operator) می گویند. در این حالت جاوا نوع مورد نیاز برای استفاده در سازنده کلاس را از سمت چپ تساوی بدست می آورد. با استفاده از این ویژگی مثال بالا به شکل زیر بازنویسی می شود :

Box<String,Integer> box=new Box<>("Arash",2253);

شما می توانید یک نوع عام را به عنوان آرگومان نوع یک عنصر Generic استفاده کنید.

نوع خام (Raw Type)

هرگاه به هنگام نمونه سازی از یک کلاس یا رابط عام ، هیچ آرگومان نوعی (Type Argument) برای آن در نظر گرفته نشود نوع ساخته شده را نوع خام می نامند برای مثال کلاس زیر را در نظر بگیرید :

public class Box<T> {
		public void set(T t){/*   */}
	}

نمونه سازی از کلاس عام Box<T> به شکل زیر است :

Box<Integer> box=new Box<>();

همانگونه که دیده می شود برای ساخت یک نوع پارامتری شده از Box<T> آرگومان نوع Integer جایگزین پارامتر نوع T شده است حال اگر همانند مثال زیر برنامه نویس از ارسال آرگومان نوع به Box<T> خودداری کند نوع بدست آمده ، نوع خام کلاس عام Box<T> خواهد بود:

Box rawbox=new Box();

در این حالت گفته می شود که Box نوع خام (Raw Type) متعلق به نوع عام Box<T> است. توجه به این نکته مهم است که کلاس ها یا رابطهای عادی (non-generic) نوع داده خام (Raw Type) نیستند و کاربرد این مفهوم تنها در حوزه انواع عام است. در هنگام استفاده از نوع خام ، به یاد داشته باشید که کد نوشته شده رفتاری پیشا Generic خواهد داشت برای نمونه در مثال بالا Box یک کلاس Object ایجاد خواهد کرد از این رو rawbox در حقیقت شیئی از نوع Object خواهد بود.

از آنجا که در زبان جاوا تا پیش از JDK 5.0 بسیاری از کلاسهای API ( برای نمونه کلاسهای Collection) به صورت غیر Generic پیاده سازی شده بودند بنا براین نوع خام در بسیاری از کدهای قدیمی نوشته شده با این زبان دیده می شود. از این رو با هدف فراهم آوردن قابلیت سازگاری پسرو (Backward Compatibility) و امکان بهره گیری از کلاسهای API پیاده سازی شده با نسخه های پیش از JDK 5.0 اختصاص یک نوع عام به نوع خام خودش مجاز است اما عکس آن مجاز نیست و اختصاص یک نوع خام به نوع عام متناظرش ، تولید هشدار (Warning) خواهد کرد. برای مثال کد زیر به درستی کار می کند :

Box<Integer> box=new Box<>();
Box rawbox=box;

اما کد زیر موجب تولید هشدار (Warning) خواهد شد :

Box rawbox=new Box();
Box<Integer> integerbox=rawbox;  // Warning: unchecked conversion

چنانچه با استفاده از یک نوع خام ، شگردی (Method) از نوع عام متناظرش را فراخوانی کنید باز هم جاوا تولید هشدار خواهد کرد برای مثال در کد زیر نوع خام rawbox متد set از کلاس عام Box<T> را فراخوانی کرده است و در نتیجه جاوا هشدار unchecked conversion را تولید کرده است:

Box<String> stringbox=new Box<>();
Box rawBox=stringbox;
rawBox.set(8);  //warning:unchecked invocation to set<T>

واژه unchecked بیانگر آن است که کامپایلر اطلاعات کافی در خصوص نوع مورد استفاده در کد که برای اطمینان از ایمنی نوع مورد نیاز است را در اختیار ندارد.


نظرات