Использование порядкового значения Enum в операторе switch-case - PullRequest
8 голосов
/ 23 июля 2011

Для моего проекта я использую перечисления, и мне нужно реализовать оператор switch-case, где проверяются порядковые номера значений конкретного Enum, например, так:

        switch ( variable )
        {
        case MyEnum.A.ordinal():
            return true;
        case MyEnum.B.ordinal():
            return true;
        default:
            return false;
        }

Примечание: returnзначения являются только примером.

К сожалению, Eclipse (я использую 1.6 JDK) выдает мою ошибку компиляции "case выражения должны быть константными выражениями".Что я должен делать?Есть ли другой метод, кроме статической таблицы поиска, описанный здесь: Преобразование из порядкового номера перечисления в тип перечисления ?

Ответы [ 6 ]

18 голосов
/ 23 июля 2011

Вот как это делается, при условии, что у вас есть где-нибудь порядковый порядковый номер где-то. Обычный способ сохранить перечисление - это имя, а не порядковый номер. Также вы не должны использовать порядковый номер в нормальных условиях, если не пытаетесь реализовать что-то вроде EnumMap / Set. Конечно, enum может быть просто портом из C-подобного материала, и для работы с неизбежным int требуется преобразование в объект Enum.

Просто используйте Enum.values(), чтобы получить массив, упорядоченный по ordinal(), так как массив клонируется каждый раз, с сохранением ссылки в направлении все в порядке.

enum E{
 A, B, C...   
}

final static E[] vals = E.values();//copy the values(), calling values() clones the array
boolean f(int variable){
  switch(vals[variable]){
  case A:
...
  case B:
...
//break;
  default:
...
   }
}
<Ч />

Просто заметил, что вам нужно только true и false, это тип поведения Set. Вы можете использовать java.util.EnumSet или простой long, если чувствуете себя смелым (и не имеете более 64 констант enum). например:

private static <E extends Enum> long ord(E e){
  return 1L<<e.ordinal();
}

static final long positiveSet = ord(E.A)+ord(E.B);
boolean f(int ordinal){
  return 0!=(positiveSet&(1L<<ordinal));
}
4 голосов
/ 23 июля 2011

Прежде всего, вы не должны так сильно полагаться на порядковый номер. Если возможно, сделайте вашу переменную String (и преобразуйте в enum, используя Enum.valueOf(string) или, в лучшем случае, сделайте ее enum.

Если вы действительно не можете, тогда используйте enum.values()[ordinal]. Затем используйте enum в переключателе.

2 голосов
/ 23 июля 2011

Ответ нацелен на комментарий @Riaan по константам и перечислениям метода и причинам производительности, и он не отвечает непосредственно на вопрос OP, поэтому я думаю, что это можно считать шумом.Тем не менее, я считаю, что важно понять, как работает внутреннее устройство.

Я взял эталонный тест из его примера и улучшил его, удалив сборку мусора и создание строк, которые занимают более 90% времени выполнения.Добавлена ​​фаза прогрева, чтобы гарантировать, что горячая точка на самом деле компилирует методы.

Есть еще кое-что, эталонным тестом является тестирование на месте вызова.Оптимизация для сайтов вызовов довольно различна для 1, для 2 для немногих больше и для больше-больше.CallSite - это вызов абстрактного (или просто переопределенного) метода.

Ниже приведен тест с 6 константами перечислений:

package t1;

public class ZEnums {

    public enum MyEnum {
  A { boolean getBooleanValue(){ return true; }},
  B { boolean getBooleanValue(){ return true; }},
  C { boolean getBooleanValue(){ return false; }},
  D { boolean getBooleanValue(){ return false; }},
  E { boolean getBooleanValue(){ return false; }},
  F { boolean getBooleanValue(){ return false; }}, 

  ;
  abstract boolean getBooleanValue();
  }

  public enum MyEnumAlt {
    A (true), 
    B (true),
    C (false),
    D (false),
    E (false),
    F (false),
    ;
    private final boolean isTrue;
    MyEnumAlt( boolean isTrue){ this.isTrue = isTrue; }
    boolean getBooleanValue(){ return isTrue; };
  }

  public static void main(String[] args) {
    log("Warming up...");
    //10k iterations won't do since not all paths for MyEnum are invoked 10k (default) times to warrant compilations 
    long warmum = testEnum(100000 )+ testAlt(100000)+testEnum(100000 )+ testAlt(100000);
    log("Warm up: %d", warmum);     
    //no info from +XX:+PrintCompilation below this one, or the test is invalid
    testMain();

    }
    public static void testMain() {
        int iterations = (int)4e7;

        log("Testing %d iterations%n", iterations);
        log("====");

        log("Testing with Overridden method...");       
    System.gc();
    {
    long start = System.currentTimeMillis();
    long len = 0;
    len = testEnum(iterations);
    long time = System.currentTimeMillis()-start;
    log("Overridden method version took %dms, length: %d ", time, len);
    }
////////////
    System.gc();
    {
    log("Testing with Constant in c-tor... ");
    long start = System.currentTimeMillis();
    long len = testAlt(iterations);

    long time = System.currentTimeMillis()-start;
    log("Constant in c-tor version took %dms, length: %d ", time, len);
    }
    }
    private static long testEnum(int iterations) {
        long len = 0;
        for(int i=0; i<iterations; i++){
        MyEnum tmpEnum = MyEnum.A;
        if(i%3==0){ tmpEnum = MyEnum.A;        
        }else if(i%4==0){ tmpEnum = MyEnum.B;
        }else if(i%5==0){ tmpEnum = MyEnum.C;
        }else if(i%6==0){ tmpEnum = MyEnum.D;
        }else if(i%6==0){ tmpEnum = MyEnum.E;
        }else{ tmpEnum = MyEnum.F; 
        }
        String tmp = tmpEnum.getBooleanValue()?"XXX":"ABCDE";
        len+=tmp.length();
    }
        return len;
    }
    private static long testAlt(int iterations) {
        long len =0;
        for(int i=0; i<iterations; i++){
        MyEnumAlt tmpEnum = MyEnumAlt.A;
        if(i%3==0){ tmpEnum = MyEnumAlt.A;
        }else if(i%4==0){ tmpEnum = MyEnumAlt.B;
        }else if(i%5==0){ tmpEnum = MyEnumAlt.C;
        }else if(i%6==0){ tmpEnum = MyEnumAlt.D;
        }else if(i%6==0){ tmpEnum = MyEnumAlt.E;
        }else{ tmpEnum = MyEnumAlt.F; 
        }
        String tmp = tmpEnum.getBooleanValue()?"XXX":"ABCDE";
        len+=tmp.length();
    }
        return len;
    }
    static void log(String msg, Object... params){ 
        String s = params.length>0?String.format(msg, params):msg;
        System.out.printf("%tH:%<tM:%<tS.%<tL %s%n", new Long(System.currentTimeMillis()), s);
    }
}
21:08:46.685 Warming up...
    148   1%      t1.ZEnums::testEnum @ 7 (125 bytes)
    150   1       t1.ZEnums$MyEnum$6::getBooleanValue (2 bytes)
    152   2       t1.ZEnums$MyEnum$1::getBooleanValue (2 bytes)
    154   3       t1.ZEnums$MyEnum$2::getBooleanValue (2 bytes)
    155   4       t1.ZEnums$MyEnum$3::getBooleanValue (2 bytes)
    158   2%      t1.ZEnums::testAlt @ 7 (125 bytes)
    162   5       t1.ZEnums::testEnum (125 bytes)
    164   6       t1.ZEnums::testAlt (125 bytes)
21:08:46.716 Warm up: 1600000
21:08:46.716 Testing 40000000 iterations

21:08:46.716 ====
21:08:46.716 Testing with Overridden method...
21:08:47.513 <b>Overridden method version took 781ms</b>, length: 160000000 
21:08:47.513 Testing with Constant in c-tor... 
21:08:48.138 <b>Constant in c-tor version took 625ms</b>, length: 160000000 

Код был запущен с -server -XX:+PrintCompilationопции.Разница не огромная, конечно.Однако это не интересная проблема.Если вы протестируете версию с двумя константами перечисления, результат может значительно отличаться.Для двух сайтов вызовов компилятор генерирует код путем встраивания рассматриваемого метода.В приведенном выше тесте это удалит весь вызов booleanValue и может даже заставить выполнить тест в O (1).

Однако самая смешная часть состоит из 2–3 констант перечисления, когда компилятор начинает использовать встроенные кэши.а затем константа и WOW magic все меняется.

Суть в том, что правильный тест действительно труден и требует определенных знаний о том, как JIT компилируется, когда GC может быть проблемой (либо удалить ее, либо принять), и так далее. Ссылки :

1 голос
/ 08 мая 2014

Лучшим решением было бы что-то вроде этого:

перечисление:

public interface ACServices {

    public static enum MessageType {

        // periodic needs to saved in DB
        PIPE_INFO_TYPE_AC_DEVICE_LIST, // periodic from littlecloud
        PIPE_INFO_TYPE_DEV_ONLINE,
        PIPE_INFO_TYPE_DEV_OFFLINE,
        PIPE_INFO_TYPE_EVENT_LOG,
        PIPE_INFO_TYPE_DEV_DETAIL,
    };

реализация:

ACServices.MessageType msgType = ACServices.MessageType.valueOf(acResponse.getType());
switch (msgType){
    case INT_INFO_DEV_STATUS:
        break;
    case INT_INFO_DEV_TZ:
        break;
    case PIPE_INFO_DEV_COUNT:
        break;
    case PIPE_INFO_TYPE_AC_DEVICE_LIST:
        break;
    case PIPE_INFO_TYPE_CONFIG_GET_TEXT:
        break;
    default:
        break;
}

Ман Пак Хонг, Дейв (manpakhong@hotmail.com)

1 голос
/ 23 июля 2011

То, что вы просите, вероятно, это: Если вам нужен переключатель в методе самого перечисления:

switch ( this )
        {
        case A:
            return true;
        case B:
            return true;
        default:
            return false;
        }

И в другом классе:

switch ( variable )  //Variable of type myEnum
        {
        case A:
            return true;
        case B:
            return true;
        default:
            return false;
        }

Легко забыть обновить операторы switch, если вы добавите другое перечисление, поэтому лучше было бы поместить такие методы в само перечисление и использовать реализации методов, специфичные для констант:

public enum MyEnum
    A { boolean getBooleanValue(){ return true; },
    B { boolean getBooleanValue(){ return true; },
    C { boolean getBooleanValue(){ return false; };
    abstract boolean getBooleanValue();
}

Таким образом, если вы когда-нибудь добавите новое значение перечисления, компилятор напомнит вам объявить метод getBooleanValue, и вы просто будете использовать A.getBooleanValue(); везде, где вам это нужно.

Как было указано в комментариях, есть еще один вариант:

public enum MyEnumAlt {
    A (true), 
    B (true),
    C (false);
    private final boolean isTrue;
    MyEnumAlt( boolean isTrue){ this.isTrue = isTrue; }
    boolean getBooleanValue(){ return isTrue; };
}

Это вопрос предпочтения, который будет зависеть от конкретной ситуации. Если вы просто возвращаете значение для каждого перечисления, версия конструктора вероятна, но я нахожу ее менее читаемой. Опасения по поводу этой производительности лучше безосновательны, как вы можете убедиться, протестировав ее:

public void testMain() {
        System.out.println("Testing with constructor: ");
        long start = System.currentTimeMillis();
        for(int i=0; i<1000*1000; i++){
            MyEnum tmpEnum = null;
            if(i%3==0){ tmpEnum = MyEnum.A;
            }else if(i%4==0){ tmpEnum = MyEnum.B;
            }else{ tmpEnum = MyEnum.C; }
            String tmp = Integer.toString(i)+" "+tmpEnum.getBooleanValue();
        }
        long time = System.currentTimeMillis()-start;
        System.out.println("Constructor version took "+time);

        System.out.println("Testing with Constant specific method implementation: ");
        long start2 = System.currentTimeMillis();
        for(int i=0; i<1000*1000; i++){
            MyEnumAlt tmpEnum2 = null;
            if(i%3==0){ tmpEnum2 = MyEnumAlt.A;
            }else if(i%4==0){ tmpEnum2 = MyEnumAlt.B;
            }else{ tmpEnum2 = MyEnumAlt.C; }
            String tmp2 = Integer.toString(i)+" "+tmpEnum2.getBooleanValue();
        }
        long time2 = System.currentTimeMillis()-start2;
        System.out.println("Constant specific method version took "+time2);
    }
1 голос
/ 23 июля 2011

просто используйте константы перечисления:

MyEnum variable;
...
switch ( variable ) {
    case A:
        return true;
    case B:
        return true;
    default:
        return false;
}

предполагая что-то вроде:

public enum MyEnum {
    A, B
}

, но остерегайтесь NullPointerException (если variable равно null)

...