Pertanyaan Bagaimana memastikan kelengkapan dalam enum switch pada waktu kompilasi?


Saya memiliki beberapa pernyataan saklar yang menguji enum. Semua enum nilai-nilai harus ditangani dalam switch pernyataan oleh a case pernyataan. Selama refactoring kode dapat terjadi bahwa enum menyusut dan tumbuh. Ketika enum menyusut kompiler melempar kesalahan. Tetapi tidak ada kesalahan yang dilemparkan, jika itu enum tumbuh. Status pencocokan akan dilupakan dan menghasilkan kesalahan waktu proses. Saya ingin memindahkan kesalahan ini dari run time ke waktu kompilasi. Secara teoritis seharusnya bisa mendeteksi yang hilang enum kasus pada waktu kompilasi. Apakah ada cara untuk mencapai hal ini?

Pertanyaannya sudah ada "Cara mendeteksi nilai baru ditambahkan ke enum dan tidak ditangani dalam sakelar"tetapi tidak berisi jawaban hanya pekerjaan yang berhubungan dengan Eclipse.


32
2018-05-28 16:57


asal


Jawaban:


Solusi lain menggunakan pendekatan fungsional. Anda hanya perlu mendeklarasikan kelas enum sesuai dengan template berikutnya:

public enum Direction {

    UNKNOWN,
    FORWARD,
    BACKWARD;

    public interface SwitchResult {
        public void UNKNOWN();
        public void FORWARD();
        public void BACKWARD();
    }

    public void switchValue(SwitchResult result) {
        switch (this) {
            case UNKNOWN:
                result.UNKNOWN();
                break;
            case FORWARD:
                result.FORWARD();
                break;
            case BACKWARD:
                result.BACKWARD();
                break;
        }
    }
}

Jika Anda mencoba menggunakan ini tanpa satu konstanta enumerasi setidaknya, Anda akan mendapatkan kesalahan kompilasi:

getDirection().switchValue(new Direction.SwitchResult() {
    public void UNKNOWN() { /* */ }
    public void FORWARD() { /* */ }
    // public void BACKWARD() { /* */ } // <- Compilation error if missing
});

4
2017-12-12 17:16



Di Java yang efektif, Joshua Bloch merekomendasikan menciptakan metode abstrak yang akan diterapkan untuk setiap konstanta. Sebagai contoh:

enum Color {
    RED   { public String getName() {return "Red";} },
    GREEN { public String getName() {return "Green";} },
    BLUE  { public String getName() {return "Blue";} };
    public abstract String getName();
}

Ini akan berfungsi sebagai saklar aman, memaksa Anda untuk menerapkan metode jika Anda menambahkan konstanta baru.

EDIT: Untuk menjernihkan beberapa kebingungan, inilah yang setara dengan menggunakan reguler switch:

enum Color {
    RED, GREEN, BLUE;
    public String getName() {
        switch(this) {
            case RED:   return "Red";
            case GREEN: return "Green";
            case BLUE:  return "Blue";
            default: return null;
        }
    }
}

18
2018-05-28 17:56



Saya tidak tahu tentang compiler Java standar, tetapi compiler Eclipse tentu dapat dikonfigurasi untuk memperingatkan tentang hal ini. Pergi ke Window-> Preferences-> Java-> Compiler-> Kesalahan / Peringatan / Enum jenis konstan tidak tercakup pada switch.


10
2018-05-28 17:02



Menurut pendapat saya dan jika kode yang akan Anda jalankan berada di luar domain enum Anda, cara untuk melakukannya adalah dengan membangun sebuah test case yang melilitkan item Anda dalam enumerasi dan mengeksekusi potongan kode yang berisi switch.Jika ada yang tidak beres atau tidak seperti yang diharapkan, Anda dapat memeriksa kembali nilai atau status objek dengan pernyataan.

Anda dapat melakukan tes sebagai bagian dari beberapa proses pembangunan dan Anda akan melihat anomali apa pun pada titik ini.

Bagaimanapun, pengujian unit hampir wajib dan bermanfaat dalam banyak proyek.

Jika kode di dalam saklar termasuk dalam enum, masukkan ke dalam seperti yang diusulkan dalam jawaban lain.


3
2018-05-28 18:23



Mungkin alat seperti FindBugs akan menandai switch seperti itu.

Jawaban yang sulit adalah refactor:

Kemungkinan 1: bisa pergi Berorientasi Objek


2
2018-05-28 17:16



Anda juga dapat menggunakan adaptasi dari pola Pengunjung ke enum, yang menghindari menempatkan semua jenis keadaan yang tidak terkait di kelas enum.

Kegagalan waktu kompilasi akan terjadi jika yang memodifikasi enum cukup hati-hati, tetapi itu tidak dijamin.

Anda akan tetap mengalami kegagalan lebih awal daripada RTE dalam pernyataan default: ini akan gagal ketika salah satu kelas pengunjung dimuat, yang dapat Anda wujudkan saat startup aplikasi.

Berikut ini beberapa kode:

Anda mulai dari enum yang terlihat seperti itu:

public enum Status {
    PENDING, PROGRESSING, DONE
}

Di sini adalah bagaimana Anda mengubahnya untuk menggunakan pola pengunjung:

public enum Status {
    PENDING,
    PROGRESSING,
    DONE;

    public static abstract class StatusVisitor<R> extends EnumVisitor<Status, R> {
        public abstract R visitPENDING();
        public abstract R visitPROGRESSING();
        public abstract R visitDONE();
    }
}

Ketika Anda menambahkan konstanta baru ke enum, jika Anda tidak lupa untuk menambahkan metode visitXXX ke kelas StatusVisitor abstrak, Anda akan memiliki langsung kesalahan kompilasi yang Anda harapkan di mana-mana Anda menggunakan pengunjung (yang harus mengganti setiap switch yang Anda lakukan di enum):

switch(anObject.getStatus()) {
case PENDING :
    [code1]
    break;
case PROGRESSING :
    [code2]
    break;
case DONE :
    [code3]
    break;
}

seharusnya menjadi:

StatusVisitor<String> v = new StatusVisitor<String>() {
    @Override
    public String visitPENDING() {
        [code1]
        return null;
    }
    @Override
    public String visitPROGRESSING() {
        [code2]
        return null;
    }
    @Override
    public String visitDONE() {
        [code3]
        return null;
    }
};
v.visit(anObject.getStatus());

Dan sekarang bagian yang jelek, kelas EnumVisitor. Ini adalah kelas atas hirarki Pengunjung, menerapkan metode kunjungan dan membuat kode gagal saat memulai (uji atau aplikasi) jika Anda lupa memperbarui pengunjung absract:

public abstract class EnumVisitor<E extends Enum<E>, R> {

    public EnumVisitor() {
        Class<?> currentClass = getClass();
        while(currentClass != null && !currentClass.getSuperclass().getName().equals("xxx.xxx.EnumVisitor")) {
            currentClass = currentClass.getSuperclass();
        }

        Class<E> e = (Class<E>) ((ParameterizedType) currentClass.getGenericSuperclass()).getActualTypeArguments()[0];
        Enum[] enumConstants = e.getEnumConstants();
        if (enumConstants == null) {
            throw new RuntimeException("Seems like " + e.getName() + " is not an enum.");
        }
        Class<? extends EnumVisitor> actualClass = this.getClass();
        Set<String> missingMethods = new HashSet<>();
        for(Enum c : enumConstants) {
            try {
                actualClass.getMethod("visit" + c.name(), null);
            } catch (NoSuchMethodException e2) {
                missingMethods.add("visit" + c.name());
            } catch (Exception e1) {
                throw new RuntimeException(e1);
            }
        }
        if (!missingMethods.isEmpty()) {
            throw new RuntimeException(currentClass.getName() + " visitor is missing the following methods : " + String.join(",", missingMethods));
        }
    }

    public final R visit(E value) {
        Class<? extends EnumVisitor> actualClass = this.getClass();
        try {
            Method method = actualClass.getMethod("visit" + value.name());
            return (R) method.invoke(this);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

Ada beberapa cara Anda dapat menerapkan / memperbaiki kode lem ini. Saya memilih untuk naik hierarki kelas, berhenti ketika superclass adalah EnumVisitor, dan membaca tipe parameter dari sana. Anda juga bisa melakukannya dengan param konstruktor sebagai kelas enum.

Anda dapat menggunakan strategi penamaan yang lebih pintar untuk memiliki nama yang lebih buruk, dan seterusnya ...

Kekurangannya adalah bahwa itu sedikit lebih verbose. Manfaatnya adalah

  • kompilasi kesalahan waktu [dalam banyak kasus saja]
  • berfungsi bahkan jika Anda tidak memiliki kode enum
  • tidak ada kode mati (pernyataan default untuk mengaktifkan semua nilai enum)
  • sonar / pmd / ... tidak mengeluh bahwa Anda memiliki pernyataan switch tanpa pernyataan default

2
2017-10-02 12:47



Itu Proyek Enum Mapper menyediakan prosesor anotasi yang akan memastikan pada saat kompilasi bahwa semua enum konstan ditangani.
Selain itu mendukung reverse lookup dan pembuat peta paritial.

Contoh penggunaan:

@EnumMapper
public enum Seasons {
  SPRING, SUMMER, FALL, WINTER
}

Prosesor anotasi akan menghasilkan kelas java Seasons_MapperFull, yang dapat digunakan untuk memetakan semua konstanta enum ke nilai sewenang-wenang.

Berikut ini contoh penggunaan di mana kita memetakan setiap enum konstan ke string.

EnumMapperFull<Seasons, String> germanSeasons = Seasons_MapperFull
     .setSPRING("Fruehling")
     .setSUMMER("Sommer")
     .setFALL("Herbst")
     .setWINTER("Winter");

Anda sekarang dapat menggunakan mapper untuk mendapatkan nilai, atau melakukan pencarian terbalik

String germanSummer = germanSeasons.getValue(Seasons.SUMMER); // returns "Sommer"
ExtremeSeasons.getEnumOrNull("Sommer");                 // returns the enum-constant SUMMER
ExtremeSeasons.getEnumOrRaise("Fruehling");             // throws an IllegalArgumentException 

2
2018-05-18 09:11



Ini adalah varian dari pendekatan Pengunjung yang memberi Anda bantuan waktu kompilasi ketika Anda menambahkan konstanta:

interface Status {
    enum Pending implements Status {
        INSTANCE;

        @Override
        public <T> T accept(Visitor<T> v) {
            return v.visit(this);
        }
    }
    enum Progressing implements Status {
        INSTANCE;

        @Override
        public <T> T accept(Visitor<T> v) {
            return v.visit(this);
        }
    }
    enum Done implements Status {
        INSTANCE;

        @Override
        public <T> T accept(Visitor<T> v) {
            return v.visit(this);
        }
    }

    <T> T accept(Visitor<T> v);
    interface Visitor<T> {
        T visit(Done done);
        T visit(Progressing progressing);
        T visit(Pending pending);
    }
}

void usage() {
    Status s = getRandomStatus();
    String userMessage = s.accept(new Status.Visitor<String>() {
        @Override
        public String visit(Status.Done done) {
            return "completed";
        }

        @Override
        public String visit(Status.Progressing progressing) {
            return "in progress";
        }

        @Override
        public String visit(Status.Pending pending) {
            return "in queue";
        }
    });
}

Cantik, ya? Saya menyebutnya "Solusi Arsitektur Rube Goldberg".

Saya biasanya hanya menggunakan metode abstrak, tetapi jika Anda benar-benar tidak ingin menambahkan metode ke enum Anda (mungkin karena Anda memperkenalkan dependensi siklik), ini adalah cara.


0
2018-01-28 08:06