enum توی جاوا دقیقاً چیه؟!

  • 25/مهر/1394
  • سیّد

شاید با enum توی زبان ++C برخورد کرده باشید. enum توی ++C یعنی یه عدد که به جای این که با اعداد نمایشش بدیم (1, 2, 3)، با اسامی نمایشش می‌دیم (A, B, C). به همین سادگی، به همین خوشمزگی! :)
اما توی جاوا قضیه خیلی فراتر از این حرفهاست. enum توی جاوا یه موجود خاصیه! برای این که دقیقاً بفهمیم enum توی جاوا یعنی چی، با یه مثال شروع می‌کنیم.

فرض کنید می‌خواین سیستم جامع مدارس رو پیاده‌سازی کنید. یکی از مفاهیمی که برای دانش‌آموزان وجود داره، «پایه‌ی تحصیلی» هست. فرض کنید مقدار این فیلد، یا پیش‌دبستانی هست، یا دبستان، یا راهنمایی، یا دبیرستان، یا پیش‌دانشگاهی. (ما هنوز توی همون دوران زندگی می‌کنیم! می‌دونم الان اینا عوض شده! ;) )
برای این مفهوم می‌تونیم یه کلاس تعریف کنیم، و توی سازنده نام پایه رو به عنوان ورودی بگیریم:

public class Grade {
	private final String name;

	public Grade(String name) {
		this.name = name;
	}
...
}

خوب اینجا یه مشکلی وجود داره: یه نفر می‌تونه پایه‌ی تحصیلی «گلابی» رو هم بسازه!


Grade myGrade = new Grade("golabi");

چی کار کنیم که نتونه این کار رو بکنه؟ می‌تونیم توی سازنده چک کنیم که کاربر فقط یکی از مقادیر مشخص ما رو بتونه بده:

public class Grade {
	private final String name;

	public Grade(String name) {
		if ("preschool".equals(name) || "school".equals(name) || ...)
			this.name = name;
		else
			throw new IllegalArgumentException("Invalid name: " + name);
	}
...
}

همینجا چند تا اشکال رو ملاحظه می‌کنید: زشت شدن کد به خاطر if های پی در پی (البته در نسخه‌های جدید جاوا می‌تونید روی String هم switch-case استفاده کنید، که باعث می‌شه کد یه مقدار قشنگ‌تر بشه)، مشکل case-sensitive بودن نام پایه‌ها، و پرتاب شدن یک Exception از سازنده در صورت دادن ورودی نامناسب.

برای حل مشکلات بالا، می‌تونیم یه ایده بزنیم. می‌تونیم تعداد اشیاء ساخته شده از این کلاس رو محدود کنیم به اونهایی که خودمون می‌خوایم. چطوری؟ به این صورت که سازنده‌ی کلاس رو private تعریف می‌کنیم که کسی نتونه از این کلاس new کنه، و بعد اشیاء مورد نظرمون رو به صورت static داخل کلاس تعریف می‌کنیم:

public class Grade {
	public static final Grade PRESCHOOL = new Grade("preschool");
	public static final Grade SCHOOL = new Grade("school");
	public static final Grade GUIDANCE = new Grade("guidance");
	public static final Grade HIGHSCHOOL = new Grade("highschool");
	public static final Grade PREUNIVERSITY = new Grade("preuniversity");
	
	private final String name;

	private Grade(String name) {
		this.name = name;
	}
	...
}

حالا هر کس بخواد می‌تونه از یکی از این اشیاء از پیش ساخته شده استفاده کنه:


Grade myGrade = Grade.PRESCHOOL;

حالا دوستان گلمون(!) توی جاوا اومدن و گفتن اسم این نوع داده رو می‌ذاریم enum:

public enum Grade {
	PRESCHOOL,
	SCHOOL,
	GUIDANCE,
	HIGHSCHOOL,
	PREUNIVERSITY;
}

(به علامت comma به جای semi-colon در انتهای هر شیء تعریف شده دقت کنید.)
حالا اگه می‌خواین مثل حالت قبل یه سازنده داشته باشید و برای هر کدوم از مقادیر enum مقدار خاصی به سازنده بدید، می‌تونید به این صورت این کار رو انجام بدید:

public enum Grade {
	PRESCHOOL("preschool"),
	SCHOOL("school"),
	GUIDANCE("guidance"),
	HIGHSCHOOL("highschool"),
	PREUNIVERSITY("preuniversity");
	
	private final String name;

	private Grade(String name) {
		this.name = name;
	}
	...
}

پس تعریف دقیق enum توی جاوا اینه:
enum کلاسیه که اشیاء ساخته‌شده ازش محدود هستند.

فقط یه سری محدودیت در مورد enum وجود داره، از جمله این که enum ها نمی‌تونن از همدیگه ارث‌بری کنن. البته یه enum می‌تونه یه واسط (interface) رو پیاده‌سازی کنه (به کمک کلمه‌ی کلیدی implements). یا این که موقع تعریف enum مجبورید از syntax خاص اون پیروی کنید (این که بلافاصله بعد از آکولاد باز جلوی اسم enum، باید شروع کنید به فهرست کردن instance های اون و با comma اونا رو از هم جدا کنید، و آخری رو هم جلوش semi-colon بذارید، و بعدش می‌تونید شروع کنید به تعریف سازنده و توابع مختلف و فیلدها).

یه روش دیگه‌ی تعریف مقادیر خاص برای instance های یک enum (یعنی مثل کاری که ما توی مثالمون برای متغیر name کردیم، که هر instance از enum مون یه مقدار خاص داد به سازنده)، اینه که تابع abstract توی enum تعریف کنید، و توی هر کدوم از instance ها اون رو پیاده‌سازی کنید:

public enum Grade {
	PRESCHOOL {
		@Override
		public String getName() {
			return "preschool";
		}
	},
	SCHOOL {
		@Override
		public String getName() {
			return "school";
		}
	},
	GUIDANCE {
		@Override
		public String getName() {
			return "guidance";
		}
	},
	HIGHSCHOOL {
		@Override
		public String getName() {
			return "highschool";
		}
	},
	PREUNIVERSITY {
		@Override
		public String getName() {
			return "preuniversity";
		}
	};
	
	public abstract String getName();
	...
}

این کار باعث می‌شه که به تعداد instance های enum شما، anonymous inner class ساخته بشه. دقیقاً انگار دارید شیئی رو از یه کلاس abstract می‌سازید و همونجا توابع abstract اش رو پیاده‌سازی می‌کنید. این موضوع رو می‌تونید با بررسی مقدار برگردانده شده از تابع getClass یکی از مقادیر enum بررسی کنید (علامت $ که توی اسم کلاس هست یعنی یه inner class هست، و اون شماره‌ی بعدش هم اسم کلاسش هست که یعنی anonymous هست).

اما مزایای استفاده از enum چیه؟
یکی این که مقادیر رو محدود کردید.
دوم این که برای این که یه جا بررسی کنید آیا یک مقدار خاص از این enum ها به دست شما رسیده یا نه، لازم نیست از equals استفاده کنید و به جاش می‌تونید از == استفاده کنید (چون تعداد instance ها محدوده و کسی نمی‌تونه شیء جدیدی از این شبه کلاس شما بسازه)، و مشخصه که سرعت اجرای == به مراتب بیشتر از equals هست.
نکته‌ی بعدی اینه که شما می‌تونید با استفاده از enum ها به خوبی منطق مربوط به یک بخش از کد رو توی یه enum جمع‌آوری کنید. مثلاً فرض کنید توی مثال خودمون، می‌خوایم یه تابع بنویسیم که یه دانش‌آموز رو توی یه مقطع ثبت نام کنه، و توش می‌خوایم سن دانش‌آموز رو چک کنیم و ببینیم که به مقطع مورد نظر می‌خوره یا نه. یه راهش اینه که یه switch-case روی enum مورد نظر بذاریم و دونه دونه چک کنیم:


switch(grade) {
case PRESCHOOL:
	if (age < 5)
		throw new InvalidGradeException(...);
	break;
case SCHOOL:
	if (age < 10)
		throw new InvalidGradeException(...);
	break;
case GUIDANCE:
	if (age < 15)
		throw new InvalidGradeException(...);
	break;
case HIGHSCHOOL:
	if (age < 20)
		throw new InvalidGradeException(...);
	break;
case PREUNIVERSITY:
	if (age < 25)
		throw new InvalidGradeException(...);
	break;
}

اشکال این کار چیه؟ این که در صورتی که پس فردا یه مقدار جدید به enum تون اضافه کنید، به احتمال بسیار زیاد یادتون می‌ره که بیاین اینجا توی این switch-case هم case متناظرش رو اضافه کنید (توجه کنید که ممکنه از این switch-case ها خیلی زیاد بشه، یعنی ده جای مختلف بخواین بسته به مقطع رفتار متفاوت انجام بدین). این کار خلاف قانون OCP هست:

برای این که کار رو قشنگ کنیم، می‌تونیم منطق مربوط به بررسی سن دانش‌آموز رو ببریم توی خود enum، به این صورت:

public enum Grade {
	PRESCHOOL {
		@Override
		public String getName() {
			return "preschool";
		}

		@Override
		public void validateAge(int age) throws InvalidAgeException {
			if (age < 5)
				throw new InvalidAgeException();
		}
	},
	SCHOOL {
		@Override
		public String getName() {
			return "school";
		}

		@Override
		public void validateAge(int age) throws InvalidAgeException {
			if (age < 10)
				throw new InvalidAgeException();
		}
	},
	GUIDANCE {
		@Override
		public String getName() {
			return "guidance";
		}

		@Override
		public void validateAge(int age) throws InvalidAgeException {
			if (age < 15)
				throw new InvalidAgeException();
		}
	},
	HIGHSCHOOL {
		@Override
		public String getName() {
			return "highschool";
		}

		@Override
		public void validateAge(int age) throws InvalidAgeException {
			if (age < 20)
				throw new InvalidAgeException();
		}
	},
	PREUNIVERSITY {
		@Override
		public String getName() {
			return "preuniversity";
		}

		@Override
		public void validateAge(int age) throws InvalidAgeException {
			if (age < 25)
				throw new InvalidAgeException();
		}
	};
	
	public abstract String getName();
	public abstract void validateAge(int age) throws InvalidAgeException;
}

حالا خیلی ساده می‌تونیم هر جایی که خواستیم، به grade دانش‌آموز بگیم سن دانش‌آموز رو validate کنه.

خلاصه‌ی مطلب این که enum توی جاوا خیلی بیشتر از یه عدد ساده (اونطوری که توی ++C تعریف شده) هست و می‌شه باهاش کارهای قشنگی انجام داد.

برچسب: 

دیدگاه ها

سلام :)
مطلب خوبی بودید...

نمیدونم شما کدوم سید هستید؟ سید تیم ایندکس آیا؟
به فرض اگه همون شخص باشید ؛ من امیر شعبانی هستم! یادتون اومد؟ سال 92 ماه های آذر ، دی و بهمن! دوباره دوست دارم برگردم :)
یک رزومه نصفه نیمه ارسال کردم.

با سپاس

تصاویر سیّد

سلام
ممنون
بله یادمه، چرا یادم نباشه؟ منم خودمم! درست فهمیدی! :)
ان‌شاءالله دوستان رزومه‌ی شما رو می‌گیرن و بررسی می‌کنن.
بدرود!

پ.ن. حالا چرا نصفه-نیمه؟!

به به سید جان! خوشحالم که خودتی :دی

اما چرا نصفه-نیمه!؟ چون دو سال سرباز بودم :/
و سربازی یعنی فراموشی خیلی چیزها :|

بدورد

خیلی ممنون از توضیح تون.خیلی قشنگ توضیح دادید.استاد ما توضیح داده بود ولی من یادم رفته بود ان شالله خدا اجرتون بده.واسه همه بچه های ترم سه نرم افزار دعاکنید.

تصاویر سیّد

خواهش می‌کنم، وظیفه‌اس.

چشم دعا می‌کنیم، ولی چرا فقط برای ترم ۳؟!

تصاویر epcpu

احتمالا چون ساختمان داده ای چیزی داشتن این ترم و نزدیک امتحان ها بوده. گفته سید هم هستی دعا کنی میگیره :D
 

خوبه که ایرانی فکر کنیم و ایرانی عمل کنیم اما بشرطی که محدویت ها از طرف عوامل مسدود نشود و ادمها با سلایق مختلف بتوانند از امکانات بهره ببرند و اون موقع میشود یک موتور جستجوی صد در صد ایرانی را به اوج برسانیم و تبلیغات اجناس ایرانی تفرجگاه ها و مکانهای دیدنی ایران جهانی خواهد سد و ورود توریست و گردسگر به اقتصاد ما کمک خواهد کرد و ایجاد شغل میسود و در خیلی از نقاط ایران در فصولی هست که توریستها میتوانند به سیاحت بپردازند امنیت داریم اما محدودیت هم داریم و این مانعی بزرگ برای جذب گردشگر است بهتر است کمی روشن گرانانه به موصوع نگاه کنیم تا ایران و ایرانی جایگاه قدرتمند خود را بدست اورد و دبی که با پانزده میلیارد دلار در سال ۲۰۱۵ بالاترین کشور جذب توریست را داشته یک صدم ایران است و تمام امکانات را مصنوعی درست کردهاند و پول پارو میکنند حالا کمی فکر کنید مدیران ما اگر کمی اقتصادی غکر کنند همه چی عالی خواهد شد و اوضاع مردم همیشه در صحنه بهبود خواهد یافت و کشور پولدار یعنی قدرت و قدرت یعنی ایران

تصاویر سیّد

ممنون از نظرتون

البته ارتباطی با مطلبی که روش نظر گذاشتید نداشت! این یک مطلب فنی بود! بهتر بود روی مطلب دیگه‌ای که به کل موتور یوز مرتبط می‌شه این نظر رو می‌ذاشتید.

در هر صورت باز هم ممنون از نظرتون.

سلام
اول اینکه خیلی ممنون بابت توضیحات جامعتون ولی یه مشکلی که برای بنده پیش اومده اینه که با سناریو مشکل دارم
در سناریویی که گفتید:
کاربر ما (که دانش آموزا هستن) قرار نیست با محیط خط فرمان یا همون کامند لاین کار کنن و برای کاربرمون یک جور GUI تعریف شده و طبق حرفی که شما زدید، اگر بخوایم کاربرمون از گزینه هایی که ما خودمون تعریف کردیم استفاده کنه ComboBox میذاریم. (یعنی من این شکلی دیدم این جور سناریو ها رو) و حالا مشکلی ک پیش میاد اینه که باز از enum استفاده نشد.
من خیلی گیج شدم
یکم راهنماییم کنید

تصاویر سیّد

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

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

در حالت دوم، که مورد اشارهٔ شما بود، ما توی سایتمون همونطور که گفتید به کاربرمون (دانش‌آموز یا والدین یا معلم‌ها) اجازه نمی‌دیم که اسم پایهٔ تحصیلی مورد نظرش رو همینطوری وارد کنه. پس باید از بین یه تعداد پایهٔ تحصیلی از پیش تعریف شده انتخاب کنه. پس همونطور که گفتید یه combo box در اختیارش می‌ذاریم که انتخاب کنه. خوب تا اینجا اصلاً ربطی به enum و غیر اون پیدا نکرد، چون بحث ما سر بخش برنامه‌نویسی‌اش بود، نه بخش کاربری سایتمون. مسئله اینه که اون combo box رو چطوری پیاده‌سازی می‌کنید؟ از کجا می‌خواین بفهمین که مقادیری که توی اون combo box استفاده می‌کنید چه مقادیری هستن؟ یا باید به صورت دستی مقادیر رو وارد کنید (که همون اشکال رو داره که اگه پس‌فردا یه پایهٔ تحصیلی دیگه اضافه شد، به احتمال زیاد یادتون می‌ره اینجا هم اضافه‌اش کنید)، یا باید یه حلقه تعریف کنید روی تمام پایه‌های تحصیلی موجود. حالا برای این کار، استفاده از enum کار شما بسیار راحت‌تر می‌کنه، می‌تونید از تابع values موجود در enum استفاده کنید و تمام مقادیر رو ازش بخونید.

امیدوارم ابهامتون برطرف شده باشه.

اضافه کردن دیدگاه جدید

Plain text

  • تگ های HTML قابل قبول نمی باشد.
  • آدرس های وب و ایمیل به صورت اتوماتیک به لینک تبدیل می شوند.
  • خطوط و پاراگرافها به صورت اتوماتیک جدا سازی می شود.