สวัสดี! วันนี้เราจะพูดถึงประเภทข้อมูลพิเศษประเภทหนึ่งของ Java:
Enum
(ย่อมาจาก "enumeration") อะไรทำให้มันพิเศษ? ลองจินตนาการถึงสิ่งที่เราต้องใช้ "เดือน" ในโปรแกรม ดูเหมือนจะไม่เป็นปัญหาใช่ไหม? เราก็แค่กำหนดคุณสมบัติว่าเดือนใดมีคุณสมบัติอย่างไร บางทีเราต้องการชื่อเดือนและจำนวนวันในนั้นก่อน วิธีแก้ปัญหาดูค่อนข้างง่าย:
public class Month {
private String name;
private int daysCount;
public Month(String name, int daysCount) {
this.name = name;
this.daysCount = daysCount;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getDaysCount() {
return daysCount;
}
public void setDaysCount(int daysCount) {
this.daysCount = daysCount;
}
@Override
public String toString() {
return "Month{" +
"name='" + name + '\'' +
", daysCount=" + daysCount +
'}';
}
}
พังทั้งคัน! เรามีMonth
คลาส, ฟิลด์บังคับ, getter/setters toString()
และ แน่นอนว่าเราต้องเพิ่มequals()
และhashCode()
เพื่อบรรลุความสุขสมบูรณ์ :) แต่ที่นี่เรามีปัญหาทางความคิด อย่างที่คุณคงจำได้ ข้อดีหลักประการหนึ่งของ OOP คือทำให้ง่ายต่อการจำลองเอนทิตีจากโลกแห่งความเป็นจริง เก้าอี้ รถยนต์ ดาวเคราะห์ — แนวคิดทั้งหมดเหล่านี้จากชีวิตธรรมดาสามารถแสดงได้อย่างง่ายดายในโปรแกรมด้วยความช่วยเหลือจากสิ่งที่เป็นนามธรรม ปัญหาคือเอนทิตีในโลกแห่งความเป็นจริงบางตัวมีช่วงของค่าที่จำกัดอย่างเคร่งครัด ปีหนึ่งมีแค่ 4 ฤดู มีเพียง 8 โน้ตในอ็อกเทฟ ปฏิทินมีแค่ 12 เดือน และ Danny Ocean of Ocean's 11 มีเพื่อนเพียง 11 คน (แม้ว่าจะไม่สำคัญก็ตาม :)) สิ่งที่สำคัญคือคลาส Java ธรรมดาไม่สามารถจำลองเอนทิตีเหล่านี้และบังคับใช้ข้อจำกัดตามธรรมชาติของพวกมันได้ ของเราMonth
ชั้นเรียนมีฟิลด์บังคับทั้งหมด แต่ถ้าโปรแกรมเมอร์คนอื่นใช้ ก็จะไม่มีใครหยุดเขาหรือเธอจากการสร้างอ็อบเจกต์ที่บ้าคลั่งได้:
public class Main {
Month month1 = new Month("lolkek", 322);
Month month2 = new Month("yahoooooooooooo", 12345);
}
หากสิ่งนี้ปรากฏในรหัสของเรา การค้นหาผู้กระทำความผิดนั้นไม่ใช่เรื่องง่าย! ในแง่หนึ่ง โปรแกรมเมอร์ที่สร้างวัตถุอาจตระหนักว่าMonth
คลาสหมายถึง "เดือนในหนึ่งปี" และไม่เขียนเรื่องไร้สาระเช่นนั้น ในทางกลับกัน โปรแกรมเมอร์ใช้ประโยชน์จากความสามารถที่ผู้ออกแบบคลาสมอบให้เท่านั้น เป็นไปได้ไหมที่จะกำหนดชื่อและจำนวนวันตามอำเภอใจ? นั่นคือสิ่งที่เราได้รับ แล้วเราควรทำอย่างไรในสถานการณ์เช่นนี้? จริงๆ แล้วก่อนที่ Java 1.5 จะเปิดตัว โปรแกรมเมอร์ต้องมีความคิดสร้างสรรค์ :) ในสมัยนั้น พวกเขาสร้างโครงสร้างดังนี้:
public class Month {
private String name;
private int daysCount;
private Month(String name, int daysCount) {
this.name = name;
this.daysCount = daysCount;
}
public static Month JANUARY = new Month("January", 31);
public static Month FEBRUARY = new Month("February", 28);
public static Month MARCH = new Month("March", 31);
@Override
public String toString() {
return "Month{" +
"name='" + name + '\'' +
", daysCount=" + daysCount +
'}';
}
}
เราได้ตัดจำนวนเดือนจากสิบสองเหลือสามเดือนเพื่อให้ตัวอย่างสั้นลง การออกแบบดังกล่าวทำให้สามารถแก้ปัญหาได้ ความสามารถในการสร้างอ็อบเจกต์ถูกจำกัดไว้เฉพาะคอนสตรัคเตอร์ส่วนตัว:
private Month(String name, int daysCount) {
this.name = name;
this.daysCount = daysCount;
}
โปรแกรมเมอร์ที่ใช้คลาสไม่สามารถสร้างMonth
วัตถุ ได้ พวกเขาต้องใช้อ็อบเจกต์สแตติกขั้นสุดท้ายที่ผู้พัฒนาคลาสจัดเตรียมไว้ให้ ตัวอย่างเช่น:
public class Main {
public static void main(String[] args) {
Month january = Month.JANUARY;
System.out.println(january);
}
}
แต่นักพัฒนา Java ให้ความสนใจกับปัญหาที่มีอยู่ แน่นอนว่าเป็นเรื่องดีที่โปรแกรมเมอร์สามารถคิดวิธีแก้ปัญหาโดยใช้เครื่องมือที่มีอยู่ในภาษาได้ แต่ดูไม่ง่ายเลย! จำเป็นต้องมีวิธีแก้ปัญหาที่ชัดเจนแม้กระทั่งสำหรับผู้เริ่มต้น และEnum
ปรากฏใน Java โดยทั่วไปEnum
เป็นคลาส Java ที่ให้ชุดของค่าวัตถุที่จำกัด นี่คือลักษณะ:
public enum Month {
JANUARY,
FEBRUARY,
MARCH
}
ในนิยาม เราระบุว่าEnum
เป็นคลาส Java แต่จริงหรือไม่? ได้ และเรายังสามารถตรวจสอบได้ ตัวอย่างเช่น ลองทำให้Month
enum ของเราสืบทอดคลาสอื่น:
public abstract class AbstractMonth {
}
// Error! The extends clause cannot be used with an enum
public enum Month extends AbstractMonth {
JANUARY,
FEBRUARY,
MARCH
}
ทำไมถึงเกิดขึ้น? เมื่อเราเขียน:
public enum Month
คอมไพเลอร์แปลงคำสั่งนี้เป็นรหัสต่อไปนี้:
public Class Month extends Enum
ดังที่คุณทราบแล้ว Java ไม่สนับสนุนการสืบทอดหลายรายการ ดังนั้นเราจึงรับมรดกไม่AbstractMonth
ได้ โครงสร้างใหม่นี้จะEnum
นำไปใช้ได้อย่างไร? แล้วมันแตกต่างจากโครงสร้างแบบเก่าที่มีstatic final
ฟิลด์อย่างไร? ตัวอย่างเช่น โครงสร้างแบบเก่าไม่อนุญาตให้เราใช้ชุดค่าของเราเองในswitch
คำสั่ง ลองนึกภาพว่าเราต้องการสร้างโปรแกรมที่จะเตือนเราถึงวันหยุดที่มีการเฉลิมฉลองในแต่ละเดือน:
public class HolidayReminder {
public void printHolidays(Month month) {
switch (month) {
// Error!
case JANUARY:
}
}
}
อย่างที่คุณเห็น คอมไพเลอร์แสดงข้อผิดพลาดที่นี่ แต่เมื่อenum
ปรากฏใน Java 1.5 ทุกอย่างก็ง่ายขึ้นมาก:
public enum Month {
JANUARY,
FEBRUARY,
MARCH
}
public class HolidayReminder {
public void printHolidays(Month month) {
switch (month) {
case JANUARY:
System.out.println("New Year's Day is January 1st!");
break;
case FEBRUARY:
System.out.println("Valentine's Day is February 14th!");
break;
case MARCH:
System.out.println("Saint Patrick's Day is March 17th!");
break;
}
}
}
public class Main {
public static void main(String[] args) {
HolidayReminder reminder = new HolidayReminder();
reminder.printHolidays(Month.JANUARY);
}
}
เอาต์พุตคอนโซล:
New Year's Day is January 1st!
โปรดทราบว่าการเข้าถึงEnum
ออบเจกต์ยังคงเป็นแบบคงที่เหมือนก่อน Java 1.5 เราไม่จำเป็นต้องสร้างMonth
วัตถุเพื่อเข้าถึงเดือน เมื่อทำงานกับ enums สิ่งสำคัญคือต้องไม่ลืมว่าEnum
เป็นคลาสเต็มรูปแบบ ซึ่งหมายความว่า หากจำเป็น คุณสามารถกำหนดคอนสตรัคเตอร์และวิธีการในนั้นได้ ตัวอย่างเช่น ในโค้ดแฟรกเมนต์ก่อนหน้านี้ เราเพียงแค่ระบุค่า: JANUARY, FEBRUARY, MARCH อย่างไรก็ตาม เราสามารถขยายMonth
enum ได้ดังนี้:
public enum Month {
JANUARY("January", 31),
FEBRUARY("February", 28),
MARCH("March", 31),
APRIL("April", 30),
MAY("May", 31),
JUNE("June", 30),
JULY("July", 31),
AUGUST("August", 31),
SEPTEMBER("September", 30),
OCTOBER("October", 31),
NOVEMBER("November", 30),
DECEMBER("December", 31);
private String name;
private int daysCount;
Month(String name, int daysCount) {
this.name = name;
this.daysCount = daysCount;
}
public static Month[] getWinterMonths() {
return new Month[]{DECEMBER, JANUARY, FEBRUARY};
}
public static Month[] getSummerMonths() {
return new Month[]{JUNE, JULY, AUGUST};
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getDaysCount() {
return daysCount;
}
public void setDaysCount(int daysCount) {
this.daysCount = daysCount;
}
@Override
public String toString() {
return "Month{" +
"name='" + name + '\'' +
", daysCount=" + daysCount +
'}';
}
}
ที่นี่เราให้enum
2 ฟิลด์ของเรา (ชื่อเดือนและจำนวนวัน) ตัวสร้างที่ใช้ฟิลด์เหล่านี้ getter/setters เมธอดtoString()
และ 2 เมธอดสแตติก อย่างที่คุณเห็นไม่มีปัญหากับสิ่งนี้ อีกครั้งEnum
เป็นคลาสที่เต็มเปี่ยม:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
System.out.println(Arrays.toString(Month.getSummerMonths()));
}
}
เอาต์พุตคอนโซล:
[Month{name='June', daysCount=30}, Month{name='July', daysCount=31}, Month{name='August', daysCount=31}]
สุดท้ายนี้ ผมอยากแนะนำหนังสือ Java ที่มีประโยชน์อย่างยิ่ง นั่นคือ" Effective Java" โดย Joshua Bloch ผู้เขียนเป็นหนึ่งในผู้สร้างของ Java ดังนั้นคุณสามารถไว้วางใจคำแนะนำของเขาเกี่ยวกับวิธีการใช้เครื่องมือภาษาอย่างถูกต้องและเหมาะสมได้อย่างแน่นอน :) สำหรับบทเรียนของเรา ฉันขอแนะนำให้คุณให้ความสนใจเป็นพิเศษกับบทของหนังสือเกี่ยวEnum
กับ มีความสุขในการอ่าน! :)
GO TO FULL VERSION