Jak wyszukać wyliczenie Java na podstawie jego wartości ciągu?

164

Chciałbym wyszukać wyliczenie na podstawie jego wartości ciągu (lub ewentualnie dowolnej innej wartości). Wypróbowałem następujący kod, ale nie zezwala on na statyczne w inicjatorach. Czy istnieje prosty sposób?

public enum Verbosity {

    BRIEF, NORMAL, FULL;

    private static Map<String, Verbosity> stringMap = new HashMap<String, Verbosity>();

    private Verbosity() {
        stringMap.put(this.toString(), this);
    }

    public static Verbosity getVerbosity(String key) {
        return stringMap.get(key);
    }
};
peter.murray.rust
źródło
IIRC, który daje NPE, ponieważ statyczna inicjalizacja jest wykonywana z góry na dół (tj. Stałe wyliczeniowe na górze są konstruowane przed rozpoczęciem stringMapinicjalizacji). Typowym rozwiązaniem jest użycie klasy zagnieżdżonej.
Tom Hawtin - tackline
Dziękuję wszystkim za tak szybką odpowiedź. (FWIW Nie uważam, aby Sun Javadocs były bardzo przydatne w przypadku tego problemu).
peter.murray.rust
To naprawdę problem językowy niż problem biblioteczny. Jednak myślę, że dokumentacja API jest czytana częściej niż JLS (chociaż być może nie przez projektantów języka), więc takie rzeczy powinny być prawdopodobnie bardziej widoczne w dokumentacji java.lang.
Tom Hawtin - tackline

Odpowiedzi:

241

Użyj valueOfmetody, która jest tworzona automatycznie dla każdego Enum.

Verbosity.valueOf("BRIEF") == Verbosity.BRIEF

Dla dowolnych wartości zacznij od:

public static Verbosity findByAbbr(String abbr){
    for(Verbosity v : values()){
        if( v.abbr().equals(abbr)){
            return v;
        }
    }
    return null;
}

Przejdź później do implementacji mapy tylko wtedy, gdy Twój profiler Ci to zaleci.

Wiem, że iteruje po wszystkich wartościach, ale przy tylko 3 wartościach wyliczeniowych nie jest to warte żadnego innego wysiłku, w rzeczywistości, jeśli nie masz wielu wartości, nie zawracałbym sobie głowy mapą, będzie wystarczająco szybko.

Gareth Davis
źródło
dzięki - i mogę użyć konwersji wielkości liter, jeśli wiem, że wartości są nadal różne
peter.murray.rust
Możesz uzyskać bezpośredni dostęp do elementu klasy „abbr”, więc zamiast „v.abbr ()” możesz użyć „v.abbr.equals ...”.
Amio.io
7
Również w przypadku dowolnych wartości (drugi przypadek) rozważ wyrzucenie IllegalArgumentExceptionwartości null zamiast zwracania wartości null ( tj. Gdy nie zostanie znalezione żadne dopasowanie) - Enum.valueOf(..)zresztą tak robi to JDK .
Priidu Neemre
135

Jesteś blisko. W przypadku dowolnych wartości spróbuj czegoś takiego:

public enum Day { 

    MONDAY("M"), TUESDAY("T"), WEDNESDAY("W"),
    THURSDAY("R"), FRIDAY("F"), SATURDAY("Sa"), SUNDAY("Su"), ;

    private final String abbreviation;

    // Reverse-lookup map for getting a day from an abbreviation
    private static final Map<String, Day> lookup = new HashMap<String, Day>();

    static {
        for (Day d : Day.values()) {
            lookup.put(d.getAbbreviation(), d);
        }
    }

    private Day(String abbreviation) {
        this.abbreviation = abbreviation;
    }

    public String getAbbreviation() {
        return abbreviation;
    }

    public static Day get(String abbreviation) {
        return lookup.get(abbreviation);
    }
}
Lyle
źródło
4
Z powodu problemów z ładowaniem klas ta metoda nie będzie działać niezawodnie. Nie polecam tego i widziałem, że regularnie zawodzi, ponieważ mapa wyszukiwania nie jest gotowa przed uzyskaniem dostępu. Musisz umieścić "wyszukiwanie" poza wyliczeniem lub w innej klasie, aby zostało załadowane jako pierwsze.
Adam Gent,
7
@Adam Gent: Poniżej znajduje się link do JLS, gdzie ta dokładna konstrukcja jest podana jako przykład. Mówi, że inicjalizacja statyczna odbywa się od góry do dołu: docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#d5e12267
Selena
3
Tak, nowoczesna java, taka jak java 8, naprawiła model pamięci. Biorąc to pod uwagę, agenty hot swap, takie jak jrebel, nadal się pogubią, jeśli osadzisz inicjalizację z powodu cyklicznych odwołań.
Adam Gent,
@Adam Gent: Hmmm. Codziennie uczę się czegoś nowego. [Cicho wymykam się, żeby refaktoryzować moje wyliczenia z singletonami bootstrap ...] Przepraszam, jeśli jest to głupie pytanie, ale czy jest to głównie problem ze statyczną inicjalizacją?
Selena
Nigdy nie widziałem problemów z niezainicjalizowaniem statycznego bloku kodu, ale korzystałem z Javy 8 dopiero od kiedy zacząłem w Javie, w 2015 roku. Właśnie wykonałem mały test porównawczy przy użyciu JMH 1.22 i użycie a HashMap<>do przechowywania wyliczeń okazało się ponad 40% szybciej niż pętla podłogowa.
cbaldan
21

z Javą 8 możesz osiągnąć w ten sposób:

public static Verbosity findByAbbr(final String abbr){
    return Arrays.stream(values()).filter(value -> value.abbr().equals(abbr)).findFirst().orElse(null);
}
Lam Le
źródło
Rzuciłbym wyjątek zamiast wracać null. Ale zależy to również od przypadku użycia
alexander
12

Odpowiedź @ Lyle jest raczej niebezpieczna i widziałem, że nie działa, szczególnie jeśli uczynisz enum statyczną klasą wewnętrzną. Zamiast tego użyłem czegoś takiego, który załaduje mapy BootstrapSingleton przed wyliczeniami.

Edycja tego nie powinna już stanowić problemu w przypadku nowoczesnych maszyn JVM (JVM 1.6 lub nowszych), ale myślę, że nadal występują problemy z JRebel, ale nie miałem okazji go ponownie przetestować .

Załaduj mnie najpierw:

   public final class BootstrapSingleton {

        // Reverse-lookup map for getting a day from an abbreviation
        public static final Map<String, Day> lookup = new HashMap<String, Day>();
   }

Teraz załaduj go do konstruktora wyliczenia:

   public enum Day { 
        MONDAY("M"), TUESDAY("T"), WEDNESDAY("W"),
        THURSDAY("R"), FRIDAY("F"), SATURDAY("Sa"), SUNDAY("Su"), ;

        private final String abbreviation;

        private Day(String abbreviation) {
            this.abbreviation = abbreviation;
            BootstrapSingleton.lookup.put(abbreviation, this);
        }

        public String getAbbreviation() {
            return abbreviation;
        }

        public static Day get(String abbreviation) {
            return lookup.get(abbreviation);
        }
    }

Jeśli masz wewnętrzne wyliczenie, możesz po prostu zdefiniować Mapę powyżej definicji wyliczenia i to (teoretycznie) powinno zostać załadowane wcześniej.

Adam Gent
źródło
3
Trzeba zdefiniować „niebezpieczne” i dlaczego tak ważna jest implementacja klasy wewnętrznej. Jest to bardziej problem z definicją statyczną od góry do dołu, ale istnieje działający kod w innej pokrewnej odpowiedzi z odsyłaczem do tego pytania, który używa statycznej mapy w samej instancji Enum z metodą „get” z wartości zdefiniowanej przez użytkownika do Typ wyliczenia. stackoverflow.com/questions/604424/lookup-enum-by-string-value
Darrell Teague
2
@DarrellTeague - pierwotny problem był spowodowany przez stare maszyny JVM (starsze niż 1.6) lub inne maszyny JVM (IBM) lub zamiany hotcode (JRebel). Problem nie powinien mieć miejsca w nowoczesnych maszynach JVM, więc mogę po prostu usunąć odpowiedź.
Adam Gent,
Warto zauważyć, że rzeczywiście jest to możliwe dzięki niestandardowym implementacjom kompilatora i optymalizacjom w czasie wykonywania ... kolejność może zostać ponownie uporządkowana, co spowoduje błąd. Jednak wydaje się, że narusza to specyfikację.
Darrell Teague
7

I nie możesz użyć valueOf () ?

Edycja: przy okazji, nic nie stoi na przeszkodzie, abyś używał static {} w wyliczeniu.

Fredrik
źródło
Lub syntetyczny valueOfw klasie wyliczenia, więc nie musisz określać klasy wyliczenia Class.
Tom Hawtin - tackline
Oczywiście po prostu nie miał wpisu javadoc, więc trudno było go połączyć.
Fredrik,
1
Możesz użyć valueOf (), ale nazwa musi być identyczna z tą ustawioną w deklaracji wyliczenia. Każdą z dwóch powyższych metod wyszukiwania można zmodyfikować tak, aby używała .equalsIgnoringCase () i miała trochę większą odporność na błędy.
@leonardo True. To, czy dodaje solidności lub po prostu tolerancji na błędy, jest dyskusyjne. W większości przypadków powiedziałbym, że jest to drugie i w takim przypadku lepiej jest zająć się tym gdzie indziej i nadal używać funkcji valueOf () Enuma.
Fredrik
4

W przypadku, gdy pomaga to innym, preferowana przeze mnie opcja, której tutaj nie ma, wykorzystuje funkcjonalność map Guavy :

public enum Vebosity {
    BRIEF("BRIEF"),
    NORMAL("NORMAL"),
    FULL("FULL");

    private String value;
    private Verbosity(final String value) {
        this.value = value;
    }

    public String getValue() {
        return this.value;
    }

    private static ImmutableMap<String, Verbosity> reverseLookup = 
            Maps.uniqueIndex(Arrays.asList(Verbosity.values()), Verbosity::getValue);

    public static Verbosity fromString(final String id) {
        return reverseLookup.getOrDefault(id, NORMAL);
    }
}

Z domyślnym ustawieniem, którego możesz użyć null, możesz throw IllegalArgumentExceptionlub możesz fromStringzwrócić Optionaldowolne zachowanie.

Chad Befus
źródło
3

od wersji Java 8 możesz zainicjować mapę w jednej linii i bez statycznego bloku

private static Map<String, Verbosity> stringMap = Arrays.stream(values())
                 .collect(Collectors.toMap(Enum::toString, Function.identity()));
Adrian
źródło
+1 Doskonałe wykorzystanie strumieni i bardzo zwięzłe bez utraty czytelności! Nie widziałem wcześniej tego użycia Function.identity(), uważam, że jest o wiele bardziej pociągający tekstowo niż e -> e.
Matsu Q.
0

Może spójrz na to. To działa dla mnie. Celem tego jest wyszukanie „RED” za pomocą „/ red_color”. Zadeklarowanie a static mapi załadowanie do niego enums tylko raz przyniosłoby pewne korzyści w zakresie wydajności, jeśli enumjest ich wiele.

public class Mapper {

public enum Maps {

    COLOR_RED("/red_color", "RED");

    private final String code;
    private final String description;
    private static Map<String, String> mMap;

    private Maps(String code, String description) {
        this.code = code;
        this.description = description;
    }

    public String getCode() {
        return name();
    }

    public String getDescription() {
        return description;
    }

    public String getName() {
        return name();
    }

    public static String getColorName(String uri) {
        if (mMap == null) {
            initializeMapping();
        }
        if (mMap.containsKey(uri)) {
            return mMap.get(uri);
        }
        return null;
    }

    private static void initializeMapping() {
        mMap = new HashMap<String, String>();
        for (Maps s : Maps.values()) {
            mMap.put(s.code, s.description);
        }
    }
}
}

Prosimy o wyrażenie opinii.

Midhun
źródło
-1

Możesz zdefiniować swój Enum jako następujący kod:

public enum Verbosity 
{
   BRIEF, NORMAL, FULL, ACTION_NOT_VALID;
   private int value;

   public int getValue()
   {
     return this.value;
   } 

   public static final Verbosity getVerbosityByValue(int value)
   {
     for(Verbosity verbosity : Verbosity.values())
     {
        if(verbosity.getValue() == value)
            return verbosity ;
     }

     return ACTION_NOT_VALID;
   }

   @Override
   public String toString()
   {
      return ((Integer)this.getValue()).toString();
   }
};

Zobacz poniższy link, aby uzyskać więcej wyjaśnień

Vinay Sharma
źródło
-1

Jeśli chcesz mieć wartość domyślną i nie chcesz tworzyć map wyszukiwania, możesz utworzyć statyczną metodę do obsługi tego. Ten przykład obsługuje również wyszukiwania, w których oczekiwana nazwa zaczynałaby się od liczby.

    public static final Verbosity lookup(String name) {
        return lookup(name, null);
    }

    public static final Verbosity lookup(String name, Verbosity dflt) {
        if (StringUtils.isBlank(name)) {
            return dflt;
        }
        if (name.matches("^\\d.*")) {
            name = "_"+name;
        }
        try {
            return Verbosity.valueOf(name);
        } catch (IllegalArgumentException e) {
            return dflt;
        }
    }

Jeśli potrzebujesz go na wartości drugorzędnej, najpierw zbuduj mapę wyszukiwania, tak jak w niektórych innych odpowiedziach.

ScrappyDev
źródło
-1
public enum EnumRole {

ROLE_ANONYMOUS_USER_ROLE ("anonymous user role"),
ROLE_INTERNAL ("internal role");

private String roleName;

public String getRoleName() {
    return roleName;
}

EnumRole(String roleName) {
    this.roleName = roleName;
}

public static final EnumRole getByValue(String value){
    return Arrays.stream(EnumRole.values()).filter(enumRole -> enumRole.roleName.equals(value)).findFirst().orElse(ROLE_ANONYMOUS_USER_ROLE);
}

public static void main(String[] args) {
    System.out.println(getByValue("internal role").roleName);
}

}

Vahap Gencdal
źródło
-2

Możesz użyć Enum::valueOf()funkcji zgodnie z sugestią Garetha Davisa i Brada Mace'a powyżej, ale upewnij się, że obsłużysz to IllegalArgumentException, co zostanie wyrzucone, jeśli użyty ciąg nie jest obecny w wyliczeniu.

Mayank Butpori
źródło