Ubah nilai integer untuk mencocokkan Java Enum

87

Saya memiliki enum seperti ini:

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

Sekarang saya mendapatkan int dari input eksternal dan menginginkan input yang cocok - melempar pengecualian jika nilai tidak ada tidak masalah, tetapi sebaiknya saya memilikinya DLT_UNKNOWN dalam kasus itu.

int val = in.readInt();
PcapLinkType type = ???; /*convert val to a PcapLinkType */
Lyke
sumber

Jawaban:

106

Anda perlu melakukan ini secara manual, dengan menambahkan peta statis di kelas yang memetakan Integer ke enum, seperti

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
sumber
1
diperbarui dengan rekomendasi dari dty, yang merupakan ide bagus.
MeBigFatGuy
Saya harap Anda menjalankan kode saya melalui kompiler terlebih dahulu ... Saya baru saja membuatnya di luar kepala saya. Saya tahu tekniknya berhasil - saya menggunakannya kemarin. Tetapi kodenya ada di komputer lain dan yang ini tidak memiliki alat pengembang saya.
Dty
1
allOf hanya tersedia untuk set
MeBigFatGuy
1
Juga, EnumMapmenggunakan enum sebagai kuncinya. Dalam hal ini, OP menginginkan enum sebagai nilai.
Dty
8
Sepertinya banyak overhead yang tidak diperlukan. Mereka yang benar-benar membutuhkan jenis operasi ini mungkin membutuhkan kinerja tinggi karena mereka menulis / membaca dari aliran / soket, dalam hal ini, caching values()(jika nilai enum Anda berurutan) atau switchpernyataan sederhana akan mengalahkan metode ini dengan mudah . Jika Anda hanya memiliki sedikit entri di dalam Anda, Enummaka tidak masuk akal untuk menambahkan overhead dari HashMap hanya untuk kenyamanan karena tidak perlu memperbarui switchpernyataan. Cara ini mungkin terlihat lebih elegan, tetapi juga boros.
hancurkan
30

Ada metode statis values()yang adalah didokumentasikan, tetapi tidak di mana Anda harapkan itu: 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];}
}

Pada prinsipnya, Anda dapat menggunakan hanya values()[i], tetapi ada rumor yang values()akan membuat salinan larik setiap kali dipanggil.

18446744073709551615
sumber
9
Menurut Joshua Bloch (Buku Java Efektif) : Jangan pernah mendapatkan nilai yang terkait dengan enum dari ordinalnya; Implementasi Anda tidak boleh bergantung pada urutan enum.
stevo.mit
4
Implementasi apa? Jika kita mengimplementasikan beberapa algoritma, implementasi tidak boleh bergantung pada urutan enum kecuali urutan itu didokumentasikan. Saat kita mengimplementasikan enum itu sendiri, tidak masalah untuk menggunakan detail implementasi seperti itu, dengan cara yang sama seperti menggunakan metode class-private.
18446744073709551615
1
Tidak setuju. Saya percaya tidak pernah dimaksudkan terlepas dari dokumentasi. Anda tidak boleh menggunakan ordinal meskipun Anda menerapkan enum sendiri. Baunya tidak enak dan rawan kesalahan. Saya bukan ahli tetapi saya tidak akan berdebat dengan Joshua Bloch :)
stevo.mit
4
@ stevo.mit lihat enum java.time.Month baru di Java 8. Metode statis Month.of (int) melakukan persis seperti yang dikatakan Joshua Bloch yang seharusnya "tidak pernah" Anda lakukan. Ia mengembalikan satu Bulan berdasarkan ordinalnya.
Klitos Kyriacou
1
@ stevo.mit Ada enum yang dipesan dan enum yang tidak berurutan . (Dan juga enum bitmask .) Tidaklah benar untuk membicarakannya hanya sebagai "enum". Keputusan tentang apa arti ekspresif untuk digunakan harus didasarkan pada tingkat abstraksi yang Anda kerjakan. Memang salah untuk menggunakan detail implementasi (cara ekspresif dari tingkat yang lebih rendah) atau asumsi penggunaan (cara ekspresif dari tingkat yang lebih tinggi). Adapun " tidak pernah ", dalam bahasa manusia tidak pernah berarti tidak pernah, karena selalu ada beberapa konteks. (Biasanya, dalam pemrograman aplikasi, tidak pernah ...) BTW, programering.com/a/MzNxQjMwATM.html
18446744073709551615
16

Anda harus membuat metode statis baru di mana Anda mengulang PcapLinkType.values ​​() dan membandingkan:

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

Itu akan baik-baik saja jika jarang dipanggil. Jika sering dipanggil, lihat Mappengoptimalan yang disarankan oleh orang lain.

Bozho
sumber
4
Mungkin mahal kalau disebut banyak. Membangun peta statis cenderung memberikan biaya amortisasi yang lebih baik.
Dty
@dty o (n) dengan n = 200 - saya rasa ini bukan masalah
Bozho
7
Itu adalah pernyataan yang sangat konyol tanpa merasakan seberapa sering itu disebut. Jika dipanggil sekali, oke. Jika dipanggil untuk setiap paket yang melewati jaringan 10Ge, maka membuat algoritme 200x lebih cepat sangatlah penting. Oleh karena itu mengapa saya memenuhi pernyataan saya dengan "jika disebut banyak"
1111 tepat
10

Anda dapat melakukan sesuatu seperti ini untuk secara otomatis mendaftarkan semuanya ke dalam koleksi yang kemudian dapat dengan mudah mengonversi bilangan bulat ke enum yang sesuai. (BTW, menambahkannya ke peta di konstruktor enum tidak diperbolehkan . Sangat menyenangkan mempelajari hal-hal baru bahkan setelah bertahun-tahun menggunakan Java. :)

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
sumber
1
Itulah yang Anda dapatkan untuk memeriksa ulang jawaban Anda sebelum memposting. ;)
Esko Luontola
10

jika Anda memiliki enum seperti ini

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

maka Anda bisa menggunakannya seperti

PcapLinkType type = PcapLinkType.values()[1]; /*convert val to a PcapLinkType */
Jack Gajanan
sumber
Anda melewatkan komentar / * snip, 200 enum lagi, tidak selalu berturut-turut. * /
MeBigFatGuy
kalau-kalau nilai enum Anda adalah transitivitas dari Nol, ini adalah praktik yang buruk
cuasodayleo
4

Seperti yang dikatakan @MeBigFatGuy, kecuali Anda dapat membuat static {...}blok Anda menggunakan loop di atas values()koleksi:

static {
    for (PcapLinkType type : PcapLinkType.values()) {
        intToTypeMap.put(type.getValue(), type);
    }
}
bagus
sumber
4

Saya tahu pertanyaan ini berumur beberapa tahun, tetapi karena Java 8, sementara itu, membawa kami Optional, saya pikir saya akan menawarkan solusi menggunakannya ( Streamdan 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));
  }
}

Optionaladalah seperti null: ini mewakili kasus ketika tidak ada nilai (valid). Tetapi ini adalah alternatif yang lebih aman untuk nulltipe atau nilai default seperti DLT_UNKNOWNkarena Anda bisa lupa untuk memeriksa kasus nullatau DLT_UNKNOWN. Keduanya adalah PcapLinkTypenilai yang valid ! Sebaliknya, Anda tidak dapat menetapkan Optional<PcapLinkType>nilai ke variabel tipe PcapLinkType.Optionalmembuat Anda memeriksa nilai yang valid terlebih dahulu.

Tentu saja, jika Anda ingin mempertahankan DLT_UNKNOWNkompatibilitas mundur atau alasan lain apa pun, Anda masih dapat menggunakan Optionalbahkan dalam kasus itu, menggunakan orElse()untuk menetapkannya sebagai nilai default:

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
sumber
3

Anda bisa menambahkan metode statis dalam enum Anda yang menerima an intsebagai parameter dan mengembalikan 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
sumber
Lebih baik jangan lupa menambahkan entri ke switchpernyataan itu jika Anda menambahkan enum baru. Tidak ideal, IMHO.
Dty
1
@dty Jadi, menurut Anda overhead dari HashMap melebihi kebutuhan untuk menambahkan kasus baru ke pernyataan switch?
hancurkan
1
Saya pikir saya lebih suka menulis kode yang membantu saya tidak membuat kesalahan dan oleh karena itu lebih cenderung benar sebelum saya fokus pada kinerja mikro dari pencarian hash.
Dty
3

Ini yang saya gunakan:

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
sumber
Menggunakan ordinal telah diidentifikasi sebagai praktik yang buruk, secara umum lebih baik dihindari.
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.dll
sumber
1
Mahal kalau disebut banyak. Perlu diingat untuk memperbarui array (mengapa Anda bahkan memilikinya ketika enum mendefinisikan .values()metode?).
Dty
@dty apakah itu coba / tangkap? Saya pikir akan lebih adil untuk mengatakannya mahal jika banyak nilainya termasuk dalam kategori DLT_UNKNOWN.
nsfyn55
1
Saya sangat terkejut melihat solusi array ditolak dan solusi peta dipilih. Yang tidak saya sukai di sini adalah --int, tapi ini jelas salah ketik.
18446744073709551615
Saya mengerti: mereka ingin nullmenggantikan DLT_UKNOWN:)
18446744073709551615
1
Mengapa tidak static final values[] = PcapLinkType.values()?
18446744073709551615
0

Tidak ada cara untuk menangani tipe enumerasi berbasis integer dengan elegan. Anda mungkin berpikir untuk menggunakan enumerasi berbasis string daripada solusi Anda. Bukan cara yang disukai sepanjang waktu, tapi masih ada.

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
sumber