Konwertuj wartość całkowitą na pasującą wartość Java Enum

86

Mam takie wyliczenie:

public enum PcapLinkType {
  DLT_NULL(0)
  DLT_EN10MB(1)
  DLT_EN3MB(2),
  DLT_AX25(3),
  /*snip, 200 more enums, not always consecutive.*/
  DLT_UNKNOWN(-1);
    private final int value;   

    PcapLinkType(int value) {
        this.value= value;
    }
}

Teraz otrzymuję int z zewnętrznego wejścia i chcę dopasować dane wejściowe - zgłoszenie wyjątku, jeśli wartość nie istnieje, jest w porządku, ale najlepiej byłoby, gdyby tak było DLT_UNKNOWN .

int val = in.readInt();
PcapLinkType type = ???; /*convert val to a PcapLinkType */
Lyke
źródło

Odpowiedzi:

105

Należy to zrobić ręcznie, dodając statyczną mapę w klasie, która odwzorowuje liczby całkowite na wyliczenia, na przykład

private static final Map<Integer, PcapLinkType> intToTypeMap = new HashMap<Integer, PcapLinkType>();
static {
    for (PcapLinkType type : PcapLinkType.values()) {
        intToTypeMap.put(type.value, type);
    }
}

public static PcapLinkType fromInt(int i) {
    PcapLinkType type = intToTypeMap.get(Integer.valueOf(i));
    if (type == null) 
        return PcapLinkType.DLT_UNKNOWN;
    return type;
}
MeBigFatGuy
źródło
1
zaktualizowany o rekomendacje od dty, co było dobrym pomysłem.
MeBigFatGuy
Mam nadzieję, że najpierw przepuściłeś mój kod przez kompilator ... Właśnie to wymyśliłem. Wiem, że ta technika działa - użyłem jej wczoraj. Ale kod jest na innym komputerze, a ten nie ma moich narzędzi programistycznych.
dty
1
allOf jest dostępne tylko dla zestawów
MeBigFatGuy
1
Ponadto EnumMapużywa wyliczeń jako kluczy. W tym przypadku OP chce wyliczeń jako wartości.
dty
8
Wydaje się, że jest to dużo niepotrzebnych kosztów ogólnych. Ci, którzy faktycznie potrzebują tego typu operacji, prawdopodobnie potrzebują wysokiej wydajności, ponieważ piszą / odczytują ze strumieni / gniazd, w takim przypadku buforowanie values()(jeśli wartości wyliczenia są sekwencyjne) lub prosta switchinstrukcja z łatwością pokonałyby tę metodę . Jeśli masz w swoim tylko kilka wpisów Enum, nie ma większego sensu dodawanie narzutu HashMap tylko po to, aby nie musieć aktualizować switchinstrukcji. Ta metoda może wydawać się bardziej elegancka, ale jest też marnotrawstwem.
zmiażdżyć
30

Istnieje metoda statyczna, values()która jest udokumentowana, ale nie tam, gdzie można się jej spodziewać: http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html

enum MyEnum {
    FIRST, SECOND, THIRD;
    private static MyEnum[] allValues = values();
    public static MyEnum fromOrdinal(int n) {return allValues[n];}
}

W zasadzie możesz użyć just values()[i], ale istnieją pogłoski, że values()utworzy kopię tablicy za każdym razem, gdy zostanie ona wywołana.

18446744073709551615
źródło
9
Według Joshua Blocha (Effective Java Book) : Nigdy nie wyprowadzaj wartości związanej z wyliczeniem z jego liczby porządkowej; Twoja implementacja nie powinna opierać się na kolejności wyliczeń.
stevo.mit
4
Wdrażanie czego? Jeśli zaimplementujemy jakiś algorytm, implementacja nie powinna opierać się na kolejności wyliczeń, chyba że jest to udokumentowane. Kiedy implementujemy samo wyliczenie, można używać takich szczegółów implementacji, tak samo, jak można używać metod klasy prywatnej.
18446744073709551615
1
Nie zgadzaj się. Wierzę, że nigdy nie ma na myśli niezależnie od dokumentacji. Nie należy używać liczb porządkowych, nawet jeśli samodzielnie implementujesz wyliczenie. To nieprzyjemny zapach i jest podatny na błędy. Nie jestem ekspertem, ale nie
kłóciłbym się
4
@ stevo.mit rzuć okiem na nowe wyliczenie java.time.Month w Javie 8. Metoda statyczna Month.of (int) robi dokładnie to, co Joshua Bloch powiedział, że „nigdy” nie powinno się robić. Zwraca miesiąc na podstawie jego liczby porządkowej.
Klitos Kyriacou
1
@ stevo.mit Istnieją uporządkowane wyliczenia i nieuporządkowane wyliczenia . (A także wyliczenia z maską bitową .) Nie można mówić o nich tylko jako o „wyliczeniach”. Decyzja o tym, jakich środków wyrazowych użyć, musi być oparta na poziomie abstrakcji , nad którym pracujesz. Rzeczywiście niewłaściwe jest stosowanie szczegółów implementacji (środki wyrazowe z niższego poziomu) lub założeń użycia (środki wyrazowe z poziomu wyższego). Co do „ nigdy ”, w ludzkich językach nigdy nie znaczy nigdy, ponieważ zawsze jest jakiś kontekst. (Zwykle w programowaniu aplikacji, nigdy ...) BTW, programering.com/a/MzNxQjMwATM.html
18446744073709551615
14

Będziesz musiał utworzyć nową statyczną metodę, w której iterujesz PcapLinkType.values ​​() i porównujesz:

public static PcapLinkType forCode(int code) {
    for (PcapLinkType typе : PcapLinkType.values()) {
        if (type.getValue() == code) {
            return type;
        }
    }
    return null;
 }

Byłoby dobrze, gdyby było wywoływane rzadko. Jeśli jest wywoływany często, spójrz na Mapoptymalizację sugerowaną przez innych.

Bozho
źródło
4
Może być kosztowne, jeśli dzwoni się dużo. Tworzenie mapy statycznej prawdopodobnie zapewni lepszy zamortyzowany koszt.
dty
@dty o (n) z n = 200 - nie sądzę, że to problem
Bozho
7
To totalnie absurdalne stwierdzenie bez poczucia częstotliwości wywołania. Jeśli zadzwoni raz, dobrze. Jeśli jest wywoływana dla każdego pakietu przechodzącego przez sieć 10Ge, bardzo ważne jest przyspieszenie algorytmu 200x. Dlatego też określiłem moje stwierdzenie
słowami
10

Możesz zrobić coś takiego, aby automatycznie zarejestrować je wszystkie w kolekcji, za pomocą której można łatwo przekonwertować liczby całkowite na odpowiednie wyliczenie. (Przy okazji, dodawanie ich do mapy w konstruktorze wyliczenia jest niedozwolone . Fajnie jest uczyć się nowych rzeczy nawet po wielu latach używania Javy. :)

public enum PcapLinkType {
    DLT_NULL(0),
    DLT_EN10MB(1),
    DLT_EN3MB(2),
    DLT_AX25(3),
    /*snip, 200 more enums, not always consecutive.*/
    DLT_UNKNOWN(-1);

    private static final Map<Integer, PcapLinkType> typesByValue = new HashMap<Integer, PcapLinkType>();

    static {
        for (PcapLinkType type : PcapLinkType.values()) {
            typesByValue.put(type.value, type);
        }
    }

    private final int value;

    private PcapLinkType(int value) {
        this.value = value;
    }

    public static PcapLinkType forValue(int value) {
        return typesByValue.get(value);
    }
}
Esko Luontola
źródło
1
To właśnie otrzymujesz za podwójne sprawdzenie odpowiedzi przed wysłaniem. ;)
Esko Luontola
10

jeśli masz takie enum

public enum PcapLinkType {
  DLT_NULL(0)
  DLT_EN10MB(1)
  DLT_EN3MB(2),
  DLT_AX25(3),
  DLT_UNKNOWN(-1);

    private final int value;   

    PcapLinkType(int value) {
        this.value= value;
    }
}

wtedy możesz go używać jak

PcapLinkType type = PcapLinkType.values()[1]; /*convert val to a PcapLinkType */
Jack Gajanan
źródło
przegapiłeś komentarz / * snip, 200 kolejnych wyliczeń, nie zawsze kolejne. * /
MeBigFatGuy
na wypadek gdyby wartość wyliczenia była przechodnia od zera, jest to zła praktyka
cuasodayleo
4

Jak mówi @MeBigFatGuy, z wyjątkiem tego, że możesz sprawić, by static {...}blok używał pętli nad values()kolekcją:

static {
    for (PcapLinkType type : PcapLinkType.values()) {
        intToTypeMap.put(type.getValue(), type);
    }
}
dty
źródło
4

Wiem, że to pytanie ma już kilka lat, ale ponieważ Java 8 w międzyczasie nas przyniosła Optional, pomyślałem, że zaproponuję rozwiązanie, które go wykorzysta ( Streami Collectors):

public enum PcapLinkType {
  DLT_NULL(0),
  DLT_EN3MB(2),
  DLT_AX25(3),
  /*snip, 200 more enums, not always consecutive.*/
  // DLT_UNKNOWN(-1); // <--- NO LONGER NEEDED

  private final int value;
  private PcapLinkType(int value) { this.value = value; }

  private static final Map<Integer, PcapLinkType> map;
  static {
    map = Arrays.stream(values())
        .collect(Collectors.toMap(e -> e.value, e -> e));
  }

  public static Optional<PcapLinkType> fromInt(int value) {
    return Optional.ofNullable(map.get(value));
  }
}

Optionaljest jak null: reprezentuje przypadek, gdy nie ma (prawidłowej) wartości. Ale jest to bardziej bezpieczna dla typu alternatywa nulllub wartość domyślna, na przykład DLT_UNKNOWNponieważ można zapomnieć o sprawdzeniu przypadków nulllub DLT_UNKNOWN. Obie są poprawnymi PcapLinkTypewartościami! Natomiast nie można przypisać Optional<PcapLinkType>wartości do zmiennej typu PcapLinkType. Optionalpowoduje, że najpierw sprawdzasz prawidłową wartość.

Oczywiście, jeśli chcesz zachować DLT_UNKNOWNzgodność ze starszymi wersjami lub z jakiegokolwiek innego powodu, nadal możesz użyć Optionalnawet w tym przypadku, używając, orElse()aby określić to jako wartość domyślną:

public enum PcapLinkType {
  DLT_NULL(0),
  DLT_EN3MB(2),
  DLT_AX25(3),
  /*snip, 200 more enums, not always consecutive.*/
  DLT_UNKNOWN(-1);

  private final int value;
  private PcapLinkType(int value) { this.value = value; }

  private static final Map<Integer, PcapLinkType> map;
  static {
    map = Arrays.stream(values())
        .collect(Collectors.toMap(e -> e.value, e -> e));
  }

  public static PcapLinkType fromInt(int value) {
    return Optional.ofNullable(map.get(value)).orElse(DLT_UNKNOWN);
  }
}
Brad Collins
źródło
3

Możesz dodać statyczną metodę w swoim wyliczeniu, która akceptuje an intjako parametr i zwraca PcapLinkType.

public static PcapLinkType of(int linkType) {

    switch (linkType) {
        case -1: return DLT_UNKNOWN
        case 0: return DLT_NULL;

        //ETC....

        default: return null;

    }
}
Buhake Sindi
źródło
Lepiej nie zapomnij dodać wpisu do tej switchinstrukcji, jeśli dodasz nowe wyliczenie. Nie idealne, IMHO.
dty
1
@dty Więc myślisz, że narzut HashMap przeważa nad potrzebą dodania nowego przypadku do instrukcji switch?
zmiażdżyć
1
Myślę, że wolałbym napisać kod, który pomoże mi uniknąć błędów i dlatego jest bardziej prawdopodobne, że będzie poprawny, zanim skupię się na mikro-wydajności wyszukiwania skrótu.
dty
3

Oto czego używam:

public enum Quality {ENOUGH,BETTER,BEST;
                     private static final int amount = EnumSet.allOf(Quality.class).size();
                     private static Quality[] val = new Quality[amount];
                     static{ for(Quality q:EnumSet.allOf(Quality.class)){ val[q.ordinal()]=q; } }
                     public static Quality fromInt(int i) { return val[i]; }
                     public Quality next() { return fromInt((ordinal()+1)%amount); }
                    }
18446744073709551615
źródło
Używanie liczby porządkowej zostało uznane za złą praktykę, ogólnie rzecz biorąc, lepiej jest unikać.
Rafael
1
static final PcapLinkType[] values  = { DLT_NULL, DLT_EN10MB, DLT_EN3MB, null ...}    

...

public static PcapLinkType  getPcapLinkTypeForInt(int num){    
    try{    
       return values[int];    
    }catch(ArrayIndexOutOfBoundsException e){    
       return DLT_UKNOWN;    
    }    
}    
nsfyn55
źródło
1
Drogie, jeśli dużo się nazywa. Musisz pamiętać, aby zaktualizować tablicę (dlaczego w ogóle ją masz, skoro wyliczenia definiują .values()metodę?).
dty
@dty czy to try / catch? Myślę, że uczciwiej byłoby powiedzieć, że jest drogi, jeśli wiele wartości należy do kategorii DLT_UNKNOWN.
nsfyn55
1
Jestem naprawdę zaskoczony, widząc odrzucone rozwiązanie dla tablic i odrzucone rozwiązanie map. Nie podoba mi się tutaj --int, ale to oczywiście literówka.
18446744073709551615
Widzę: chcą nullzamiast DLT_UKNOWN:)
18446744073709551615
1
Dlaczego nie static final values[] = PcapLinkType.values()?
18446744073709551615
0

Nie ma możliwości eleganckiej obsługi typów wyliczeniowych opartych na liczbach całkowitych. Możesz pomyśleć o użyciu wyliczenia opartego na ciągach zamiast rozwiązania. Nie zawsze jest to preferowany sposób, ale nadal istnieje.

public enum Port {
  /**
   * The default port for the push server.
   */
  DEFAULT("443"),

  /**
   * The alternative port that can be used to bypass firewall checks
   * made to the default <i>HTTPS</i> port.
   */
  ALTERNATIVE("2197");

  private final String portString;

  Port(final String portString) {
    this.portString = portString;
  }

  /**
   * Returns the port for given {@link Port} enumeration value.
   * @return The port of the push server host.
   */
  public Integer toInteger() {
    return Integer.parseInt(portString);
  }
}
Buğra Ekuklu
źródło