Konversi dari tipe enum ordinal ke enum

316

Saya memiliki tipe enum ReportTypeEnumyang dapat dilewati antar metode di semua kelas saya, tetapi kemudian saya harus meneruskannya di URL jadi saya menggunakan metode ordinal untuk mendapatkan nilai int. Setelah saya mendapatkannya di halaman JSP saya yang lain, saya perlu mengonversinya untuk kembali ke ReportTypeEnumsehingga saya bisa meneruskannya.

Bagaimana saya bisa mengubah ordinal ke ReportTypeEnum?

Menggunakan Java 6 SE.

Lennie
sumber
1
Tidak ada Java 6 EE, sampai sekarang (AFAIK). Ada Java SE 6, dan Java EE 5.
Hosam Aly

Jawaban:

632

Untuk mengubah ordinal menjadi represantation enumnya, Anda mungkin ingin melakukan ini:

ReportTypeEnum value = ReportTypeEnum.values()[ordinal];

Harap perhatikan batasan array.

Perhatikan bahwa setiap panggilan untuk values()mengembalikan array yang baru dikloning yang mungkin berdampak pada kinerja secara negatif. Anda mungkin ingin men-cache array jika itu akan sering dipanggil.

Contoh kode tentang cara cachevalues() .


Jawaban ini diedit untuk memasukkan umpan balik yang diberikan di dalam komentar

Joachim Sauer
sumber
Saya menerapkan solusi ini dan tidak berfungsi untuk saya. Ini mengembalikan nilai ordinal tidak dijamin untuk mencocokkan urutan di mana jenis yang ditambahkan ditambahkan. Saya tidak tahu apa yang
dianjurkan
@IcesDante: ordinal pasti dijamin sesuai dengan urutan nilai Enumerasi di sumber. Jika Anda mengamati perilaku yang berbeda, maka ada sesuatu yang salah. Namun jawaban saya di atas tidak optimal untuk semua alasan yang dijelaskan dalam jawaban lain.
Joachim Sauer
@ JoachimSauer Mungkin IcedDante berarti ordinal mungkin tidak cocok jika diproduksi dan disimpan oleh versi sumber yang lebih lama, di mana nilai enum berada dalam urutan yang berbeda.
LarsH
137

Ini hampir pasti merupakan ide yang buruk . Tentu saja jika ordinal secara de-facto tetap ada (misalnya karena seseorang telah menandai URL) - itu berarti Anda harus selalu menjaga enumpemesanan di masa depan, yang mungkin tidak jelas bagi pengelola kode di telepon.

Mengapa tidak mengkodekan enummenggunakan myEnumValue.name()(dan mendekode via ReportTypeEnum.valueOf(s)) sebagai gantinya?

oxbow_lakes
sumber
24
Bagaimana jika Anda mengubah nama enum (tetapi tetap memesan)?
Arne Evertsson
6
@ Andre - Saya pikir ini jauh lebih kecil kemungkinannya daripada beberapa orang tidak berpengalaman yang datang dan menambahkan valueposisi awal atau alfabet / logis yang benar. (Dengan logika saya maksud misalnya TimeUnitnilai-nilai memiliki posisi logis)
oxbow_lakes
7
Saya tentu saja lebih suka untuk memaksa urutan enum daripada nama enum saya ... ini sebabnya saya lebih suka menyimpan ordinal daripada nama enum dalam database. Selain itu, lebih baik menggunakan manipulasi int daripada String ...
8
Saya setuju. Di API publik, mengubah nama Enum akan merusak kompatibilitas tetapi mengubah urutan tidak. Karena alasan itu, lebih masuk akal untuk menggunakan nama itu sebagai "kunci" Anda
Noel
3
Menyimpan ordinal membuatnya lebih mudah untuk menerjemahkan ide-ide Anda ke bahasa lain. Bagaimana jika Anda perlu menulis beberapa komponen dalam C?
QED
94

Jika saya akan menggunakan values()banyak:

enum Suit {
   Hearts, Diamonds, Spades, Clubs;
   public static final Suit values[] = values();
}

Sementara itu dimanapun.java:

Suit suit = Suit.values[ordinal];

Pikirkan batasan array Anda.

QED
sumber
3
+1 ini sejauh ini merupakan solusi terbaik IMHO karena seseorang dapat melewati peraturan terutama di android.os.Message.
likejudo
9
Ini sangat mengkhawatirkan karena array bisa berubah . Meskipun nilai [] adalah final, nilai itu tidak mencegah Suit.values[0] = Suit.Diamonds;kode Anda. Idealnya hal itu tidak akan pernah terjadi, tetapi prinsip keseluruhan jangan biarkan bidang yang bisa berubah tetap berlaku. Untuk pendekatan ini, pertimbangkan menggunakan Collections.unmodifiableListatau sejenisnya.
Mshnik
Bagaimana dengan - nilai setelan final statis pribadi [] = nilai (); public static Suit [] getValues ​​() {mengembalikan nilai; }
Pratyush
4
@Pratyush yang akan membuat variabel array tidak berubah, tetapi tidak isinya. Saya masih bisa melakukan getValues ​​() [0] = somethingElse;
Calabacin
Setuju, dengan demikian intinya tidak memaparkan Suit values[]secara langsung atau tidak langsung (bagaimana disebutkan melalui getValues()), saya bekerja dengan metode publik di mana ordinalnilai harus dikirim bagaimana argumen dan mengembalikan Suitrepresentasi dari Suit values[]. Intinya di sini (ubin pertanyaan dari awal) adalah membuat tipe enum dari ordinal enum
Manuel Jordan
13

Saya setuju dengan kebanyakan orang bahwa menggunakan ordinal mungkin adalah ide yang buruk. Saya biasanya memecahkan masalah ini dengan memberikan enum konstruktor pribadi yang dapat mengambil misalnya nilai DB kemudian membuat fromDbValuefungsi statis mirip dengan yang ada di jawaban Jan.

public enum ReportTypeEnum {
    R1(1),
    R2(2),
    R3(3),
    R4(4),
    R5(5),
    R6(6),
    R7(7),
    R8(8);

    private static Logger log = LoggerFactory.getLogger(ReportEnumType.class);  
    private static Map<Integer, ReportTypeEnum> lookup;
    private Integer dbValue;

    private ReportTypeEnum(Integer dbValue) {
        this.dbValue = dbValue;
    }


    static {
        try {
            ReportTypeEnum[] vals = ReportTypeEnum.values();
            lookup = new HashMap<Integer, ReportTypeEnum>(vals.length);

            for (ReportTypeEnum  rpt: vals)
                lookup.put(rpt.getDbValue(), rpt);
         }
         catch (Exception e) {
             // Careful, if any exception is thrown out of a static block, the class
             // won't be initialized
             log.error("Unexpected exception initializing " + ReportTypeEnum.class, e);
         }
    }

    public static ReportTypeEnum fromDbValue(Integer dbValue) {
        return lookup.get(dbValue);
    }

    public Integer getDbValue() {
        return this.dbValue;
    }

}

Sekarang Anda dapat mengubah urutan tanpa mengubah pencarian dan sebaliknya.

jmkelm08
sumber
Ini adalah jawaban yang benar. Saya terkejut mendapat beberapa poin dibandingkan dengan jawaban lain yang lebih langsung tetapi berpotensi tidak valid (karena perubahan kode di masa depan).
Calabacin
8

Anda bisa menggunakan tabel pencarian statis:

public enum Suit {
  spades, hearts, diamonds, clubs;

  private static final Map<Integer, Suit> lookup = new HashMap<Integer, Suit>();

  static{
    int ordinal = 0;
    for (Suit suit : EnumSet.allOf(Suit.class)) {
      lookup.put(ordinal, suit);
      ordinal+= 1;
    }
  }

  public Suit fromOrdinal(int ordinal) {
    return lookup.get(ordinal);
  }
}
Jan
sumber
3
Lihat juga Enums .
trashgod
11
Wow! Cuma wow! Ini rapi, tentu saja, tapi ... Anda tahu - programmer C dalam diri saya berteriak kesakitan melihat bahwa Anda mengalokasikan HashMap full-blown dan melakukan pencarian di dalamnya semuanya hanya untuk dasarnya mengelola 4 konstanta: sekop, hati, berlian dan klub! Pemrogram AC akan mengalokasikan 1 byte untuk setiap: 'const char CLUBS = 0;' dll ... Ya, pencarian HashMap adalah O (1), tetapi memori dan CPU overhead dari HashMap, dalam hal ini membuatnya banyak pesanan lebih lambat dan sumber daya lebih lapar daripada memanggil .values ​​() secara langsung! Tidak heran kalau Jawa adalah tempat kenangan jika orang-orang menulis seperti ini ...
Leszek
2
Tidak setiap program membutuhkan kinerja permainan triple A. Dalam banyak kasus, perdagangan memori dan CPU untuk keamanan jenis, keterbacaan, perawatan, dukungan lintas platform, pengumpulan sampah, dll ... dapat dibenarkan. Bahasa tingkat yang lebih tinggi ada karena suatu alasan.
Jan
3
Tetapi jika rentang kunci Anda selalu 0...(n-1), maka array lebih sedikit kode dan lebih mudah dibaca juga; peningkatan kinerja hanyalah bonus. private static final Suit[] VALUES = values();dan public Suit fromOrdinal(int ordinal) { return VALUES[ordinal]; }. Keuntungan ekstra: crash langsung pada ordinals yang tidak valid, daripada diam-diam mengembalikan nol. (Tidak selalu keuntungan. Tapi sering.)
Thomas
4

Inilah yang saya gunakan. Saya tidak berpura-pura bahwa ini jauh kurang "efisien" daripada solusi sederhana di atas. Apa yang dilakukannya adalah memberikan pesan pengecualian yang jauh lebih jelas daripada "ArrayIndexOutOfBounds" ketika nilai ordinal yang tidak valid digunakan dalam solusi di atas.

Itu memanfaatkan fakta bahwa javadoc EnumSet menentukan elemen pengembalian iterator dalam urutan alami mereka. Ada yang menegaskan kalau itu tidak benar.

Tes JUnit4 menunjukkan bagaimana ini digunakan.

 /**
 * convert ordinal to Enum
 * @param clzz may not be null
 * @param ordinal
 * @return e with e.ordinal( ) == ordinal
 * @throws IllegalArgumentException if ordinal out of range
 */
public static <E extends Enum<E> > E lookupEnum(Class<E> clzz, int ordinal) {
    EnumSet<E> set = EnumSet.allOf(clzz);
    if (ordinal < set.size()) {
        Iterator<E> iter = set.iterator();
        for (int i = 0; i < ordinal; i++) {
            iter.next();
        }
        E rval = iter.next();
        assert(rval.ordinal() == ordinal);
        return rval;
    }
    throw new IllegalArgumentException("Invalid value " + ordinal + " for " + clzz.getName( ) + ", must be < " + set.size());
}

@Test
public void lookupTest( ) {
    java.util.concurrent.TimeUnit tu = lookupEnum(TimeUnit.class, 3);
    System.out.println(tu);
}
gerardw
sumber
1

Inilah yang saya lakukan di Android dengan Proguard:

public enum SomeStatus {
    UNINITIALIZED, STATUS_1, RESERVED_1, STATUS_2, RESERVED_2, STATUS_3;//do not change order

    private static SomeStatus[] values = null;
    public static SomeStatus fromInteger(int i) {
        if(SomeStatus.values == null) {
            SomeStatus.values = SomeStatus.values();
        }
        if (i < 0) return SomeStatus.values[0];
        if (i >= SomeStatus.values.length) return SomeStatus.values[0];
        return SomeStatus.values[i];
    }
}

ini singkat dan saya tidak perlu khawatir tentang memiliki pengecualian di Proguard

Seseorang disuatu tempat
sumber
1

Anda dapat mendefinisikan metode sederhana seperti:

public enum Alphabet{
    A,B,C,D;

    public static Alphabet get(int index){
        return Alphabet.values()[index];
    }
}

Dan gunakan seperti:

System.out.println(Alphabet.get(2));
Amir Fo
sumber
0
public enum Suit implements java.io.Serializable, Comparable<Suit>{
  spades, hearts, diamonds, clubs;
  private static final Suit [] lookup  = Suit.values();
  public Suit fromOrdinal(int ordinal) {
    if(ordinal< 1 || ordinal> 3) return null;
    return lookup[value-1];
  }
}

kelas tes

public class MainTest {
    public static void main(String[] args) {
        Suit d3 = Suit.diamonds;
        Suit d3Test = Suit.fromOrdinal(2);
        if(d3.equals(d3Test)){
            System.out.println("Susses");
        }else System.out.println("Fails");
    }
}

Saya menghargai bahwa Anda berbagi dengan kami jika Anda memiliki kode yang lebih efisien, Enum saya besar dan terus-menerus dipanggil ribuan kali.

Maj
sumber
Saya pikir Anda berarti "jika (ordinal <1 || ordinal> 4) mengembalikan nol;"
geowar
0

Jadi salah satu caranya adalah melakukan ExampleEnum valueOfOrdinal = ExampleEnum.values()[ordinal];yang berfungsi dan mudah, namun, seperti yang disebutkan sebelumnya, ExampleEnum.values()mengembalikan array yang dikloning untuk setiap panggilan. Itu bisa sangat mahal. Kita bisa menyelesaikannya dengan cara cache array seperti itu ExampleEnum[] values = values(). Ini juga "berbahaya" untuk memungkinkan array cache kita untuk dimodifikasi. Seseorang dapat menulis ExampleEnum.values[0] = ExampleEnum.type2;Jadi saya akan menjadikannya pribadi dengan metode accessor yang tidak melakukan penyalinan tambahan.

private enum ExampleEnum{
    type0, type1, type2, type3;
    private static final ExampleEnum[] values = values();
    public static ExampleEnum value(int ord) {
        return values[ord];
    }
}

Anda akan menggunakan ExampleEnum.value(ordinal)untuk mendapatkan nilai enum yang terkaitordinal

pelet joe
sumber
-1

Setiap enum memiliki nama (), yang memberikan string dengan nama anggota enum.

Diberikan enum Suit{Heart, Spade, Club, Diamond}, Suit.Heart.name()akan memberi Heart.

Setiap enum memiliki valueOf()metode, yang mengambil tipe enum dan string, untuk melakukan operasi terbalik:

Enum.valueOf(Suit.class, "Heart")kembali Suit.Heart.

Mengapa ada orang yang menggunakan tata cara di luar saya. Mungkin nanodetik lebih cepat, tetapi tidak aman, jika anggota enum berubah, karena pengembang lain mungkin tidak menyadari beberapa kode bergantung pada nilai-nilai ordinal (terutama di halaman JSP yang dikutip dalam pertanyaan, overhead jaringan dan basis data sepenuhnya mendominasi waktu, tidak menggunakan bilangan bulat di atas string).

Tony BenBrahim
sumber
2
Karena membandingkan bilangan bulat jauh lebih cepat daripada membandingkan string?
HighCommander4
2
Tetapi tata cara akan berubah jika seseorang memodifikasi enum (Add / reorder memvers). Kadang-kadang ini tentang keamanan dan bukan kecepatan, terutama pada halaman JSP di mana latensi jaringan 1.000.000 kali perbedaan antara membandingkan array bilangan bulat (string) dan bilangan bulat tunggal.
Tony BenBrahim
toString dapat diganti, sehingga mungkin tidak mengembalikan nama enum. Nama metode () adalah apa yang memberi nama enum (itu final)
gerardw
komentar kode dan versi aplikasi juga merupakan hal (mungkin format file lebih sederhana dan lebih kecil dengan cara ini)
joe pelletier
Tidak yakin mengapa ini dibatalkan ketika pada dasarnya merekomendasikan hal yang sama seperti pada jawaban oxbow_lakes. Jelas lebih aman daripada menggunakan ordinal.