Pobieranie wyliczenia związanego z wartością int

89

Wcześniej moje wyliczenia LegNo były zdefiniowane po prostu jako:

NO_LEG, LEG_ONE, LEG_TWO

i dzwoniąc return LegNo.values()[i];, mogłem uzyskać wartość związaną z każdym wyliczeniem.

Ale teraz zdecydowałem, że chcę, aby LegNowyliczenie NO_LEGbyło wartością int -1 zamiast 0, więc zdecydowałem się użyć prywatnego konstruktora do zainicjowania i ustawienia jego wartości int

NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

private LegNo(final int leg) { legNo = leg; }

teraz jedyną rzeczą jest to, że ponieważ robię to w ten sposób, values()metoda nie będzie działać dla NO_LEGwyliczenia. Jak uzyskać wyliczenie skojarzone z int? Czy istnieje jakiś skuteczny sposób na zrobienie tego, inny niż użycie instrukcji przełączania case lub if-elseif-elseif

Widzę wiele pytań SO związanych z uzyskaniem wartości int z wyliczenia, ale jestem po odwrotnej kolejności.

L-Samuels
źródło

Odpowiedzi:

148

EDYCJA Sierpień 2018

Dziś zaimplementowałbym to w następujący sposób

public enum LegNo {
    NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

    private final int value;

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

    public static Optional<LegNo> valueOf(int value) {
        return Arrays.stream(values())
            .filter(legNo -> legNo.value == value)
            .findFirst();
    }
}

Będziesz musiał zachować mapowanie wewnątrz wyliczenia.

public enum LegNo {
    NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

    private int legNo;

    private static Map<Integer, LegNo> map = new HashMap<Integer, LegNo>();

    static {
        for (LegNo legEnum : LegNo.values()) {
            map.put(legEnum.legNo, legEnum);
        }
    }

    private LegNo(final int leg) { legNo = leg; }

    public static LegNo valueOf(int legNo) {
        return map.get(legNo);
    }
}

Blok statyczny zostanie wywołany tylko raz, więc praktycznie nie ma tutaj problemu z wydajnością.

EDYCJA: zmieniono nazwę metody na valueOfbardziej wbudowaną w inne klasy Java.

adarshr
źródło
przepraszam, nie jestem pewien, czy wszystko było jasne. chcę przekazać int i uzyskać wyliczenie z nim związane.
L-Samuels
@ L-Samuels Chyba nie przeczytałem poprawnie twojego pytania. Zobacz moją aktualizację.
adarshr
2
Wiem, że to wydaje się oczywiste, ale używać go tak: LegNo foo = LegNo.valueOf(2);. Poprzedni kod zwróci plik LegNo.LEG_TWO.
FirstOne
1
Należy zauważyć, że przekazanie nieprawidłowej wartości całkowitej (nie zamapowanej) zwróci nullzgodnie z oczekiwaniami przy użyciu HashMap.get : zwraca wartość, na którą mapowany jest określony klucz, lub wartość null, jeśli ta mapa nie zawiera mapowania dla klucza.
Pierwszy raz
Chociaż składnia strumienia jest zgrabna, warto zauważyć, że ma większą złożoność czasową niż statyczna mapa (która wprawdzie ma większe zużycie pamięci). Nie jest to problem dla 3 wartości, ale zdecydowanie problem, jeśli valueOf()wyliczasz 1000-składową wewnątrz innej pętli.
Patrick M
24

Możesz również dołączyć metodę statyczną do wyliczenia, która iteruje przez wszystkie elementy członkowskie i zwraca poprawną.

public enum LegNo {
   NO_LEG(-1),
   LEG_ONE(1),
   LEG_TWO(2);

   private int legIndex;

   private LegNo(int legIndex) { this.legIndex = legIndex; }

   public static LegNo getLeg(int legIndex) {
      for (LegNo l : LegNo.values()) {
          if (l.legIndex == legIndex) return l;
      }
      throw new IllegalArgumentException("Leg not found. Amputated?");
   }
}

Teraz, jeśli chcesz uzyskać wartość Enum jako liczbę całkowitą, po prostu użyj:

int myLegIndex = 1; //expected : LEG_ONE
LegNo myLeg = LegNo.getLeg(myLegIndex);
Mike Adler
źródło
Przypuszczam, że byłoby to bardziej eleganckie niż użycie instrukcji if else if. Ale biorąc pod uwagę, że było więcej pytań do przeszukania, strategia mapy sugerowana przez @adarshr byłaby lepsza. Chociaż głosuj na humor.
L-Samuels
1
Bardzo podoba mi się też strategia mapy. Zwłaszcza, gdy wyliczenie albo ma wiele wartości, albo trzeba je często wyszukiwać za pomocą tego mechanizmu. Jeśli jednak wyszukiwanie wartości według skojarzonego int jest stosunkowo rzadkim zjawiskiem lub masz wiele różnych wyliczeń z tymi samymi wymaganiami wyszukiwania, uważam, że mój sposób byłby bardziej przyjazny dla zasobów, ponieważ narzut mapy jest zapisywany. Ponadto uważam, że to mniej zagracony kod. Mam jednak kilka przypadków użycia, w których na pewno sam przełączę się na typ mapy.
Mike Adler
Nigdy nie należy wyprowadzać skojarzonej wartości wyliczenia na podstawie jej liczby porządkowej. Używanie mapy statycznej JEST zalecaną metodologią przez architektów Javy.
hfontanez
Pole legIndex pokrywa się z liczbą porządkową w tym przykładzie, ale może mieć dowolną wartość int. Nie jest wykonywane wyszukiwanie porządkowe. Ponadto podaj lub połącz powód, dla którego uważasz, że wyszukiwanie porządkowe jest złe.
Mike Adler
1
„Nie znaleziono nogi. Amputowany?”
Gnagy
17

odpowiedź adarshr dostosowana do Java 8:

import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toMap;

import java.util.Map;

public enum LegNo {
    NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

    private final int legNo;

    private final static Map<Integer, LegNo> map =
            stream(LegNo.values()).collect(toMap(leg -> leg.legNo, leg -> leg));

    private LegNo(final int leg) {
        legNo = leg;
    }

    public static LegNo valueOf(int legNo) {
        return map.get(legNo);
    }
}
Marcin
źródło
11

Możesz również uzyskać dostęp do wartości Enum odpowiadającej podanej wartości całkowitej, po prostu wywołując metodę values ​​() na wyliczeniu LegNo. Zwraca pole wyliczeń LegNo: LegNo.values()[0]; //returns LEG_NO LegNo.values()[1]; //returns LEG_ONE LegNo.values()[2]; //returns LEG_TWO

Nie dokładnie to, czego szukał, ale całkiem blisko i naprawdę bardzo proste. (Chociaż temat nie żyje, może się przydać komuś innemu).

Tadeas
źródło
6

Java 8 sposób z wartością domyślną:

public enum LegNo {
    NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

    private final int legNo;

    LegNo(int legNo) {
        this.legNo = legNo;
    }

    public static LegNo find(int legNo, Supplier<? extends LegNo> byDef) {
        return Arrays.asList(LegNo.values()).stream()
                .filter(e -> e.legNo == legNo).findFirst().orElseGet(byDef);
    }
}

zadzwonić:

LegNo res = LegNo.find(0, () -> LegNo.NO_LEG);

lub z wyjątkiem:

LegNo res = LegNo.find(0, () -> {
    throw new RuntimeException("No found");
});
Dmitry Sokolyuk
źródło
2
public enum LegNo {

  NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

  private final int code;

  LegNo(int code) {
    this.code = code;
    ReverseStorage.reverseMap.put(code, this);
  }

  public static Optional<LegNo> getByCode(int code) {
    return Optional.ofNullable(ReverseStorage.reverseMap.get(code));
  }

  private static final class ReverseStorage {
    private static final Map<Integer, LegNo> reverseMap = new LinkedHashMap<>();
  }
}
Andrey Lebedenko
źródło
1

Ponieważ wyliczenie zawiera tylko 3 elementy, najszybszym sposobem będzie użycie serii if else, tak jak zasugerowałeś.

edycja: odpowiedź, którą podał adarshr, lepiej nadaje się do ogólnych przypadków, w których istnieje wiele wartości wyliczeniowych, ale myślę, że jest to przesada dla twojego problemu.

DieterDP
źródło
Posiadanie Mapw kodzie z pewnością nie jest przesadą. Poza tym sprawia, że ​​metoda jest dużo czystsza niż spaghetti warunków if-else.
adarshr
Zgadzam się, że mapa jest lepsza, gdy masz dużo wartości wyliczeniowych, ale dla 3 wartości trzymałbym się konstrukcji if / else. Myślę, że to kwestia gustu.
DieterDP
Jakiekolwiek podejście wybierzemy, sygnatura metody public LegNo valueOf(int value)nie powinna być zmieniana. Jeśli-else można następnie zapisać w samym wyliczeniu. Jeśli if-else wyjdzie z wyliczenia, z pewnością stanie się niezbyt czystym kodem.
adarshr
1
Całkowicie się z tobą zgadzam :)
DieterDP
1
public enum LegNo {
    NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

    private int legNo;

    private LegNo(int leg) { legNo = leg; }

    public static LegNo valueOf(int legNo) {
        for (LegNo leg : LegNo.values()) {
            if (leg.legNo == legNo) return leg;
        }   
    }
}

assert LegNo.valueOf(2) == LegNo.LEG_TWO
assert LegNo.valueOf(3) == null
Tom B.
źródło
4
Dopuszczalne dla wyliczeń z <10 wartościami, ale całkowicie nieskuteczne dla dużej liczby wartości
wyliczeń