Bagaimana saya bisa mencari Java enum dari nilai String-nya?

164

Saya ingin mencari enum dari nilai stringnya (atau mungkin nilai lainnya). Saya sudah mencoba kode berikut tetapi tidak mengizinkan statis di inisialisasi. Apakah ada cara sederhana?

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
sumber
IIRC, yang memberikan NPE karena inisialisasi statis dilakukan top down (yaitu konstanta enum di atas dibangun sebelum turun ke stringMapinisialisasi). Solusi yang biasa adalah dengan menggunakan kelas bersarang.
Tom Hawtin - tackline
Terima kasih semuanya atas tanggapan yang begitu cepat. (FWIW saya tidak menemukan Sun Javadocs sangat berguna untuk masalah ini).
peter.murray.rust
Ini benar-benar masalah bahasa daripada masalah perpustakaan. Namun, saya pikir dokumen API dibaca lebih dari JLS (walaupun mungkin tidak oleh perancang bahasa), jadi hal-hal seperti ini mungkin harus lebih menonjol di dokumen java.lang.
Tom Hawtin - tackline

Jawaban:

241

Gunakan valueOfmetode yang secara otomatis dibuat untuk setiap Enum.

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

Untuk nilai arbitrer, mulailah dengan:

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

Hanya pindah nanti ke implementasi Peta jika profiler Anda memberi tahu Anda.

Saya tahu ini mengulangi semua nilai, tetapi dengan hanya 3 nilai enum, hampir tidak layak dilakukan dengan upaya lain, pada kenyataannya kecuali jika Anda memiliki banyak nilai, saya tidak akan repot dengan Peta, itu akan cukup cepat.

Gareth Davis
sumber
terima kasih - dan saya dapat menggunakan konversi kasus jika saya tahu nilainya masih berbeda
peter.murray.rust
Anda dapat langsung mengakses anggota kelas 'abbr' jadi alih-alih "v.abbr ()" Anda dapat menggunakan "v.abbr.equals ...".
Amio.io
7
Juga, untuk nilai arbitrer (kasus kedua) pertimbangkan untuk melempar IllegalArgumentExceptionbukan mengembalikan nol ( yaitu ketika tidak ada kecocokan ditemukan) - ini adalah bagaimana JDK Enum.valueOf(..)melakukannya.
Priidu Neemre
135

Anda sudah dekat. Untuk nilai arbitrer, coba sesuatu seperti berikut ini:

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
sumber
4
Karena masalah classloader metode ini tidak akan berfungsi andal. Saya tidak merekomendasikannya dan saya telah melihatnya gagal secara teratur karena peta pencarian tidak siap sebelum diakses. Anda perlu meletakkan "lookup" di luar enum atau di kelas lain sehingga akan memuat pertama.
Adam Gent
7
@ Adam Gent: Ada tautan di bawah ke JLS tempat konstruksi yang tepat ini ditampilkan sebagai contoh. Dikatakan bahwa inisialisasi statis terjadi dari atas ke bawah: docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#d5e12267
Selena
3
Ya java modern seperti java 8 telah memperbaiki model memori. Itu dikatakan agen swap panas seperti jrebel masih bisa kacau jika Anda menanamkan inisialisasi karena referensi melingkar.
Adam Gent
@Adam Gent: Hmmm. Saya belajar sesuatu yang baru setiap hari. [Diam-diam menyelinap ke refactor enum saya dengan singel bootstrap ...] Maaf jika ini adalah pertanyaan bodoh, tetapi apakah ini sebagian besar masalah dengan inisialisasi statis?
Selena
Saya belum pernah melihat masalah dengan blok kode statis yang tidak diinisialisasi, tapi saya hanya menggunakan Java 8 sejak saya mulai di Jawa, pada tahun 2015. Saya hanya melakukan sedikit benchmark menggunakan JMH 1.22 dan menggunakan a HashMap<>untuk menyimpan enum yang terbukti lebih dari 40% lebih cepat dari loop foor.
cbaldan
21

dengan Java 8 Anda dapat mencapai dengan cara ini:

public static Verbosity findByAbbr(final String abbr){
    return Arrays.stream(values()).filter(value -> value.abbr().equals(abbr)).findFirst().orElse(null);
}
Lam Le
sumber
Saya akan melemparkan pengecualian daripada kembali null. Tapi itu juga tergantung pada kasus penggunaan
alexander
12

Jawaban @ Lyle agak berbahaya dan saya telah melihatnya tidak bekerja terutama jika Anda membuat enum kelas dalam statis. Sebaliknya saya telah menggunakan sesuatu seperti ini yang akan memuat peta BootstrapSingleton sebelum enum.

Sunting ini seharusnya tidak menjadi masalah lagi dengan JVM modern (JVM 1.6 atau lebih tinggi) tapi saya pikir masih ada masalah dengan JRebel tapi saya belum punya kesempatan untuk menguji ulang .

Muat saya terlebih dahulu:

   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>();
   }

Sekarang muat di enum constructor:

   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);
        }
    }

Jika Anda memiliki enum bagian dalam, Anda bisa mendefinisikan Peta di atas definisi enum dan bahwa (secara teori) harus dimuat sebelumnya.

Adam Gent
sumber
3
Perlu mendefinisikan "berbahaya" dan mengapa implementasi kelas batin penting. Ini lebih merupakan masalah definisi statis top-to-bottom tetapi ada kode yang berfungsi dalam jawaban terkait lainnya dengan tautan kembali ke pertanyaan ini yang menggunakan Peta statis pada instance Enum itu sendiri dengan metode "dapatkan" dari nilai yang ditentukan pengguna ke nilai Jenis enum. stackoverflow.com/questions/604424/lookup-enum-by-string-value
Darrell Teague
2
@DarrellTeague masalah aslinya adalah karena JVM lama (pra 1.6) atau JVM lainnya (IBM) atau hotcode swappers (JRebel). Masalahnya seharusnya tidak terjadi di JVM modern jadi saya mungkin hanya menghapus jawaban saya.
Adam Gent
Bagus untuk dicatat bahwa memang mungkin karena implementasi kompiler khusus dan optimisasi runtime ... pemesanan dapat dilakukan penyetelan ulang, yang menghasilkan kesalahan. Namun, ini tampaknya melanggar spesifikasi.
Darrell Teague
7

Dan Anda tidak dapat menggunakan valueOf () ?

Sunting: Btw, tidak ada yang menghentikan Anda menggunakan statis {} dalam enum.

Fredrik
sumber
Atau sintetis valueOfpada kelas enum, jadi Anda tidak perlu menentukan kelas enum Class.
Tom Hawtin - tackline
Tentu saja, itu tidak memiliki entri javadoc jadi sulit untuk ditautkan.
Fredrik
1
Anda bisa menggunakan valueOf () tetapi nama harus cocok dengan yang ditetapkan dalam deklarasi enum. Salah satu dari dua metode pencarian di atas dapat dimodifikasi untuk menggunakan .equalsIgnoringCase () dan memiliki sedikit kekokohan terhadap kesalahan.
@leonardo Benar. Jika itu menambah kekokohan atau hanya toleransi kesalahan bisa diperdebatkan. Dalam kebanyakan kasus, saya akan mengatakan itu adalah yang terakhir dan dalam kasus itu lebih baik untuk menanganinya di tempat lain dan masih menggunakan nilai Enum ().
Fredrik
4

Jika itu membantu orang lain, opsi yang saya pilih, yang tidak tercantum di sini, menggunakan fungsionalitas Peta Guava :

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);
    }
}

Dengan default yang dapat Anda gunakan null, Anda dapat throw IllegalArgumentExceptionatau Anda fromStringdapat mengembalikan Optional, perilaku apa pun yang Anda inginkan.

Chad Befus
sumber
3

karena java 8 Anda dapat menginisialisasi peta dalam satu baris dan tanpa blok statis

private static Map<String, Verbosity> stringMap = Arrays.stream(values())
                 .collect(Collectors.toMap(Enum::toString, Function.identity()));
Adrian
sumber
+1 Penggunaan streaming yang luar biasa, dan sangat ringkas tanpa mengorbankan keterbacaan! Saya juga belum pernah melihat penggunaan ini Function.identity()sebelumnya, saya merasa jauh lebih menarik secara tekstual daripada e -> e.
Matsu Q.
0

Mungkin, lihat ini. Ini bekerja untuk saya. Tujuan dari ini adalah untuk mencari 'RED' dengan '/ red_color'. Mendeklarasikan static mapdan memuat enums ke dalamnya hanya sekali akan membawa beberapa manfaat kinerja jika enumjumlahnya banyak.

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);
        }
    }
}
}

Silakan masukkan opini Anda.

Midhun
sumber
-1

Anda dapat mendefinisikan Enum sebagai kode berikut:

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();
   }
};

Lihat tautan berikut untuk klarifikasi lebih lanjut

Vinay Sharma
sumber
-1

Jika Anda menginginkan nilai default dan tidak ingin membangun peta pencarian, Anda bisa membuat metode statis untuk mengatasinya. Contoh ini juga menangani pencarian di mana nama yang diharapkan akan dimulai dengan angka.

    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;
        }
    }

Jika Anda membutuhkannya pada nilai sekunder, Anda hanya perlu membuat peta pencarian terlebih dahulu seperti pada beberapa jawaban lainnya.

ScrappyDev
sumber
-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
sumber
-2

Anda dapat menggunakan Enum::valueOf()fungsi seperti yang disarankan oleh Gareth Davis & Brad Mace di atas, tetapi pastikan Anda menangani IllegalArgumentExceptionyang akan dibuang jika string yang digunakan tidak ada di enum.

Mayank Butpori
sumber