Kurs Java Podstawy - rozszerzony

Enum

Ciekawą opcją w Javie jest tworzenie typu enum. Pozwala on na definiowane wybranego zbioru możliwych wartości.

 

Ten tutorial obejmuje:

1. Prosty enum

2. Własny konstruktor

3. Przesłonięcie metody

4. Iteracja po elementach

5. Implementowanie interfejsów

6. Własny komparator

7. Użyteczny przykład użycia

 

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

	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;
	}

}

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

 

 

Masz pytania? Znalazłeś błąd? Komentarze są do Twojej dyspozycji! :)

Komentarze

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.