Як забяспечыць паўнату ў камутатары пералічэнняў падчас кампіляцыі?

У мяне ёсць некалькі аператараў пераключэння, якія правяраюць у перечислимой . Усе пералік значэнні павінны быць апрацаваны ў Кнопка заявы ў выпадку інструкцыі. Падчас рэфактарынгу кода можа здарыцца так, што пералік сціскаецца і расце. Калі пералік ўсаджвання кампілятар выдае памылку. Але ніякай памылкі не генеруецца, калі клавішы пералік расце. Стан адпаведнасці забываецца і выдае памылку падчас выканання. Я хацеў бы перамясціць гэтую памылку ад запуску часу кампіляцыі. Тэарэтычна гэта павінна быць магчыма выявіць якая адсутнічае пералік выпадкі падчас кампіляцыі. Ці ёсць спосаб для дасягнення гэтай мэты?

Пытанне існуе ўжо "

32
Напісаць тэст, які не выконваецца, калі адзін выпадак адсутнічае. Запусціце тэсты на рэгулярнай аснове.
дададзена аўтар assylias, крыніца
@OliCharlesworth Я хачу, каб пазбавіцца ад па змаўчанні , бо гэты від па змаўчанні заўсёды памылка часу выканання. Таму што, калі гэта будзе сапраўднае стан павінна быць значэнне пералічэння.
дададзена аўтар ceving, крыніца
@OliCharlesworth Як вы думаеце, што я атрымаў памылку падчас выканання? ;-)
дададзена аўтар ceving, крыніца
Не маглі б вы кінуць RTE на па змаўчанні: справу і напісаць тэст, які ўводзіць ўсе магчымыя значэнні пералічэнняў?
дададзена аўтар vikingsteve, крыніца
Я не хацеў бы гэта ва ўсіх выпадках. Часам некаторыя значэння застаюцца на мэты, або апрацоўваюцца па змаўчанні. Так што цяжка прыдумаць з карыснай праверкай часу кампіляцыі без некаторага пашырэння мовы, дзе праграміст можа паказаць, калі «поўны перамыкач" пажадана ці не.
дададзена аўтар Henry, крыніца
Верагодна, у вас няма па змаўчанні выпадак?
дададзена аўтар Oliver Charlesworth, крыніца
Ну, калі ў вас ёсць па змаўчанні так, то малаверагодна, што любы час кампіляцыі інструмент будзе папярэджваць вас пра «зьніклых без вестак» спраў (таму што яны на самой справе не хапае ў гэтым сцэнары;))
дададзена аўтар Oliver Charlesworth, крыніца

10 адказы

У Эфектыўнае Java , Джошуа Блох рэкамендуе стварыць абстрактны метад, які будзе рэалізаваны для кожнай канстанты. Напрыклад:

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

Гэта будзе функцыянаваць як бяспечны перамыкач, прымушаючы вас рэалізаваць метад, калі дадаць новую канстанту.

EDIT: Для таго, каб растлумачыць некаторую блытаніну, вось эквівалент з дапамогай звычайнага перамыкач :

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
дададзена
Было б карысна, калі б вы паказалі адпаведны перамыкач. З таго, што вы кажаце, я прыйшла ў галаву думка, што я павінен падклас арыгінальны пералічэнне ў любым месцы, дзе выкарыстоўваецца перамыкач пералічэння. Ці правільна гэта? Падобна на тое, шмат накладных выдаткаў.
дададзена аўтар ceving, крыніца
Добра гэта магчыма (я паспрабаваў гэта, і гэта працуе), але калі ў мяне ёсць больш, чым адзін перамыкач, я рухаюся незвязаны код з розных месцаў у цэнтральнай пералік класа. Ён працуе, але код не належыць там.
дададзена аўтар ceving, крыніца
@ceving Не, вы не можаце стварыць падклас элемент перечислимую . Мая здагадка было тое, што ў вас ёсць доступ да кода, у гэтым выпадку можа быць пераважнай, каб пабудаваць перамыкач у самым пераліку. Глядзіце вышэй эквівалентнай версіі рэалізаваны ў якасці стандарту перамыкач .
дададзена аўтар shmosel, крыніца

I don't know about the standard Java compiler, but the Eclipse compiler can certainly be configured to warn about this. Go to Window->Preferences->Java->Compiler->Errors/Warnings/Enum type constant not covered on switch.

10
дададзена
Так, я меў тую ж ідэю і дадаў спасылку на іншае пытанне.
дададзена аўтар ceving, крыніца
Я не выкарыстоўваю Eclipse.
дададзена аўтар ceving, крыніца
@ceving: Ну, Eclipse пастаўляецца са сваім уласным кампілятарам, які можа быць выкліканы аўтаномным з каманднага радка, калі гэта неабходна. (Google для «Зацьмення» СЕС)
дададзена аўтар Oliver Charlesworth, крыніца
@ceving: Насамрэч, я бачыў. Але вы на самой справе не сказалі, якой кампілятар вы сапраўды зацікаўлены ў (без гэтай інфармацыі, ваш пытанне проста дублікат гэтай другой;))
дададзена аўтар Oliver Charlesworth, крыніца
@ceving: Ах добра. Вы, верагодна, варта абнавіць сваё пытанне, каб паказаць, што вы відавочна зацікаўленыя ў той Javac (ці любы іншы кампілятар) ...
дададзена аўтар Oliver Charlesworth, крыніца

Іншае рашэнне выкарыстоўвае функцыянальны падыход. Вам проста трэба абвясціць клас пералічэння ў адпаведнасці са наступным шаблонам:

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

Калі вы спрабуеце выкарыстоўваць гэта без адной канстанты пералічэння, па меншай меры, вы атрымаеце памылку кампіляцыі:

getDirection().switchValue(new Direction.SwitchResult() {
    public void UNKNOWN() { /* */ }
    public void FORWARD() { /* */ }
   //public void BACKWARD() { /* */ }//<- Compilation error if missing
});
4
дададзена
Гэта памяншае п перамыкач заявы 1 перамыкач заяву. Гэта не ідэальнае рашэнне, але вялікі крок наперад. Але я думаю, што гэта магло б быць лепш выклікаць SwitchResult Directable і switchValue прамой , таму што я не думаю, што гэта рашэнне можа быць адведзенай ў шаблон.
дададзена аўтар ceving, крыніца
@ceving +1 за добрае найменне.
дададзена аўтар matrix, крыніца

На маім позірку, і калі код, што вашыя збіраецца выконваць па-за дамена вашага пералічэння, спосаб зрабіць гэта, каб пабудаваць блок тэст, які перабірае вашы пытанні ў пералічэнні і выканаць фрагмент кода, які змяшчае switch.If нешта пойдзе не так, ці не, як чакаецца, вы можаце праверыць вяртаецца значэнне або стан аб'екта з сцвярджэннем.

Вы можаце выканаць тэсты як частка некаторага працэсу будаўніцтва, і вы ўбачыце якія-небудзь анамаліі ў гэтай кропцы.

Ва ўсякім выпадку, модульнае тэставанне з'яўляецца амаль абавязковым і карысным у многіх праектах.

Калі код ўнутры перамыкача належыць у пералічэнні, ўключыць яго ў адпаведнасці з прапановай у іншых адказах.

3
дададзена
Выкарыстанне модульных тэстаў, каб усталяваць моўныя праблемы пахаў на біту, як ўзламаць.
дададзена аўтар ceving, крыніца

Верагодна, такі інструмент, як FindBugs будзе адзначаць такія перамыкачы.

Жорсткі адказ будзе рэарганізаваць:

Possibility 1: can go Object абоiented

Калі гэта магчыма, залежыць ад кода ў выпадках.

Замест

switch (language) {
case EO: ... break;
case IL: ... break;
}

create an abstract method:, say p

language.p();

або

switch (p.categабоy()) {
case 1://Less cases.
...
}

Possibility 2: higher level

When having many switches, in an enum like DocumentType, WабоD, EXCEL, PDF, ... . Then create a WабоdDoc, ExcelDoc, PdfDoc extending a base class Doc. And again one can wабоk object абоiented.

2
дададзена
Што гэта можа дапамагчы? Калі катэгорыя() можа вяртаць 1, 3 і 5, які правярае, што перамыкач змяшчае 1, 3 і 5 выраз для пошуку адпаведнікаў выпадку?
дададзена аўтар ceving, крыніца
Я думаю, што ў вашай магчымасці 1 яшчэ можна прапусціць «катэгорыю» падчас кампіляцыі.
дададзена аўтар ceving, крыніца
(1) Калі рэалізацыя сапраўды неабходная, ды <�б> абстрактны - дзякуючы @OliCharlesworth. Гэта залежыць ад таго, як правіла, дадаюць перамыкач або дадаецца значэнне пералічэння. (2) Калі ў многіх выпадках такія ж, маючы перамыкач з сказаць толькі 3 катэгорыі менш схільныя памылак, і колькасць катэгорый не будзе расці так жа хутка, калі калі-небудзь. <�Я> Усё гэта толькі праграмная інжынерыя кода стылю аргументацыя. Будзь разумным залежыць.
дададзена аўтар Joop Eggen, крыніца
Гэта, верагодна, варта адзначыць, што р() павінны быць адзначаны абстрактны ў базавым класе.
дададзена аўтар Oliver Charlesworth, крыніца

Вы можаце таксама выкарыстоўваць адаптацыю шаблону Visitor да пералічэннях, якія пазбягаюць пакласці ўсе віды нязвязанага стану ў класе пералічэнняў.

Няўдача падчас кампіляцыі адбудзецца, калі адзін мадыфікаванне пералічэння дастаткова асцярожная, але не гарантавана.

Вы ўсё яшчэ будзеце мець адмова раней, чым RTE ў заяве па змаўчанні: яна не будзе выкананы, калі адзін з класа наведвальнікаў загружаюцца, што вы можаце зрабіць адбыцца пры запуску прыкладання.

Вось код:

Вы пачынаеце з пералічэння, якія выглядаюць так:

public enum Status {
    PENDING, PROGRESSING, DONE
}

Вось як вы пераўтварыць яго выкарыстоўваць шаблон наведвальніка:

public enum Status {
    PENDING,
    PROGRESSING,
    DONE;

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

Пры даданні новай канстанты ў пералік, калі вы не забыліся, каб дадаць метад visitXXX ў абстрактным клас StatusVisitor, вы будзеце мець непасрэдна памылку кампіляцыі вы чакаеце, усюды вы выкарыстоўвалі наведвальнікаў (які павінен замяніць кожны перамыкач вы зрабілі на пералічэнні):

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

павінны стаць:

StatusVisitor v = new StatusVisitor() {
    @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());

А цяпер непрыгожая частка, клас EnumVisitor. Гэта вышэйшы клас іерархіі наведвальнікаў, рэалізацыя метады візіту і робяць код збою пры запуску (выпрабаванне або прымянення), калі вы забыліся абнавіць Absract наведвальніка:

public abstract class EnumVisitor, R> {

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

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

Ёсць некалькі спосабаў, якімі Вы маглі б рэалізаваць/палепшыць гэты клей код. Я выбіраю ісці ўверх па іерархіі класаў, спыняецца, калі суперкласса з'яўляецца EnumVisitor, і чытаць параметризированный тып адтуль. Вы таксама можаце зрабіць гэта з канструктарам парамі з'яўляюцца класам пералічэння.

Вы можаце выкарыстоўваць разумную стратэгію наймення мець менш выродлівыя імёны, і гэтак далей ...

Недахоп заключаецца ў тым, што гэта крыху больш шматслоўным. перавагі

  • памылка кампіляцыі часу [у большасці выпадкаў так ці інакш]
  • працуе, нават калі вы не валодаеце кодам перечислимой
  • не мёртвы код (заява па змаўчанні перамыкача на ўсе значэння пералічэнняў)
  • сонар/PMD/... не скарджуся, што ў вас ёсць перамыкач заяву без заявы па змаўчанні
2
дададзена

Enum Mapper праект забяспечвае працэсар анатацый, які будзе пераканацца, што падчас кампіляцыі, што ўсе пералічэння канстанты апрацоўваюцца.
Акрамя таго, ён падтрымлівае зваротны пошук і paritial мапперы.

Прыклад выкарыстання:

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

Працэсар анатацый будзе генераваць клас Java Seasons_MapperFull , які можа быць выкарыстаны для адлюстравання ўсіх канстант пералічэнняў на адвольныя значэння.

Ніжэй прыведзены прыклад выкарыстання, дзе мы адлюстроўваецца кожны пералік канстанту радка.

EnumMapperFull germanSeasons = Seasons_MapperFull
     .setSPRING("Fruehling")
     .setSUMMER("Sommer")
     .setFALL("Herbst")
     .setWINTER("Winter");

Цяпер вы можаце выкарыстоўваць картограф, каб атрымаць значэнне, або зрабіць зваротны пошук

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

У выпадку, калі існуе некалькі пералічэнняў на розных узроўнях праекта, якія павінны адпавядаць адзін аднаму, гэта можа быць забяспечана з дапамогай тэсту:

private static > String[] names(T[] values) {
    return Arrays.stream(values).map(Enum::name).toArray(String[]::new);
}

@Test
public void testEnumCompleteness() throws Exception {
    Assert.assertArrayEquals(names(Enum1.values()), names(Enum2.values()));
}
1
дададзена

Калі вы карыстаецеся Android Studio (прынамсі версіі 3 і вышэй), вы можаце актываваць гэтую дакладную праверку ў абстаноўцы інспекцый. Гэта можа быць даступны на іншых IntelliJ Java IDE, а таксама.

Перайсці да Preferences/аглядах . У Java/кіравання Праблемы патоку <�код /> раздзел, праверце пункт «перамыкач» ENUM зацвярджэнне, што сумуе выпадак . Пры жаданні вы можаце змяніць строгасць Error , каб зрабіць яго больш відавочным, чым папярэджанне.

0
дададзена

Гэта варыянт падыходу Visitor, які дае вам час кампіляцыі дапамогі пры даданні канстанты:

interface Status {
    enum Pending implements Status {
        INSTANCE;

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

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

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

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

void usage() {
    Status s = getRandomStatus();
    String userMessage = s.accept(new Status.Visitor() {
        @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";
        }
    });
}

Прыгожая, а? Я называю гэта «Руба Голдберга Architecture Solution».

Я звычайна проста выкарыстоўваць абстрактны метад, але калі вы сапраўды не хочаце, каб дадаць метады да вашага перечислимому (магчыма, таму, што вы прывядзеце цыклічную залежнасць), гэта шлях.

0
дададзена
Я не ўпэўнены, калі я разумею гэта. Гэта адзін пералік з трыма значэннямі ці трыма пералічанымі з адным значэннем?
дададзена аўтар ceving, крыніца