Enum

Java'da sabit değer kümelerini tip güvenli şekilde tanımlama.

Başlangıç 35 dk
EN

Enum

Java'da enum (enumeration), sabit bir değer kümesini tip güvenli şekilde tanımlamanı sağlayan özel bir referans türüdür. Java 5 ile dile eklendi ve o günden beri "bu değişken şu birkaç değerden birini alabilir" durumlarının standart çözümü hâline geldi.

Enum Nedir?

Bir değişkenin alabileceği değerler önceden bilinen, sınırlı bir kümeyse (örneğin haftanın günleri, bir siparişin durumları, bir kartın rengi), bunu string veya int sabitlerle değil enum ile modellemelisin. Java'da her enum, arka planda gizlice java.lang.Enum sınıfını extend eden bir sınıftır — yani enum sabitleri aslında birer nesnedir, sadece sayısı sabittir:

enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

Bu tek satırda Java, arka planda yedi tane Day nesnesi (MONDAY, TUESDAY, ...) oluşturur ve bunları static final alanlar olarak saklar. Bu yüzden enum sabitlerini == ile karşılaştırmak tamamen güvenlidir — equals()'a ihtiyaç yoktur.

Enum vs String

Enum kullanmak, string sabitlere göre derleme zamanında tip güvenliği sağlar. Şu iki yaklaşımı karşılaştır:

// String ile — derleyici seni korumaz
void setStatus(String status) { ... }
setStatus("APPROVEDD"); // yazım hatası, ama kod derlenir ve sorun çalışma zamanında çıkar

// Enum ile — derleyici seni korur
void setStatus(OrderStatus status) { ... }
setStatus(OrderStatus.APPROVEDD); // derleme hatası — böyle bir sabit yok

String ile yazım hatası ancak çalışma zamanında (belki de üretimde) fark edilir; enum ile aynı hata derleme anında yakalanır. Ayrıca IDE'ler enum kullanırken otomatik tamamlama ve "olası tüm değerler" listesi sunabilir — string ile bu mümkün değildir.

Temel Enum Kullanımı

En basit haliyle bir enum şöyle tanımlanır:

enum Gender {
    MALE,
    FEMALE
}

Constructor

Enum sabitleri birer nesne olduğu için constructor'ları da olabilir — her sabit oluşturulurken kendi parametrelerini geçebilir:

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

    private final double massKg;
    private final double radiusM;

    // The constructor must always be private (or package-private) — enum
    // constants are only ever "constructed" on the definition line above,
    // by the enum itself.
    Planet(double massKg, double radiusM) {
        this.massKg = massKg;
        this.radiusM = radiusM;
    }

    public double getMassKg() {
        return massKg;
    }

    public double getRadiusM() {
        return radiusM;
    }

    public double surfaceGravity() {
        final double gravitationalConstant = 6.67300E-11;
        return gravitationalConstant * massKg / (radiusM * radiusM);
    }
}

Burada her gezegen sabiti (MERCURY, VENUS, EARTH), enum'un tanımlandığı satırda kendi kütle ve yarıçap değerleriyle "inşa ediliyor". Bu constructor çağrısı, sınıf ilk yüklendiğinde (class loading anında) bir kereye mahsus çalışır — her sabit için tek bir nesne örneği vardır, tıpkı bir Singleton gibi.

Alanlar (Fields)

Constructor'a geçirilen değerler genellikle private final alanlarda saklanır — bu, her enum sabitinin kendine özgü, değişmez (immutable) verisi olduğu anlamına gelir. Yukarıdaki Planet örneğinde massKg ve radiusM tam olarak bu şekilde tanımlandı; dışarıya ise yalnızca getter metotlarıyla açıldı:

class PlanetUsageExample {
    public static void main(String[] args) {
        Planet earth = Planet.EARTH;

        System.out.println("Kütle: " + earth.getMassKg() + " kg");
        System.out.printf("Yüzey yerçekimi: %.2f m/s^2%n", earth.surfaceGravity());
    }
}

Metotlar

Enum'lar sıradan sınıflar gibi instance metotları barındırabilir. Planet örneğindeki surfaceGravity() buna zaten bir örnekti. Constructor'a hiç ihtiyaç duymadan, sabit değerlere göre karar veren bir metot da ekleyebiliriz:

enum DayWithMethod {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;

    boolean isWeekend() {
        return this == SATURDAY || this == SUNDAY;
    }
}
for (DayWithMethod day : DayWithMethod.values()) {
    System.out.println(day + " hafta sonu mu? " + day.isWeekend());
}

values()

values(), derleyicinin her enum için otomatik ürettiği statik bir metottur; tüm sabitleri, tanımlandıkları sırayla bir dizi (array) olarak döner — yukarıdaki döngüde zaten kullandık:

DayWithMethod[] days = DayWithMethod.values();
System.out.println(days.length); // 7

valueOf()

valueOf(String), verilen ismi tam olarak eşleşen sabiti döner — eşleşme bulunamazsa IllegalArgumentException fırlatır:

DayWithMethod day = DayWithMethod.valueOf("MONDAY"); // MONDAY
DayWithMethod hata = DayWithMethod.valueOf("monday"); // IllegalArgumentException! Büyük/küçük harfe duyarlı

Kullanıcıdan gelen serbest metni doğrudan valueOf()'a vermeden önce mutlaka doğrula ya da çağrıyı try/catch ile sarmala.

name()

name(), sabitin kaynak kodda yazıldığı ismi birebir döner — toString() override edilmiş olsa bile name() her zaman orijinal ismi verir:

System.out.println(DayWithMethod.MONDAY.name()); // "MONDAY"

ordinal()

ordinal(), sabitin tanımlandığı sıradaki pozisyonunu (0'dan başlayarak) döner:

System.out.println(DayWithMethod.MONDAY.ordinal()); // 0
System.out.println(DayWithMethod.SUNDAY.ordinal());  // 6

Bu bölümün en başında verdiğimiz uyarıyı burada tekrar hatırlatmakta fayda var: ordinal() değerini asla kalıcı veri (veritabanı, dosya, API sözleşmesi) olarak saklama — enum tanımına yeni bir sabit eklemek ya da sırayı değiştirmek, mevcut verinin anlamını sessizce bozar.

switch ile Kullanım

Enum'lar, switch ifadeleriyle doğal olarak çok iyi çalışır. Java 21 ile gelen modern switch söz dizimiyle:

String mesaj = switch (DayWithMethod.SATURDAY) {
    case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Çalışma günü";
    case SATURDAY, SUNDAY -> "Hafta sonu";
};

Arayüz (Interface) İmplementasyonu

Bir enum, sınıf gibi extend edemez (zaten örtük olarak Enum'ı extend eder) ama istediğin kadar arayüz implement edebilir. Bu, enum sabitlerine ortak bir "sözleşme" kazandırmanın güzel bir yoludur:

interface Describable {
    String describe();
}

enum TrafficLight implements Describable {
    RED, YELLOW, GREEN;

    @Override
    public String describe() {
        return "Trafik ışığı: " + name();
    }
}

Burada her TrafficLight sabiti aynı describe() implementasyonunu paylaşıyor. Bir sonraki bölümde, her sabitin kendi implementasyonunu nasıl yazabileceğini göreceğiz.

Soyut Metot (Abstract Method) ve Sabite Özel Gövde

Enum'ın en güçlü özelliklerinden biri, her sabitin bir metodun kendi implementasyonunu yazabilmesidir — buna "constant-specific method body" denir:

enum Operation {
    PLUS("+") {
        @Override
        public double apply(double a, double b) {
            return a + b;
        }
    },
    MINUS("-") {
        @Override
        public double apply(double a, double b) {
            return a - b;
        }
    },
    TIMES("*") {
        @Override
        public double apply(double a, double b) {
            return a * b;
        }
    };

    private final String symbol;

    Operation(String symbol) {
        this.symbol = symbol;
    }

    public abstract double apply(double a, double b);

    @Override
    public String toString() {
        return symbol;
    }
}

EnumSet

EnumSet, yalnızca enum sabitleri için tasarlanmış, bit vektörü tabanlı çok verimli bir Set implementasyonudur — HashSet<MyEnum> kullanmaktan neredeyse her zaman daha hızlı ve daha az bellek harcar:

import java.util.EnumSet;

class EnumSetExample {
    public static void main(String[] args) {
        EnumSet<DayWithMethod> weekend = EnumSet.of(DayWithMethod.SATURDAY, DayWithMethod.SUNDAY);
        EnumSet<DayWithMethod> weekdays = EnumSet.complementOf(weekend);

        System.out.println("Hafta sonu: " + weekend);
        System.out.println("Hafta içi: " + weekdays);
    }
}

EnumMap

EnumMap<K,V>, anahtarları enum olan bir Map implementasyonudur; içeride bir HashMap yerine, sabitlerin ordinal() değerine göre indekslenmiş bir dizi kullanır — bu da onu HashMap<MyEnum, V>'den belirgin şekilde hızlı yapar:

import java.util.EnumMap;
import java.util.Map;

class EnumMapExample {
    public static void main(String[] args) {
        EnumMap<DayWithMethod, String> plan = new EnumMap<>(DayWithMethod.class);
        plan.put(DayWithMethod.MONDAY, "Spring Boot çalış");
        plan.put(DayWithMethod.SATURDAY, "Dinlen");

        for (Map.Entry<DayWithMethod, String> entry : plan.entrySet()) {
            System.out.println(entry.getKey() + " -> " + entry.getValue());
        }
    }
}

Singleton Deseni

Effective Java kitabında Joshua Bloch'un önerdiği gibi, tek elemanlı bir enum, Java'da bir Singleton yazmanın en güvenli yoludur:

enum ConfigurationManager {
    INSTANCE;

    private String environment = "production";

    public String getEnvironment() {
        return environment;
    }

    public void setEnvironment(String environment) {
        this.environment = environment;
    }
}

Kullanım: ConfigurationManager.INSTANCE.getEnvironment().

Strategy Pattern

Az önceki Operation örneği aslında bir Strategy Pattern uygulamasıydı: her sabit, aynı arayüzün (burada apply metodu) farklı bir "stratejisini" taşıyor. Bunu daha iş odaklı bir örnekle pekiştirelim:

enum ShippingStrategy {
    STANDARD {
        @Override
        public double calculate(double weightKg) {
            return weightKg * 2.5;
        }
    },
    EXPRESS {
        @Override
        public double calculate(double weightKg) {
            return weightKg * 5.0 + 10;
        }
    },
    SAME_DAY {
        @Override
        public double calculate(double weightKg) {
            return weightKg * 8.0 + 25;
        }
    };

    public abstract double calculate(double weightKg);
}

Klasik Strategy Pattern'de her strateji için ayrı bir sınıf ve bunları birbirine bağlayan bir Factory yazman gerekirdi; enum ile hem stratejiler hem de "hangi strateji hangi anahtara karşılık geliyor" eşlemesi tek bir yapıda, ekstra kod olmadan birleşiyor.

Gerçek Dünya Örnekleri

Enum'ların üretim kodunda en sık göründüğü yerlerden biri durum makineleridir (state machine). Bir siparişin geçerli durum geçişlerini enum ile modelleyelim:

import java.util.EnumSet;
import java.util.Set;

enum OrderStatus {
    PENDING,
    PAID,
    SHIPPED,
    DELIVERED,
    CANCELLED;

    private static final Set<OrderStatus> TERMINAL = EnumSet.of(DELIVERED, CANCELLED);

    public boolean isTerminal() {
        return TERMINAL.contains(this);
    }

    public boolean canTransitionTo(OrderStatus next) {
        return switch (this) {
            case PENDING -> next == PAID || next == CANCELLED;
            case PAID -> next == SHIPPED || next == CANCELLED;
            case SHIPPED -> next == DELIVERED;
            case DELIVERED, CANCELLED -> false;
        };
    }
}

Bu tür bir yapı, "hangi durumdan hangi duruma geçilebilir" kuralını dağınık if/else bloklarına değil, enum'ın kendisine yerleştirir — kural tek bir yerde yaşar ve derleyici, olası tüm durumların (switch exhaustiveness) ele alındığını garanti eder.

Diğer yaygın gerçek dünya kullanımları: HTTP durum kodu kategorileri, kullanıcı rolleri/yetkileri, ödeme yöntemleri, log seviyeleri (DEBUG, INFO, WARN, ERROR) — hepsi aynı desenin farklı uygulamalarıdır.

Mülakat Soruları

Enum, int sabitlere göre ne gibi avantajlar sağlar? Tip güvenliği (derleme zamanı kontrolü), okunabilirlik, switch ile exhaustiveness kontrolü ve values()/valueOf() gibi hazır API.

Bir enum başka bir sınıfı extend edebilir mi? Hayır — her enum örtük olarak java.lang.Enum'ı extend eder ve Java tek kalıtımı desteklediği için başka bir sınıfı extend edemez. Ancak istediği kadar arayüz implement edebilir.

== ile equals() enum sabitleri için aynı sonucu verir mi? Evet. Her enum sabiti JVM'de tek bir örnek olarak var olduğundan, == ile referans karşılaştırması equals() ile aynı sonucu üretir. Yine de gelenek olarak equals() tercih edilir.

ordinal()'ı neden veri saklamak için kullanmamalıyız? Çünkü sabitlerin sırası (dolayısıyla ordinal değerleri) kaynak kodda değişebilir; bu durumda önceden kaydedilmiş bir ordinal değeri artık yanlış sabiti işaret edebilir. Bunun yerine name() (ya da özel bir kod alanı) sakla.

Enum'lar Serializable mıdır? Evet, java.lang.Enum zaten Serializable'ı implement eder ve JVM enum serialization'ını özel olarak ele alır (sabit, name() üzerinden yeniden çözülür) — bu yüzden özel bir readObject/writeObject yazmana gerek kalmaz, hatta yazman önerilmez.

Bir enum clone()'lanabilir mi? Hayır. Enum.clone() CloneNotSupportedException fırlatır — çünkü her sabitin JVM'de tek bir örneği olması garanti edilmelidir; klonlama bu garantiyi bozar.