Hej! Idag ska vi prata om en av Javas speciella datatyper:
Enum
(förkortning för "uppräkning"). Vad gör den speciell? Låt oss föreställa oss vad vi behöver för att implementera "månader" i ett program. Det verkar inte vara problematiskt, eller hur? Vi behöver bara bestämma vilka egenskaper som varje månad har. Kanske behöver vi först månadens namn och antalet dagar i den. Lösningen ser ganska enkel ut:
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 +
'}';
}
}
Hela shabang! Vi har en Month
klass, de obligatoriska fälten, getter/setters och toString()
. Såvida vi inte behöver lägga till equals()
ochhashCode()
att uppnå fullständig lycka :) Men här har vi ett konceptuellt problem. Som du säkert kommer ihåg är en av de främsta fördelarna med OOP att det gör det enkelt att modellera enheter från den verkliga världen. En stol, en bil, en planet — alla dessa begrepp från det vanliga livet är lätt representerade i ett program med hjälp av abstraktion. Problemet är att vissa verkliga enheter har ett strikt begränsat värdeintervall. Det är bara 4 säsonger på ett år. Det finns bara 8 toner i en oktav. Kalendern har bara 12 månader. Och Danny Ocean of Ocean's 11 har bara 11 vänner (även om detta inte spelar någon roll :)) Vad som spelar roll är att en vanlig Java-klass inte kan modellera dessa enheter och upprätthålla deras naturliga begränsningar. VårMonth
klass har alla obligatoriska fält. Men om en annan programmerare använder det kan ingen hindra honom eller henne från att skapa helt galna objekt:
public class Main {
Month month1 = new Month("lolkek", 322);
Month month2 = new Month("yahoooooooooooo", 12345);
}
Om detta visas i vår kod kommer det inte att vara lätt att hitta den skyldige! Å ena sidan kan programmeraren som skapar objekten inse att klassen Month
betyder "månad på ett år" och inte skriva sådant nonsens. Å andra sidan utnyttjar programmeraren bara de förmågor som klassdesignern tillhandahållit. Är det möjligt att tilldela godtyckliga namn och antal dagar? Det är precis vad vi fick. Vad ska vi då göra i den här situationen? Ärligt talat, innan Java 1.5 släpptes, var programmerare tvungna att bli kreativa :) På den tiden skapade de strukturer som denna:
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 +
'}';
}
}
Här har vi minskat antalet månader från tolv till tre för att göra exemplet kortare. Sådana konstruktioner gjorde det möjligt att lösa problemet. Möjligheten att skapa objekt var begränsad till en privat konstruktör:
private Month(String name, int daysCount) {
this.name = name;
this.daysCount = daysCount;
}
Programmerare som använder klassen kunde inte bara skapa Month
objekt. De var tvungna att använda de slutliga statiska objekten från klassutvecklaren. Till exempel, så här:
public class Main {
public static void main(String[] args) {
Month january = Month.JANUARY;
System.out.println(january);
}
}
Men Java-utvecklare uppmärksammade det befintliga problemet. Visst är det bra att programmerare kunde komma på en lösning med hjälp av de verktyg som finns på språket, men det ser inte särskilt lätt ut! En uppenbar lösning behövdes, även för nybörjare. Och så Enum
dök upp i Java. I grund och botten Enum
är det en Java-klass som tillhandahåller en begränsad uppsättning objektvärden. Så här ser det ut:
public enum Month {
JANUARY,
FEBRUARY,
MARCH
}
I definitionen angav vi att det Enum
är en Java-klass, men är det verkligen sant? Ja, och vi kan till och med verifiera det. Försök till exempel att få vår Month
enum att ärva någon annan klass:
public abstract class AbstractMonth {
}
// Error! The extends clause cannot be used with an enum
public enum Month extends AbstractMonth {
JANUARY,
FEBRUARY,
MARCH
}
Varför händer det? När vi skriver:
public enum Month
kompilatorn konverterar denna sats till följande kod:
public Class Month extends Enum
Som du redan vet stöder Java inte multipelt arv. Därför kan vi inte ärva AbstractMonth
. Hur kan denna nya konstruktion, , Enum
användas? Och hur skiljer det sig från den gamla konstruktionen med static final
fält? Tja, som ett exempel lät den gamla konstruktionen oss inte använda vår egen uppsättning värden i switch
uttalanden. Föreställ dig att vi vill skapa ett program som påminner oss om de högtider som firas varje månad:
public class HolidayReminder {
public void printHolidays(Month month) {
switch (month) {
// Error!
case JANUARY:
}
}
}
Som du kan se, ger kompilatorn ett fel här. Men när det väl enum
dök upp i Java 1.5 blev allt mycket enklare:
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);
}
}
Konsolutgång:
New Year's Day is January 1st!
Observera att tillgången till Enum
objekt förblev statisk, precis som den var före Java 1.5. Vi behöver inte skapa ett Month
objekt för att komma åt månaderna. När man arbetar med enums är det väldigt viktigt att inte glömma att det Enum
är en fullfjädrad klass. Detta innebär att du vid behov kan definiera konstruktörer och metoder i den. Till exempel, i det föregående kodfragmentet angav vi helt enkelt värdena: JANUARI, FEBRUARI, MARS. Men vi kan utöka vår Month
uppräkning så här:
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 +
'}';
}
}
Här gav vi våra enum
2 fält (namnet på månaden och antalet dagar), en konstruktor som använder dessa fält, getter/setters, metoden toString()
och 2 statiska metoder. Som ni ser var det inga problem med detta. Återigen, Enum
är verkligen en fullfjädrad klass:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
System.out.println(Arrays.toString(Month.getSummerMonths()));
}
}
Konsolutgång:
[Month{name='June', daysCount=30}, Month{name='July', daysCount=31}, Month{name='August', daysCount=31}]
Slutligen vill jag tipsa om en extremt användbar Java-bok, nämligen "Effektiv Java" av Joshua Bloch . Författaren är en av Javas skapare, så du kan definitivt lita på hans råd om hur du korrekt och kompetent använder språkets verktyg :) När det gäller vår lektion rekommenderar jag att du ägnar särskild uppmärksamhet åt bokens kapitel om Enum
. Glad läsning! :)