Enum - Typ wyliczeniowy

Enum to typ wyliczeniowy, który umożliwia w Javie zadeklarowanie ograniczonej liczby możliwych wartości. Przydatny np. w przypadku deklaracji statusów procesu.

Prosty enum

Osobna klasa:

public enum Kolor {
 	CZERWONY, ZIELONY, NIEBIESKI;
 }

Wewnętrzna klasa:

public class EnumTest {
 	public enum Kolor {
 		CZERWONY, ZIELONY, NIEBIESKI;
 	}
 }

Zastosowanie:

	public static void main(String[] args) {
 
 		EnumTest.Kolor kolor = EnumTest.Kolor.CZERWONY;
 
 		if(kolor.equals(EnumTest.Kolor.CZERWONY)) {
 			System.out.println("Zgadza sie");
 		}
 
 		if(kolor.equals(EnumTest.Kolor.NIEBIESKI)) {
 			System.out.println("Zgadza sie");
 		} else {
 			System.out.println("Nie zgadza sie");
 		}
 	}

Na razie szału nie ma, ale spokojnie - to dopiero początki.

Własny Konstruktor

	public enum Kolor {
 
 		CZERWONY(false), 
 		ZIELONY(true), 
 		NIEBIESKI(true);
 
 		boolean ladny;
 
 		private Kolor(boolean czyLadny) {
 			ladny = czyLadny;
 		}
 	}

Przykładowe zastosowanie:

public class EnumTest {
 	public enum Kolor {
 
 		CZERWONY(false), 
 		ZIELONY(true), 
 		NIEBIESKI(true);
 
 		boolean ladny;
 
 		private Kolor(boolean czyLadny) {
 			ladny = czyLadny;
 		}
 	}
 
 	public static void main(String[] args) {
 
 		Kolor kolor = EnumTest.Kolor.CZERWONY;		
 		System.out.println("Kolor czerwony jest "+czyLadny(kolor));
 
 		kolor = EnumTest.Kolor.ZIELONY;		
 		System.out.println("Kolor zielony jest "+czyLadny(kolor));
 
 	}
 
 	public static String czyLadny(Kolor kolor) {
 		return (kolor.ladny) ? "ladny" : "brzydki";
 	}
 
 }

Wynik:

Kolor czerwony jest brzydki
Kolor zielony jest ladny

Można też trochę zwięźlej:

public class EnumTest {
 	public enum Kolor {
 
 		CZERWONY(false), 
 		ZIELONY(true), 
 		NIEBIESKI(true);
 
 		boolean ladny;
 
 		private Kolor(boolean czyLadny) {
 			ladny = czyLadny;
 		}
 	}
 
 	public static void main(String[] args) {
 
 		System.out.println(czyLadny(Kolor.CZERWONY));
 		System.out.println(czyLadny(Kolor.ZIELONY));		
 	}
 
 	public static String czyLadny(Kolor kolor) {
 		String czyLadny = (kolor.ladny) ? "ladny" : "brzydki";
 
 		return "Kolor "+kolor.toString()+" jest "+czyLadny;
 	}
 
 }

Wynik:

Kolor CZERWONY jest brzydki
Kolor ZIELONY jest ladny

Tym razem wynik jest niezadowalający, ponieważ cała nazwa drukowana jest z dużych liter. Spróbujemy to naprawić

Przesłonięcie metody toString()

	public enum Kolor {
 
 		CZERWONY(false), 
 		ZIELONY(true), 
 		NIEBIESKI(true);
 
 		boolean ladny;
 
 		private Kolor(boolean czyLadny) {
 			ladny = czyLadny;
 		}
 
 		@Override
 		public String toString() {
 			String poprzedniaNazwa = super.toString();
 			String nowaNazwa = poprzedniaNazwa.toLowerCase();
 			return nowaNazwa;
 		}
 	}

Krócej:

		@Override
 		public String toString() {
 			return super.toString().toLowerCase();
 		}

No i wynik wywołania:

Kolor czerwony jest brzydki
Kolor zielony jest ladny

Iteracja po elementach

Możemy bezproblemowo przeiterować po elementach enuma.

Za pomocą zwykłej pętli for:

		Kolor[] kolory = Kolor.values();
 		for(int i=0; i<kolory.length; i++) {
 			System.out.println(czyLadny(kolory[i]));
 		}

Krócej:

		for(int i=0; i<Kolor.values().length; i++) {
 			System.out.println(czyLadny(Kolor.values()[i]));
 		}

Za pomocą foreach:

		for(Kolor kolor: Kolor.values()) {
 			System.out.println(czyLadny(kolor));
 		}

Chyba nie muszę przekonywać która opcja wygląda najbardziej przejrzyście :)

Wynik wywołania:

Kolor czerwony jest brzydki
Kolor zielony jest ladny
Kolor niebieski jest ladny

Implementowanie interfejsów

Enum, jak reszta klas w Javie, może rozszerzać interfejsy. Przykład:

public class EnumTest {
 	public enum Kolor implements Runnable {
 
 		CZERWONY(false), 
 		ZIELONY(true), 
 		NIEBIESKI(true);
 
 		boolean ladny;
 
 		private Kolor(boolean czyLadny) {
 			ladny = czyLadny;
 		}
 
 		@Override
 		public String toString() {
 			String poprzedniaNazwa = super.toString();
 			String nowaNazwa = poprzedniaNazwa.toLowerCase();
 			return nowaNazwa;
 		}
 
 		@Override
 		public void run() {
 			System.out.println(czyLadny(this));
 		}
 	}
 
 	public static void main(String[] args) {
 
 		for(Runnable kolor: Kolor.values()) {
 			kolor.run();
 		}	
 	}
 
 	public static String czyLadny(Kolor kolor) {
 		String czyLadny = (kolor.ladny) ? "ladny" : "brzydki";
 
 		return "Kolor "+kolor.toString()+" jest "+czyLadny;
 	}
 
 }

Kurs Java

Dowodem na to, że wszystko działa może być linia:

for(Runnable kolor: Kolor.values()) {

Własny komparator

Typ enum nie umożliwia nadpisania metody int compareTo() - jest ona ustawiona jako finalna. Co zrobić w takim razie, jeśli potrzebujemy własny komparator?

Piszemy własną klasę implementującą interfejs Comparator

	public class MojKomparator implements Comparator<Kolor> {
 		public int compare(Kolor pierwszy, Kolor drugi) {
 
 			// najpierw porównaj, czy obydwa sa ladne / brzydkie
 			if (pierwszy.ladny != drugi.ladny) {
 				if (pierwszy.ladny) {
 					return -1;
 				} else {
 					return 1;
 				}
 			} else {
 				// jesli te same, to porównaj nazwy (alfabetycznie)
 				return pierwszy.toString().compareTo(drugi.toString());
 			}
 		}
 	}

Następnie jej używamy:

	public static void main(String[] args) {
 
 		EnumTest test = new EnumTest();
 		MojKomparator mojKomparator = test.new MojKomparator();
 
 		Kolor[] kolory = Kolor.values();
 
 		System.out.println("Przed sortowaniem:");
 		for (Kolor kolor : kolory) {
 			System.out.println(kolor.toString());
 		}
 
 		Arrays.sort(kolory, mojKomparator);
 
 		System.out.println("\nPo sortowaniu");
 		for (Kolor kolor : kolory) {
 			System.out.println(kolor.toString());
 		}
 	}

Wynik:

Przed sortowaniem:
 czerwony
 zielony
 niebieski
 
 Po sortowaniu (najpierw ładne, potem brzydkie, a jeśli oba są tej samej kategorii, to sortujemy alfabetycznie po nazwie)
 niebieski
 zielony
 czerwony

Użyteczny przykład użycia

Załóżmy, że piszemy program i potrzebna jest funkcjonalność odczytania od użytkownika odpowiedzi typu tak lub nie.

Pierwsze rozwiązanie jakie przychodzi na myśl, to po prostu porównanie tekstu podanego przez usera. Mniej więcej coś takiego:

	public boolean odpUsera(String wejscie) {
 		if(wejscie.equals("tak"))
 			return true;
 
 		return false;
 	}

Jednak co się stanie, jeśli użytkownik wpisze "t" albo "yes". Tego nie przewidujemy. Można oczywiście dodać dodatkowe warunki, ale czy taki kod:

	public boolean odpUsera(String wejscie) {
 		if (wejscie.equals("tak") || wejscie.equals("t")
 				|| wejscie.equals("yes") || wejscie.equals("y")
 				|| wejscie.equals("sure") || wejscie.equals("ok"))
 			return true;
 
 		return false;
 	}

wygląda elegancko? Według mnie niezbyt. Spróbujemy zrealizować to stosując odpowiedni enum.

public class EnumPrzyklad {
 
 	public enum Odpowiedz {
 
 		TAK(true),
 		NIE(false),
 		T(true),
 		N(false),
 		YES(true),
 		NO(false),
 		OK(true),
 		SURE(true),
 		NOPE(false);
 
 		private boolean wartosc;
 
 		private Odpowiedz(boolean wartosc) {
 			this.wartosc = wartosc;
 		}
 
 		public boolean getWartosc() {
 			return wartosc;
 		}
 	}
 
 	public static void main(String[] args) {
 
 		EnumPrzyklad test = new EnumPrzyklad();
 		String[] wartosciUsera = {"tak", "TAK", "OK", "ta", "nie", "niewiem", "NoPe" };
 
 		test.simplePrint("==WEJSCIE==", "==WYJSCIE==");
 		test.simplePrint("-----------", "-----------");
 
 		for(String wejscie : wartosciUsera) {
 
 			test.simplePrint(wejscie, test.odpUseraString(wejscie));	
 		}	
 	}
 
 	public String odpUseraString(String wejscie) {
 		try {
 			return odpUsera(wejscie) ? "pozytywna" : "negatywna";
 		} catch (UnknownAnswerException e) {
 			return "nieznana";
 		}
 	}
 
 	public boolean odpUsera(String wejscie) throws UnknownAnswerException {
 		for(Odpowiedz odp : Odpowiedz.values()) {
 			if(odp.toString().equalsIgnoreCase(wejscie))
 				return odp.getWartosc();
 		}
 		throw new UnknownAnswerException();
 	}
 
 	private void simplePrint(String one, String two) {
 		System.out.printf("%15s  |  %10s\n", one, two);
 	}
 
 }

Wynik:

    ==WEJSCIE==  |  ==WYJSCIE==
     -----------  |  -----------
             tak  |   pozytywna
             TAK  |   pozytywna
              OK  |   pozytywna
              ta  |    nieznana
             nie  |   negatywna
         niewiem  |    nieznana
            NoPe  |   negatywna

Podsumowanie

Jak widać enum ma sporo zastosowań. Najczęściej korzysta się z niego jednak jako przedstawienie wszystkich dostępnych wartości i tam sprawdza się najlepiej.

Dyskusja i komentarze

Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.

Poniżej znajdziesz archiwalne wpisy z czasów, gdy strona była jeszcze hobbystycznym blogiem.

Emihis

Dzieki! Te informacje napewno sie przydadza:)

Marcinho

Bardzo pomocny artykuł :)

Rob

W przykładzie z sotrowaniem, nie posotrowało alfabetycznie.

Marcin Kunert

Faktycznie, zaraz sprawdzę o co chodzi. Dzięki za info.

Marcin Kunert

Sprawdziłem - jednak jest dobrze. Sortujemy najpierw po kategorii, a potem alfabetycznie. (najpierw ładne, potem brzydkie, a jeśli oba są tej samej kategorii, to sortujemy alfabetycznie po nazwie)

domek

Jest źle. Błąd polega na tym, że komparator przy dwóch różnych wartościach (bez wzglęgu czy dostaje true, false czy false, true) zawsze uznaje, że pierwszy powinien być pierwszy argument. Należałoby jeszcze sprawdzić czy pierwszy argument to true i zwrócić -1, jeśli false zwrócić 1. W powyższym przypadku sortuje akurat dobrze, ale wystarczy dodać jako pierwszy jakiś dodatkowy kolor będący true, np. BIALY(true), ZOLTY(true), CZERWONY(false), ZIELONY(true), NIEBIESKI(true); I już sortowanie będzie błędne (w podanym przypadku CZERWONY będzie pierwszy, mimo, że jest false).

Marcin Kunert

Masz rację. Dzięki za wskazanie błędu. Poprawiłem.

Bartol

Podswietla mi UnknownAnswerException, dlaczego ?!

Marcin Kunert

Bo musisz taki wyjątek stworzyć. public class UnknownAnswerException extends Exception { }

Bartol

Dobra, pomogło. Dzięki.