Liigu peamise sisu juurde

Enum - konstantide kogumik

Sissejuhatus

Mõned väärtused programmis tulevad kindlast ja ette teadaolevast hulgast. Näiteks:

  • Valgusfoor saab olla alati kas punane, kollane või roheline.
  • Mängukaartide mast saab olla kas ärtu, ruutu, risti või poti.
  • Veebipoe tellimuse staatus võib olla kas ootel, teele pandud või kohale toimetatud.

Nende väärtuste loetlemiseks on Javas olemas eriline andmetüüp, milleks on enum. enum (enumeration) tüüp on eriline andmetüüp, kus saab hoida selliseid eeldefineeritud konstante.

Konstandid ilma enum-ita

Enne enum-iga tutvumist, uurime kuidas eelnevalt mainitud veebipoe tellimuste näidet realiseerida näiteks sõnedega:

class Order {
private String status;

public Order() {
this.status = "PENDING";
}

public void ship() {
this.status = "SHIPPED";
}

public String getStatus() {
return status;
}
}

Selline lahendus töötaks, kuid:

  • Mis juhtub kui koodi tekib kirjaviga (nt: status = shippd, tekib staatus mida ei eksisteeri). Kuidas sellist olukorda lahendatakse?
  • Vead ilmnevad alles käitusajal, kompilaator antud juhul vigu ei tuvasta.
  • Kus on nimekiri kõikidest võimalikest staatustest?

Sõnede asemel võiks näiteks täisarve kasutada ja neid static final konstantidena hoida:

public static final int STATUS_PENDING = 0;
public static final int STATUS_SHIPPED = 1;
public static final int STATUS_DELIVERED = 2;

See ei lahendaks probleemi ära, vastupidiselt just tekitab segadust juurde.

Konstandid enum-iga

enum-iga on võimalik neid probleeme lahendada:

public enum OrderStatus {
PENDING,
SHIPPED,
DELIVERED
}

Deklareerides enum-tüübi nimega OrderStatus, luuakse nende väärtustega teatud konstantide kogumik, mille seast on võimalik valida neid kolme väärtust. Koodis saaks neid järgnevalt kasutada:

class Order {
private OrderStatus status;

public Order() {
this.status = OrderStatus.PENDING;
}

public void ship() {
this.status = OrderStatus.SHIPPED;
}

public void deliver() {
this.status = OrderStatus.DELIVERED;
}

public OrderStatus getStatus() {
return status;
}
}
Order order = new Order();
System.out.println(order.getStatus()); // PENDING

order.ship();
System.out.println(order.getStatus()); // SHIPPED

order.deliver();
System.out.println(order.getStatus()); // DELIVERED

Kuna tellimuse staatus on nüüd määratletud tüüp, tagab ka kompilaator korrektset kasutust. Kui proovida staatuseks määrata midagi (nt: CANCELLED), siis tekib kompileerimisviga.

enum-id switch-lausetes

enum-id töötavad eriti hästi switch-lausetes, luues puhta ja loetava juhtimisvoo:

public String getStatusMessage(OrderStatus status) {
switch (status) {
case PENDING:
return "Your order is being processed.";
case SHIPPED:
return "Your order is on the way.";
case DELIVERED:
return "Your order has been delivered.";
default:
return "Unknown status.";
}
}

Alates Java 14-st saab kasutada ka switch-väljendeid, mille kohta saate rohkem uurida siit.

Võrdlemine

Kahte enum-it võrreldakse omavahel == operaatoriga:

OrderStatus status = OrderStatus.SHIPPED;

if (status == OrderStatus.SHIPPED) {
System.out.println("On the way!");
}

enum-id on loomult konstantsed ja luuakse programmi käitusajal ainult ühel korral. See tähendab, et üle terve programmi eksisteerib ainult üks OrderStatus.SHIPPED objekt.

Meetodid enum-ites

Tehnilise poole pealt enum-id on erilist tüüpi klassid. Sellest tulenevalt on neil olemas paar kasulikku meetodit ning vajadusel saab neid ise ka juurde luua.

name() ja toString()

Vaikimisi tagastavad mõlemad meetodid konstandi nime sõnena:

OrderStatus status = OrderStatus.PENDING;
System.out.println(status.name()); // PENDING
System.out.println(status.toString()); // PENDING
System.out.println(status); // PENDING (toString is called implicitly)

values()

values() meetod tagastab kõik võimalikud antud konstandi väärtused massiivina:

for (OrderStatus status : OrderStatus.values()) {
System.out.println(status);
}
// PENDING
// SHIPPED
// DELIVERED

valueOf()

valueOf() meetodi abil on võimalik sõne teisendada vastavaks konstandiks. Sisend on tõstutundlik ning küsitav konstant peab ka reaalselt olemas olema, vastasel juhul tekib IllegalArgumentException erind.

OrderStatus status = OrderStatus.valueOf("SHIPPED");
System.out.println(status); // SHIPPED

ordinal()

ordinal() meetod tagastab mitmendal kohal konstant loendis on deklareerimise järjekorras:

System.out.println(OrderStatus.PENDING.ordinal());   // 0
System.out.println(OrderStatus.SHIPPED.ordinal()); // 1
System.out.println(OrderStatus.DELIVERED.ordinal()); // 2
hoiatus

Seda meetodit tasuks vältida loogikas või andmete salvestamisel. Kui keegi muudab enum-konstantide järjekorda või lisab keskele uue väärtuse, muutuvad ka ordinal() väärtused ning kogu nendest sõltuv kood võib katki minna.

Näiteks:

enum OrderStatus {
PENDING, // 0
SHIPPED, // 1
DELIVERED // 2
}

public class Main {
public static void main(String[] args) {
OrderStatus status = OrderStatus.SHIPPED;

int storedValue = status.ordinal(); // 1
System.out.println(storedValue);
}
}

Kui nüüd seda klassi muuta, muutuvad ka ordinal() väärtused:

enum OrderStatus {
PENDING, // 0
PROCESSING, // 1
SHIPPED, // 2
DELIVERED // 3
}

Kui andmebaasis oli salvestatud staatuse jaoks väärtus 1, tähendab see nüüd hoopis PROCESSING, mitte SHIPPED. Programm ise veateadet ei anna, kuid äriloogika võib vale olla.

enum koos väljade ja meetoditega

enum-itel võivad olla väljad, konstruktorid ja meetodid, täpselt nagu tavalistel klassidel. See on kasulik siis, kui iga konstant peab endaga kaasas kandma lisainfot:

public enum Planet {
MERCURY(3.303e+23, 2.4397e6),
VENUS(4.869e+24, 6.0518e6),
EARTH(5.976e+24, 6.37814e6),
MARS(6.421e+23, 3.3972e6);

private final double mass; // in kilograms
private final double radius; // in meters

// Enum constructor — always private (implicitly or explicitly)
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}

public double getMass() {
return mass;
}

public double getRadius() {
return radius;
}

// 6.67300E-11 is the universal gravitational constant
public double surfaceGravity() {
return 6.67300E-11 * mass / (radius * radius);
}
}
System.out.println(Planet.EARTH.surfaceGravity());   // ~9.802
System.out.println(Planet.MARS.surfaceGravity()); // ~3.711

Iga Planet konstant on omaette objekt koos oma massi ja raadiusega. Konstruktorit kutsutakse välja automaatselt enum-it luues.

oht

NB! Konstruktorid on enum-itel alati privaatsed ehk new Planet(...) ei ole võimalik mujal koodis kirjutada.