Kurs Java od podstaw Tydzień 6

Enum

Enum

Tą lekcję zaczniemy od lekko tajemniczego słowa – enum. Czym jest enum? Nie powiem Ci tego przecież od razu, chwilę przytrzymam Cię w niepewności – jeśli już miałeś styczność z enumem to nic się przecież nie stało. 😉

O co tu chodzi…

Weźmy na początek klasę Product z naszej kursowej aplikacji do zarządzania produktami. Wygląda on teraz mniej więcej tak:

package pl.maniaq;


public class Product {

    public final static String PRODUCT_SEPARATOR = "#";
    public final static char PRODUCT_TYPE = 'P';

    private Long id;
    private String productName;
    private Float price;
    private Float weight;
    private String color;
    private Integer productCount;

    public Product(Long id, String productName, Float price, Float weight, String color, Integer productCount) {
        this.id = id;
        this.productName = productName;
        this.price = price;
        this.weight = weight;
        this.color = color;
        this.productCount=productCount;
    }

    public void setPrice(Float price) {
        this.price = price;
    }

    public void setProductCount(Integer productCount) {
        this.productCount = productCount;
    }

    public Long getId() {
        return id;
    }

    public String getProductName() {
        return productName;
    }

    public Float getPrice() {
        return price;
    }

    public Float getWeight() {
        return weight;
    }

    public String getColor() {
        return color;
    }

    public Integer getProductCount() {
        return productCount;
    }

    protected String getBasicProductString() {
        return id + PRODUCT_SEPARATOR + productName + PRODUCT_SEPARATOR + price + PRODUCT_SEPARATOR + weight + PRODUCT_SEPARATOR + color + PRODUCT_SEPARATOR + productCount;
    }

    @Override
    public String toString() {
        return PRODUCT_TYPE + PRODUCT_SEPARATOR + getBasicProductString();
    }
}

Widzisz te dwie statyczne stałe na samej górze?

public final static String PRODUCT_SEPARATOR = "#";
public final static char PRODUCT_TYPE = 'P';

O właśnie te – mi się one zbytnio nie podobają. Gdyby była możliwość to bym chętnie coś z nimi zrobił. W końcu są statycznymi stałymi, których w całej aplikacji są dokładnie po jednym egzemplarzu – są krótki mówiąc takim singletonami.

Może jednak jest możliwość wyrzucenia ich z tej klasy, aby rozwiązanie było dużo czytelniejsze niz teraz?

Enum

Oczywiście, że jest – i jest nim enum.

Enum jest niczym innym jak specyficzną klasą, która w swoim ciele może przechowywać obiekty tego samego typu co sama nią jest (trochę zagmatwane – zaraz się wyjaśni ;)) – a dokładnie te obiekty są singletonami.

Może domyślasz się już jego zastosowania na podstawie przykładu powyżej, jednak na początku stwórzmy sobie pierwszy enum.

Pierwszy enum

Tworzenie enum to nie jest żaden rocket science – robi się to podobnie jak klasę.

Klikamy na drzewo projektu, klikamy PPM, wybieramy New -> New Class, wpisujemy nazwę enum: Colors oraz z listy wybieramy enum i klikamy oczywiście ok.

I powstaje nam tam taki enum:

public enum Colors {
}

Choć nie ma słowa class to enum ma bardzo podobne zachowania jak klasa o czym zaraz się przekonasz.

Co miałem na myśli mówiąc, że enum przechowuje w sobie singletony tego samego typu? A no coś takiego, że możemy bezpośrednio w nim utworzyć obiekty typu Colors oddzielając je przecinkami – właśnie w ten sposób:

public enum Colors {
    RED, GREEN, BLUE, YELLOW;
}

Jeśli nie chcemy więcej wypisywać obiektów – singletonów – to stawiamy średnik.

I w taki o to prosty sposób stworzyliśmy cztery obiekty typu Colors: RED, GREEN, BLUE, YELLOW.

Pamiętaj, że nazwy obiekty enum są zazwyczaj pisane z wielkich liter!

Po co to wszystko…

Jeśli jeszcze się nie domyśliłeś po co nam enum to już lecę z wyjaśnieniami.

Enum jest również nazywany typem wyliczeniowym – każdy obiekt, który został w nim utworzony ma przypisany do siebie numer – od 0 do nieskończoności. Pierwszy obiekt ma oczywiście numer równy 0, a drugi 1 itd. Jednak to nie tłumaczy jeszcze po co powstał ten typ wyliczeniowy…

Typ wyliczeniowy powstał po to, aby móc tworzyć zbiory stałych, zamiast tworzyć właśnie statyczne stałe porozrzucane po całej aplikacji, lepiej jest stworzyć enum, w którym są wszystkie potrzebne stałe.

W takim razie zamiast takiego kodu:

public final static String PRODUCT_SEPARATOR = "#";
public final static char PRODUCT_TYPE = 'P';

Powinniśmy stworzyć taki:

public enum ProductConsts {
    PRODUCT_SEPARATOR, PRODUCT_TYPE;
}

Choć to możemy jeszcze rozszerzyć – w końcu mówiłem, że enum zachowuje podobnie się jak klasa. Zrobimy to jednak już na przykładzie kolorów.

Panie, to w końcu klasa czy nie?

Mamy aktualnie taki stan naszego typu wyliczeniowego z kolorami:

public enum Colors {
    RED, GREEN, BLUE, YELLOW;
}

Jest to są to tylko puste obiekty, które nic w sobie nie przechowują. Choć na pewno są obiektami, ponieważ korzystają z konstruktora bezparametrowa, który jest domyślnie dodawany.

public enum Colors {
    RED(), GREEN(), BLUE(), YELLOW();
}

W takim razie możemy również stworzyć swój konstruktor – parametrowy, do którego będziemy zapisywać wartości RGB – czyli nasycenie koloru czerwonego, zielonego i niebieskiego.

public enum Colors {
    RED(), GREEN(), BLUE(), YELLOW();
    
    Colors(int red, int green, int blue) {
        
    }
}

Spójrz, że konstruktor bezparametrowy już istnieje, gdy zdefiniowaliśmy swój własny konstruktor parametrowy.

Ze względu, że jest to prawie klasa to możemy również przechowywać w nim pola.

public enum Colors {
    RED(), GREEN(), BLUE(), YELLOW();

    private int red;
    private int green;
    private int blue;

    Colors(int red, int green, int blue) {

    }
}

Oczywiście zainicjalizujmy nasze pola:

public enum Colors {
    RED(), GREEN(), BLUE(), YELLOW();

    private int red;
    private int green;
    private int blue;

    Colors(int red, int green, int blue) {
        this.red=red;
        this.green=green;
        this.blue=blue;
    }
}

Kod nie jest jeszcze gotowy do skompilowania, musimy jeszcze użyć konstruktora parametrowego podczas tworzenia naszych singletonów.

public enum Colors {
    RED(255, 0, 0), GREEN(0, 255, 0), BLUE(0, 0, 255), YELLOW(255, 204, 0);

    private int red;
    private int green;
    private int blue;

    Colors(int red, int green, int blue) {
        this.red=red;
        this.green=green;
        this.blue=blue;
    }
}

Nasz kod jest już kompilowalny, jednak dodajmy jeszcze coś jeszcze…

Implementacja

Stwórzmy sobie interfejs – w końcu enum potrafi również implementować interfejsy – coś mu zostało z tej klasy. 😉

public interface ColorOperations {
    String getHexColor();
    int getRGB();
}

No i zaimplementujmy:

package pl.maniaq;

import java.awt.*;

public enum Colors implements ColorOperations{
    RED(255, 0, 0), GREEN(0, 255, 0), BLUE(0, 0, 255), YELLOW(255, 204, 0);

    private int red;
    private int green;
    private int blue;

    Colors(int red, int green, int blue) {
        this.red=red;
        this.green=green;
        this.blue=blue;
    }

    @Override
    public String getHexColor() {
        return "#"+Integer.toHexString(getRGB());
    }

    @Override
    public int getRGB() {
        return new Color(red, green, blue).getRGB();
    }

}

Do obliczenia RGB wykorzystuję klasę Color, zaś na wartość hex używana jest metoda toHexString, która konwertuję liczbę z systemu dziesiętnego na liczbę hex – czyli szesnastkową.

Dodajmy jeszcze do tego krótkiego maina:

public class Main {

    public static void main(String[] args) {
      System.out.println(Colors.BLUE.getHexColor());
        System.out.println(Colors.RED.getHexColor());;
        System.out.println(Colors.YELLOW.getHexColor());
    }
}

W taki o to sposób otrzymaliśmy wartości hex danych kolorów:

#ff0000ff
#ffff0000
#ffffcc00

Switch case

Enum bardzo fajnie się sprawdza i wygląda w switchu – sam zobacz:

switch (color) {
    case YELLOW:
        System.out.println("Dales mi kolor żółty");
        break;
    case GREEN:
        System.out.println("Dales mi kolor zielony");
        break;
    case BLUE:
        System.out.println("Dales mi kolor niebieski");
        break;
    case RED:
        System.out.println("Dales mi kolor czerwony");
        break;
}

Myślisz, że możesz to się gdzieś przydać? Mi się wydaję, że w ProductParserze.

Values

Skoro nasz enum to typ wyliczeniowy i każdy z obiektów ma swój indeks to tak naprawdę mamy możliwość dostania się do tablicy tych singletonów metodą values() – a po niej możemy latać np. pętlą foreach.

for (Colors colors : Colors.values()) {
    System.out.println("Biorę color hex: " + colors.getHexColor());
}

Name

Można również dostać się do nazwy obiektu przy użyciu metody name().

for (Colors colors : Colors.values()) {
    System.out.println("Biorę color hex: " + colors.getHexColor());
    System.out.println("Biorę color: " + colors.name());
}

I otrzymujemy:

Dales mi kolor żółty
Biorę color hex: #ffff0000
Biorę color: RED
Biorę color hex: #ff00ff00
Biorę color: GREEN
Biorę color hex: #ff0000ff
Biorę color: BLUE
Biorę color hex: #ffffcc00
Biorę color: YELLOW

Ordinal

Oraz do numeru obiektu – czyli jego id.

for (Colors colors : Colors.values()) {
    System.out.println("Biorę color hex: " + colors.getHexColor());
    System.out.println("Biorę color: " + colors.name());
    System.out.println("Jego numer: " + colors.ordinal());
}

I otrzymujemy:

Dales mi kolor żółty
Biorę color hex: #ffff0000
Biorę color: RED
Jego numer: 0
Biorę color hex: #ff00ff00
Biorę color: GREEN
Jego numer: 1
Biorę color hex: #ff0000ff
Biorę color: BLUE
Jego numer: 2
Biorę color hex: #ffffcc00
Biorę color: YELLOW
Jego numer: 3

Enum vs Klasa

Od początku powtarzam, że enum jest prawie klasą – podsumuje tu najwazniejsze różnice.

  1. Enum ma tylko konstruktor prywatny – czyli jego obiekty można utworzyć tylko w enumie (po przecinku) – taki zapis nie jest możliwy!
    Colors color2 = new Colors(125, 55, 2);
  2. Enum nie może dziedziczyć po innej klasie, ani innym enum! Taki zapis nie jest możliwy!
    public enum Colors extends OtherColors
  3. Enum może implementować interfejs

    public enum Colors implements ColorOperations
  4. Enum może być łatwo używanych w switchu
     switch (color) {
                case YELLOW:
                    System.out.println("Dales mi kolor żółty");
                    break;
                case GREEN:
                    System.out.println("Dales mi kolor zielony");
                    break;
                case BLUE:
                    System.out.println("Dales mi kolor niebieski");
                    break;
                case RED:
                    System.out.println("Dales mi kolor czerwony");
                    break;
            }
  5. Obiekty enuma są zawsze singletonami

Podsumowanie

Podsumowując enum jest idealnym narzędziem do przechowywania stałych – String pozwala nam na przechowanie tylko jednej wartości – ze względu, że typ wyliczeniowy oferuje możliwośc utworzenia obiektu możemy przechowywać więcej danych niż w samym Stringu.

Dodatkowym atutem jest to, że typ wyliczeniowy oferuje obiekty będące singletonami co nie zuzywa zbędnie pamięci maszyny wirtualnej. Dużym atutem jest też to, że łatwo i przyjemnie używa się switcha w kontekście typu wyliczeniowego. No po prostu jest do tego stworzonego – do przechowywania przeróżnych stałych!

Kod całej aplikacji z lekcji możesz zobaczyć tutaj.

Aby utrwalić swoją wiedzę rozwiąż poniższe zadanie.

  1. Stwórz interfejs Size wraz z metodą:  int getHeight(), int getChestLength() oraz int getWaistLength()
  2. Stwórz enum TshirtSize wraz z wartościami: XS, S, M, L, XL
  3. Stwórz prywatny konstruktor, w którym ustawisz height, chestLength, waistLength.
  4. Zaimplementuj interfejs Size.
  5. W metodzie main wypisz następujące wartości wszystkich obiektów TShirtSizeNumer obiektu, jego nazwa, height, chestLength oraz waistLength.
  6. Użyj switcha i obsłuż w nim wszystkie rozmiary wypisując: numer i nazwę obiektu.

Moje przykładowe zadanie możesz zobaczyć tutaj.

 

Subscribe
Powiadom o
guest
0 komentarzy
Inline Feedbacks
View all comments