جنریک در جاوا چیست ؟ شرح مفهوم Java Generics با مثال

جنریک در جاوا یا همان ژنریک در جاوا (Generics) به معنی انواع داده پارامتربندی یا پارامترسازی شده (Parameterize Type) است. ایده این مفهوم و موجودیت در زبان جاوا این است که به انواع داده‌ای مثل String‌ ،Integer و سایر موارد و همچنین انواع تعریف شده توسط کاربر اجازه بدهد که برای متُدها، کلاس‌ها و واسط‌ها (اینترفیس‌ها) در نقش پارامتر ظاهر شوند. با استفاده از جنریک‌ها امکان ایجاد کلاس‌هایی فراهم می‌شود که با نوع داده‌های مختلف کار می‌کنند. موجودیتی مثل یک کلاس، واسط یا متدی که روی یک نوع داده پارامترسازی شده عملیات انجام می‌دهد، یک موجودیت جنریک به حساب می‌آید. Generic در لغت به معنی «عمومی» است.

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

کلاس Object در جاوا، ابَرکلاس (Superclass) تمام دیگر کلاس‌ها در این زبان برنامه نویسی به حساب می‌آید و ارجاع به Object می‌تواند به هر شیئی اشاره داشته باشد. این قابلیت‌ها در جاوا به کمبود امنیت دچار هستند. جنریک‌ها در جاوا به نوعی قابلیت امنیت را به اشیاء اضافه می‌کنند. این نوع ویژگی امنیتی در ادامه این مقاله و مثال‌های ارائه شده بیش‌تر مورد بحث قرار خواهد گرفت.

جنریک‌ها در جاوا مشابه «قالب‌ها» (Template) در C++‎ هستند. برای مثال، کلاس‌هایی مثل HashSet ،ArrayList ،HashMap و سایر موارد از جنریک‌ها به خوبی استفاده می‌کنند. میان دو رویکرد در انواع جنریک، تفاوت‌هایی اساسی وجود دارد. در ادامه به انواع Generic در جاوا پرداخته شده است.

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

انواع جنریک در جاوا

انواع ژنریک یا همان جنریک در جاوا را می‌توان به دو نوع مختلف دسته‌بندی کرد:

  • متد‌های جنریک (Generic Method)
  • کلاس‌های جنریک (Generic Classes)

در ادامه هر یک از این دو نوع جنریک در جاوا معرفی شده‌اند.

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

متد جنریک در جاوا پارامتری را دریافت می‌کند و مقداری را پس از اجرای یک وظیفه بازمی‌گرداند. متد جنریک در جاوا دقیقاً مشابه یک تابع معمولی است، البته، یک متد جنریک پارامترهای نوعی دارد که به وسیله نوع داده واقعی به آن‌ها اشاره شده است. این باعث می‌شود که بتوان از متد جنریک به صورتی عمومی‌تر استفاده کرد. کامپایلر ایمنی نوع را بر عهده می‌گیرد که این باعث می‌شود برنامه نویسان بتوانند به راحتی کدنویسی کنند، زیرا مجبور نخواهند بود تبدیل نوع‌های (Type Casting) طولانی را یک به یک خودشان انجام دهند.

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

یک کلاس جنریک در جاوا دقیقاً مثل یک کلاس غیر‌عمومی یا غیرجنریک (Non-Generic) پیاده‌سازی می‌شود. تنها تفاوت این است که یک کلاس جنریک در جاوا دارای بخش پارامتر نوع است. امکان وجود بیش از یک نوع از پارامترها به صورت جدا شده به وسیله کاما (,) وجود دارد. به کلاس‌هایی که یک یا بیش از یک پارامتر را می‌پذیرند، «کلاس‌های پارامترسازی شده» (Parameterized Class) یا «انواع پارامترسازی شده» (Parameterized Types) گفته می‌شود.

کلاس جنریک در جاوا

مشابه C++‎، برای نشان دادن انواع داده پارامترسازی شده در هنگام ایجاد کلاس جنریک از <> استفاده می‌شود. برای ایجاد اشیایی از یک کلاس ژنریک، از سینتکس (قاعده نحوی) زیر استفاده می‌شود:

// To create an instance of generic class 
BaseType <Type> obj = new BaseType <Type>()

باید این نکته را در نظر داشت که در نوع پارامتر نمی‌توان از عناصر اولیه‌ای (Primitive) مثل int ،char یا double استفاده کرد. اکنون در ادامه، برنامه جاوا برای نمایش نحوه عملکرد کلاس‌های جنریک تعریف شده توسط کاربر ارائه شده است:

// Java program to show working of user defined
// Generic classes
  
// We use < > to specify Parameter type
class Test<T> {
    // An object of type T is declared
    T obj;
    Test(T obj) { this.obj = obj; } // constructor
    public T getObject() { return this.obj; }
}
  
// Driver class to test above
class Main {
    public static void main(String[] args)
    {
        // instance of Integer type
        Test<Integer> iObj = new Test<Integer>(15);
        System.out.println(iObj.getObject());
  
        // instance of String type
        Test<String> sObj
            = new Test<String>("GeeksForGeeks");
        System.out.println(sObj.getObject());
    }
}

خروجی کدهای فوق به صورت زیر خواهد بود:

۱۵
GeeksForGeeks

علاوه بر این، می‌توان چندین پارامتر نوع را هم در کلاس‌های جنریک ارجاع داد. در ادامه مثالی در این خصوص به زبان جاوا ارائه شده است:

// Java program to show multiple
// type parameters in Java Generics

// We use < > to specify Parameter type
class Test<T, U>
{
	T obj1; // An object of type T
	U obj2; // An object of type U

	// constructor
	Test(T obj1, U obj2)
	{
		this.obj1 = obj1;
		this.obj2 = obj2;
	}

	// To print objects of T and U
	public void print()
	{
		System.out.println(obj1);
		System.out.println(obj2);
	}
}

// Driver class to test above
class Main
{
	public static void main (String[] args)
	{
		Test <String, Integer> obj =
			new Test<String, Integer>("GfG", 15);

		obj.print();
	}
}

خروجی کدهای فوق در ادامه آمده است:

GfG
۱۵

توابع جنریک در جاوا

علاوه بر این در جاوا می‌توان توابع جنریک هم نوشت و ایجاد کرد. توابع جنریک در جاوا را می‌توان با آرگومان‌هایی با انواع داده متفاوت بر اساس نوع آرگومان‌های ارجاع شده به متُد جنریک فراخوانی کرد. در این حالت، کامپایلر هر متد را مدیریت می‌کند. در ادامه مثالی به زبان جاوا برای نمایش نحوه عملکرد توابع جنریک تعریف شده توسط کاربر ارائه شده است:

// Java program to show working of user defined
// Generic functions

class Test {
	// A Generic method example
	static <T> void genericDisplay(T element)
	{
		System.out.println(element.getClass().getName()
						+ " = " + element);
	}

	// Driver method
	public static void main(String[] args)
	{
		// Calling generic method with Integer argument
		genericDisplay(11);

		// Calling generic method with String argument
		genericDisplay("GeeksForGeeks");

		// Calling generic method with double argument
		genericDisplay(1.0);
	}
}

خروجی کدهای فوق به صورت زیر است:

java.lang.Integer = 11
java.lang.String = GeeksForGeeks
java.lang.Double = 1.0

آیا جنریک ها در جاوا فقط با انواع داده مرجع کار می‌کنند؟

بله، وقتی نمونه‌ای از یک نوع جنریک اعلان می‌شود، آرگومان نوعی که به پارامتر نوع ارجاع داده می‌شود، باید یک نوع مرجع (Reference Type) باشد. نمی‌توان از نوع‌های داده مبنا مثل int یا char استفاده کرد.

Test<int> obj = new Test<int>(20);

خط کد فوق منجر به یک خطای زمان کامپایل می‌شود که با استفاده از پوشش دهنده‌های نوع برای بسته‌بندی (کپسوله کردن) یک نوع داده مبنا قابل رفع است. اما آرایه‌های نوع مبنا را می‌توان به پارامتر نوع ارجاع داد، زیرا آرایه‌ها از نوع مرجع هستند.

ArrayList<int[]> a = new ArrayList<>();

آیا نوع های جنریک بر اساس آرگومان های نوع‌شان متفاوت هستند؟

بله، باید کدهای جاوای زیر را در نظر گرفت:

// Java program to show working
// of user-defined Generic classes

// We use < > to specify Parameter type
class Test<T> {
	// An object of type T is declared
	T obj;
	Test(T obj) { this.obj = obj; } // constructor
	public T getObject() { return this.obj; }
}

// Driver class to test above
class Main {
	public static void main(String[] args)
	{
		// instance of Integer type
		Test<Integer> iObj = new Test<Integer>(15);
		System.out.println(iObj.getObject());

		// instance of String type
		Test<String> sObj
			= new Test<String>("GeeksForGeeks");
		System.out.println(sObj.getObject());
		iObj = sObj; // This results an error
	}
}

خروجی کدهای فوق در ادامه آمده است:

error:
 incompatible types:
 Test cannot be converted to Test

با وجود اینکه iObj و sObj از نوع Test هستند، آن‌ها مراجعی به انواع داده مختلف به حساب می‌آیند، زیرا پارامترهای نوع در آن‌ها متفاوت است. جنریک‌ها از این طریق ایمنی نوع را فراهم و از بروز خطا جلوگیری می‌کنند.

پارامترهای نوع در جنریک های جاوا

قراردادهای نام‌گذاری پارامترهای نوع برای یادگیری جنریک‌ها به‌طور کامل دارای اهمیت هستند. پارامترهای نوع رایج در ادامه فهرست شده‌اند:

  • T – نوع (Type)
  • E – عنصر (Element)
  • K – کلید (Key)
  • N – عدد (Number)
  • V – مقدار (Value)

مزایای جنریک در جاوا 

برنامه‌هایی که از جنریک‌ها استفاده می‌کنند مزایای بسیاری نسبت به کدهای غیرعمومی (غیرجنریک | Non-Generic) دارند. در ادامه برخی از این مزایا فهرست شده‌اند:

  1. قابلیت استفاده مجدد از کدها
  2. ایمنی نوع
  3. عدم نیاز به تبدیل نوع تک به تک (Individual Type Casting)
  4. امکان پیاده‌سازی الگوریتم‌های جنریک

در ادامه توضیحات بیش‌تری در خصوص هر یک از مزایای فهرست شده ارائه شده است.

۱. قابلیت استفاده مجدد از کدها

می‌توان یک متد، کلاس یا واسط را یک بار نوشت و آن را برای هر نوع مورد نظر استفاده کرد. با کمک جنریک‌ها در جاوا می‌توان کدهایی نوشت که با انواع متفاوتی از داده‌ها کار می‌کنند. برای مثال:

public <T> void genericsMethod (T data) {...}

در خط کد بالا یک متُد جنریک ساخته شده است. از همین متُد می‌توان برای اجرای عملیاتی روی داده‌های صحیح، رشته‌ای و سایر موارد استفاده کرد.

۲. ایمنی نوع

جنریک‌ها باعث می‌شوند خطاها به جای زمان اجرا، در زمان کامپایل مشخص شوند. باید به این نکته توجه داشت که همیشه بهتر است به جای نقص داشتن کدها در زمان اجرا، مشکلات برنامه در زمان کامپایل مشخص شوند. به فرض قصد ایجاد یک ArrayList از اسامی دانشجویان وجود دارد. اگر برنامه نویس به جای یک رشته، یک شی از نوع صحیح را اضافه کند، کامپایلر اجازه این کار را خواهد داد. اما وقتی این داده‌ها از ArrayList بازیابی می‌شوند، مشکلاتی در زمان اجرا به وجود خواهد آمد.

// Java program to demonstrate that NOT using
// generics can cause run time exceptions

import java.util.*;

class Test
{
	public static void main(String[] args)
	{
		// Creatinga an ArrayList without any type specified
		ArrayList al = new ArrayList();

		al.add("Sachin");
		al.add("Rahul");
		al.add(10); // Compiler allows this

		String s1 = (String)al.get(0);
		String s2 = (String)al.get(1);

		// Causes Runtime Exception
		String s3 = (String)al.get(2);
	}
}

خروجی کدهای فوق به صورت زیر است:

Exception in thread "main" java.lang.ClassCastException: 
   java.lang.Integer cannot be cast to java.lang.String
    at Test.main(Test.java:19)

حالا جنریک‌ها چگونه این مشکل را برطرف می‌کنند؟ در پاسخ باید گفت وقتی ArrayList تعریف می‌شود، می‌توان مشخص کرد که این لیست تنها می‌تواند اشیاء رشته‌ای را دریافت کند:

// Using Java Generics converts run time exceptions into
// compile time exception.
import java.util.*;

class Test
{
	public static void main(String[] args)
	{
		// Creating a an ArrayList with String specified
		ArrayList <String> al = new ArrayList<String> ();

		al.add("Sachin");
		al.add("Rahul");

		// Now Compiler doesn't allow this
		al.add(10);

		String s1 = (String)al.get(0);
		String s2 = (String)al.get(1);
		String s3 = (String)al.get(2);
	}
}

خروجی:

۱۵: error: no suitable method found for add(int)
        al.add(10); 
          ^

۳. عدم نیاز به تبدیل نوع تک به تک

در صورتی که از جنریک‌ها استفاده نشود، آنگاه در مثال فوق، هر بار که داده‌هایی از ArrayList دریافت می‌شوند، باید حتماً تبدیل نوع موقت (Type Casting) را انجام داد.انجام این تبدیل نوع موقت در هر عملیات بازیابی دردسری بزرگ به حساب می‌آید. در صورتی که پیش از این، اطلاع از این مسئله وجود داشته باشد که لیست مربوطه تنها داده‌های رشته‌ای را نگهداری می‌کند، هر بار نیاز به تبدیل نوع موقت وجود نخواهد داشت.

// We don't need to typecast individual members of ArrayList

import java.util.*;

class Test {
	public static void main(String[] args)
	{
		// Creating a an ArrayList with String specified
		ArrayList<String> al = new ArrayList<String>();

		al.add("Sachin");
		al.add("Rahul");

		// Typecasting is not needed
		String s1 = al.get(0);
		String s2 = al.get(1);
	}
}

۴. امکان پیاده سازی الگوریتم های جنریک

با استفاده از جنریک‌ها، می‌توان الگوریتم‌هایی را پیاده‌سازی کرد که روی انواع مختلف اشیا کار می‌کنند و در همان حال ایمنی نوع هم دارند.

جمع‌بندی

در این مقاله به شرح چیستی جنریک در جاوا پرداخته و نحوه عملکرد و استفاده از آن توضیح داده شد. همچنین پیرامون سایر مباحث مربوط به Generic در جاوا، نظیر کلاس جنریک، توابع جنریک، انواع جنریک در جاوا، مزایای استفاده از جنریک‌ها و سایر موارد بحث شد. امید است این مقاله مفید واقع شود.

اگر این مطلب مفید بوده است، استفاده از دوره‌های آموزشی و مطالب زیر نیز پیشنهاد می‌شوند:

 

منبع [+]

۲ نظر در "جنریک در جاوا چیست ؟ شرح مفهوم Java Generics با مثال"

پاسخ دادن به کپل لغو پاسخ

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *