Skip to Content
Author's profile photo Fabian Lupa

Enumerations in ABAP

To represent a fixed value set in ABAP you can use several different technologies. The newest one is enumerations which are provided at language level and can be used as of AS ABAP 7.51. With this blog post I want to show which possibilities there are for ABAP developers to define enumerations and use them in signature elements in the safest way possible.


Goals of enumerations

Enumerations enable you to define a fixed amount of named values for a specific context within the type system of a programming language. These behave like constants but have the additional feature of being limited to the defined values (and not to the type of the constant). This is especially useful when defining formal parameters using enumerations, because then the user of your API is less likely to use your method wrong (because the type system does not allow it, it would be a compile time error).

 

Example of API miss-usage

Think of the following example:

CLASS lcl_light_switcher DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS:
      "! Switch the lights on / off
      "! @parameter iv_on | Should they be on?
      switch IMPORTING iv_on TYPE abap_bool.
ENDCLASS.

For most of us it is clear that iv_on should either be ' ' or 'X' because the type of the parameter is abap_bool. So typical usage would be something like this (preferred using the abap_* constants):

lcl_light_switcher=>switch( abap_true ).
lcl_light_switcher=>switch( abap_false ).
lcl_light_switcher=>switch( ' ' ).
lcl_light_switcher=>switch( space ).
lcl_light_switcher=>switch( 'X' ).

However, the type system does not force you to use ' ' or 'X' because abap_bool is just a type definition in the global type-pool abap and its actual type is C with a length of 1.

This means the following does not lead to a compiler error:

lcl_light_switcher=>switch( 'A' ).

It will however probably lead to an error in the implementation of the method because the programmer who implemented it does not expect someone to use ‘A’ as the actual parameter because by convention only ' ' and 'X' should be used. For abap_bool this is probably fine because most developers know about abap’s missing in-build boolean type and the ways around it. But what about other types that are module / application specific or that you define on your own?

 

Ways to define fixed value quantities in ABAP

Let’s say we have more states than just true and false. Our lamp is not just on or off now but can display different colors (like traffic lights).

 

Enumeration values as constants

If we want to use constants now (like abap_true and abap_false) we have to define them on our own. But where do we do that? There are three different types of development objects to use:

  • TYPE-POOLS: like SLIS, OSCON, ABAP, …
  • INTERFACES: like IF_SALV_C_*, generated interfaces in BOPF, …
  • CLASSES: either directly in the class with the method that needs the constant or in its own, like CL_GUI_CONTROL=>METRIC_PIXEL

Since type pools are obsolete, interfaces (or also classes) are the way to go nowadays. Here is an example using a constants-interface:

INTERFACE zif_light_states PUBLIC.
  TYPES:
    gty_light_state TYPE i.
  CONSTANTS:
    gc_green          TYPE gty_light_state VALUE 0,
    gc_yellow         TYPE gty_light_state VALUE 1,
    gc_red            TYPE gty_light_state VALUE 2,
    gc_red_and_yellow TYPE gty_light_state VALUE 3.
ENDINTERFACE.

CLASS zcl_light_switcher DEFINITION
  PUBLIC
  CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS:
      "! Switch the light
      "! @parameter iv_new_state | The new light color (see ZIF_LIGHT_STATES constants)
      "! @raising zcx_illegal_argument | iv_new_state is invalid
      switch_light IMPORTING iv_new_state TYPE zif_light_states=>gty_light_state
                   RAISING   zcx_illegal_argument.
  PROTECTED SECTION.
    DATA:
      mv_light TYPE zif_light_states=>gty_light_state.
  PRIVATE SECTION.
ENDCLASS.

CLASS zcl_light_switcher IMPLEMENTATION.
  METHOD switch_light.
    TYPES: lty_i_range TYPE RANGE OF zif_light_states=>gty_light_state.

    " Validate iv_new_state
    IF iv_new_state NOT IN VALUE lty_i_range(
          ( sign = 'I' option = 'EQ' low = zif_light_states=>gc_green )
          ( sign = 'I' option = 'EQ' low = zif_light_states=>gc_yellow )
          ( sign = 'I' option = 'EQ' low = zif_light_states=>gc_red )
          ( sign = 'I' option = 'EQ' low = zif_light_states=>gc_red_and_yellow )
        ).
      RAISE EXCEPTION TYPE zcx_illegal_argument
        EXPORTING
          is_message  = zcx_illegal_argument=>gc_with_name_and_value
          iv_par_name = 'IV_NEW_STATE'
          iv_value    = iv_new_state ##NO_TEXT.
    ENDIF.

    mv_light = iv_new_state.
  ENDMETHOD.
ENDCLASS.

As you can see the user of the API is pointed to the constants interface in the method documentation and by the type of the importing parameter. The type declaration of gty_light_state in the interface would normally be unnecessary (because the inbuilt type I of course already exists) but it helps to reference the place where the constants are stored. You could of course also create a DDIC data element and refer to the interface in its description. But if it is not documented somewhere, the API user is forced to look into the implementation of the method to find out what actual parameters to use (which is troublesome and not always possible, think of defining your method in an interface without implementation).

So now the possible values are known, but still, as the developer implementing the method (and using design by contract principle) you should validate the actual parameters of the caller. Which means checking if it is one of the constants. In the example above this is done using a range table. You can see another weak point here. If one decides to add another state to our traffic lights (like blinking yellow) the validation logic would have to be updated even if the rest of the method could handle an additional state just fine.

One advantage or disadvantage of using interfaces for constant definitions is that you can implement the interface in the class that uses them. This enables you to access the constants directly (as long as you defined aliases). On the other hand you will also find everything the interface defines in the debugger if you inspect an object instance in the members view. I am personally not a fan of doing that.

 

DDIC Domain values

Another way is using domains in the data dictionary. These seem particularly well suited for this task at first.

You can easily validate if a variable’s value is defined in a domain using standard function modules or RTTI. Here is the same light switcher class using a domain (/ a data element that refers to a domain):

CLASS zcl_light_switcher DEFINITION
  PUBLIC
  CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS:
      "! Switch the light
      "! @parameter iv_new_state | The new light color (see ZSCN_D_LIGHTSTATE domain)
      "! @raising zcx_illegal_argument | iv_new_state is invalid
      switch_light IMPORTING iv_new_state TYPE zscn_l_lightstate
                   RAISING   zcx_illegal_argument.
  PROTECTED SECTION.
    DATA:
      mv_light TYPE zscn_l_lightstate.
  PRIVATE SECTION.
ENDCLASS.

CLASS zcl_light_switcher IMPLEMENTATION.
  METHOD switch_light.
    " Validate iv_new_state
    CALL FUNCTION 'CHECK_DOMAIN_VALUES'
      EXPORTING
        domname = 'ZSCN_D_LIGHTSTATE'
        value   = iv_new_state.

    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_illegal_argument
        EXPORTING
          is_message  = zcx_illegal_argument=>gc_not_in_domain
          iv_par_name = 'IV_NEW_STATE'
          iv_value    = iv_new_state
          iv_domname  = 'ZSCN_D_LIGHTSTATE' ##NO_TEXT.
    ENDIF.

    mv_light = iv_new_state.
  ENDMETHOD.
ENDCLASS.

However, one thing to keep in mind is that there is no way to refer to each of the values in a static way (as they have to be fetched from the database first). That sometimes leads to a situation where you have a domain and constants you have to keep in sync. For example in a CASE statement. An advantage is though, that you can use traditional dynpro value helps / you get them “for free” with localization support (and also domain appends).

 

Using new 7.51 enumerations

Since AS ABAP 7.51 there is a construct available at language level for exactly the use case described here. The implementation chosen by SAP (to my surprise) is “value type based” and not object oriented as in many other languages (like Java or C#). In short: by defining an enumeration you define a type (value type to be exact) and associated constants for it, that implicitely get values assigned if you don’t do it yourself. Additionally any assignment to data objects that are typed as an enumeration type will get compile time checks if the associated constants are used or not. If not it results in a syntax error.

I unfortunately do not have access to a 7.51 system (AS ABAP 7.51 developer edition where are you?) but looking at the documentation it should work like this:

CLASS zcl_light_switcher DEFINITION
  PUBLIC
  CREATE PUBLIC.

  PUBLIC SECTION.
    TYPES:
      BEGIN OF ENUM gte_light_state,
        green,
        yellow,
        red,
        red_and_yellow,
      END OF ENUM gte_light_state.
    METHODS:
      switch_light IMPORTING ie_new_state TYPE gte_light_state.
(...)
ENDCLASS.

In my opinion these should replace the interface constants in all use cases, as long as you don’t have to worry about backwards compatibility.

I think it makes sense in ABAP to not have enumerations implemented as classes because of the lack of inner classes (local classes are not statically accessable outside of the class). So you would have to create a global class for any small enumeration, which, if you do it consistently, quickly assumes alarming proportions like the SALV  library has with constant interfaces.

However in other cases you might want to bundle some functionality with your enumeration instead of just providing the fixed values. Let’s see how you can do that.

 

Class based enumerations

In other programming languages with OO support (like Java or C#) there are so called enumeration classes. Unlike the new ABAP language construct which is also called enumeration, enumeration classes are “syntax sugar” that generate classes and instances of them for each fixed value. In Java for example you can write something like this for our traffic lights:

package com.flaiker.scnenums;

public enum LightStates {
    GREEN, YELLOW, RED, RED_AND_YELLOW;
}

What happens behind the scenes is that a normal Java class is generated with public static final class attributes for each of the fixed values. These are reference variables to their own class LightStates which are initialized in the static class constructor. Since these are just normal Java classes under the hood you can add additional methods, attributes etc. (please don’t quote me on how exactly the compiler does it)

package com.flaiker.scnenums;

public enum LightStates {
    GREEN("Green"),
    YELLOW("Yellow"),
    RED("Red"),
    RED_AND_YELLOW("Red and yellow");

    private String name;

    private LightStates(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

In ABAP we unfortunately do not have enumeration classes. We can however implement them “manually” effectively skipping the step the Java compiler does automatically.

 

Minimal OO enum implementation

CLASS zcl_light_state DEFINITION
  PUBLIC
  FINAL
  CREATE PRIVATE.

  PUBLIC SECTION.
    CLASS-METHODS:
      class_constructor.
    CLASS-DATA:
      go_green          TYPE REF TO zcl_light_state READ-ONLY,
      go_yellow         TYPE REF TO zcl_light_state READ-ONLY,
      go_red            TYPE REF TO zcl_light_state READ-ONLY,
      go_red_and_yellow TYPE REF TO zcl_light_state READ-ONLY.
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.

CLASS zcl_light_state IMPLEMENTATION.
  METHOD class_constructor.
    CREATE OBJECT: go_green, go_yellow, go_red, go_red_and_yellow.
  ENDMETHOD.
ENDCLASS.

As you can see just like in the Java enumeration we have static reference variables for each of the enumeration values. These cannot be changed from the outside (because they are READ-ONLY and effectively final). They are loaded on first use of the class using the class constructor and refer to instances of the class itself. It is not possible to create any other instances of the class because the constructor is private (CREATE PRIVATE) and the class is also final (so no publicly creatable subclasses). This means there can only ever be these 4 instances of the class available (*). You can now use the enumeration class similarly to the constants interface but except for comparing constant values now memory addresses will be compared.

CLASS zcl_light_switcher DEFINITION
  PUBLIC
  CREATE PUBLIC.

  PUBLIC SECTION.
    METHODS:
      "! Switch the light
      "! @parameter io_new_state | The new light color
      "! @raising zcx_illegal_argument | io_new_state cannot be null
      switch_light IMPORTING io_new_state TYPE REF TO zcl_light_state
                   RAISING   zcx_illegal_argument,
      "! @parameter ro_state | Current light state
      get_current_state RETURNING VALUE(ro_state) TYPE REF TO zcl_light_state.
  PROTECTED SECTION.
    DATA:
      mo_light TYPE REF TO zcl_light_state.
  PRIVATE SECTION.
ENDCLASS.

CLASS zcl_light_switcher IMPLEMENTATION.
  METHOD switch_light.
    " Validate io_new_state
    IF io_new_state IS NOT BOUND.
      RAISE EXCEPTION TYPE zcx_illegal_argument
        EXPORTING
          is_message  = zcx_illegal_argument=>gc_nullpointer
          iv_par_name = 'IO_NEW_STATE' ##NO_TEXT.
    ENDIF.

    mo_light = io_new_state.
  ENDMETHOD.

  METHOD get_current_state.
    ro_state = mo_light.
  ENDMETHOD.
ENDCLASS.

For the caller of the switch_light method it is clear that io_new_state must be an instance of zcl_light_state because of the type of the parameter. He can therefore (knowing of class based enumerations) look for the available values in the class-data of the class and use one of the values as the actual parameter, like this:

lo_light_switcher->switch_light( zcl_light_states=>go_green ).

Because the parameter is now an object reference to an instance of a class where there can only ever be the instances created which are defined in itself, the only illegal parameter value is null, which you should probably check for.
Compared to a constants interface or a domain or even 7.51 enumerations you might say this is much more effort. And it is. However, the advantages are that you let the type system check values of parameters at compile time (except null) and you have a full on ABAP objects class, in which you can bundle related functionality to the enumeration, like serialization, additional attributes etc. Here is a more advanced example.

 

Advanced OO enum example

"! Enumeration to represent states of traffic lights
CLASS zcl_light_state DEFINITION
  PUBLIC
  FINAL
  CREATE PRIVATE.

  PUBLIC SECTION.
    CLASS-METHODS:
      class_constructor,
      "! Get enum instance from name
      "! @parameter iv_name | Name of the enum value
      "! @parameter ro_light_state | Found enum value
      "! @raising zcx_illegal_argument | iv_name is not the name of any enum value
      from_name IMPORTING iv_name               TYPE string
                RETURNING VALUE(ro_light_state) TYPE REF TO zcl_light_state
                RAISING   zcx_illegal_argument.
    METHODS:
      "! Get the next state
      "! @parameter ro_next | Next state
      get_next RETURNING VALUE(ro_next) TYPE REF TO zcl_light_state.
    CLASS-DATA:
      "! Traffic can and should flow
      go_green          TYPE REF TO zcl_light_state READ-ONLY,
      "! Traffic can flow but soon cannot
      go_yellow         TYPE REF TO zcl_light_state READ-ONLY,
      "! Traffic must not flow
      go_red            TYPE REF TO zcl_light_state READ-ONLY,
      "! Traffic must not flow but can soon
      go_red_and_yellow TYPE REF TO zcl_light_state READ-ONLY.
    DATA:
      mv_name TYPE string READ-ONLY.
  PROTECTED SECTION.
  PRIVATE SECTION.
    METHODS:
      "! @parameter iv_name | Name of the light state, must be unique!
      constructor IMPORTING iv_name TYPE csequence.
    CLASS-DATA:
      gt_registry TYPE STANDARD TABLE OF REF TO zcl_light_state.
ENDCLASS.

CLASS zcl_light_state IMPLEMENTATION.
  METHOD class_constructor.
    DEFINE init.
      &1 = NEW #( &2 ).
      INSERT &1 INTO TABLE gt_registry.
    END-OF-DEFINITION.

    init: go_green          `Green`,
          go_yellow         `Yellow`,
          go_red            `Red`,
          go_red_and_yellow `Red + Yellow`.
  ENDMETHOD.

  METHOD from_name.
    TRY.
        ro_light_state = gt_registry[ table_line->mv_name = iv_name ].
      CATCH cx_sy_itab_line_not_found INTO DATA(lx_ex).
        RAISE EXCEPTION TYPE zcx_illegal_argument
          EXPORTING
            is_message  = zcx_illegal_argument=>gc_enum_not_registered
            iv_par_name = 'IV_NAME'
            iv_value    = iv_name
            ix_previous = lx_ex ##NO_TEXT.
    ENDTRY.
  ENDMETHOD.

  METHOD constructor.
    mv_name = iv_name.
  ENDMETHOD.

  METHOD get_next.
    ro_next = SWITCH #( me WHEN go_green          THEN go_yellow
                           WHEN go_yellow         THEN go_red
                           WHEN go_red            THEN go_red_and_yellow
                           WHEN go_red_and_yellow THEN go_green ).
    " If this fails a new enum value has been added and is not yet supported
    " by this method.
    ASSERT ro_next IS BOUND.
  ENDMETHOD.
ENDCLASS.
lo_light_switcher->switch( lo_light_switcher->get_current_state( )->get_next( ) ).

In standard SAP these classes are for example used in the ABAP workbench. If you look in the related development packages you can find classes prefixed with CE (E for enumeration I assume) that are built and used in exactly this way. In fact the workbench is a very good example for enumeration classes because they are ideally used in technical developments because persisting them to the database requires extra steps (they need to be serialized to a constant value again) and for classic user interfaces there will be no generated value help dialogs.

If you look further in the repository infosystem with CL*ENUM* you can find lots of other examples. There are even abstract base classes for enumerations you might want to take a look at (for example CL_BCFG_ENUM_BASE).

 

Comparison

Technology Dynpro support Static access to values Values can be added without needing to adjust validation logic DB persistence Enhancable using additional methods / attributes
Constants (in Type-Pools, Interfaces, Classes) No Yes No Yes (using constant) No
Domains Yes No Yes Yes (using domain key) No
Enumerations (7.51) No Yes Yes (check for INITIAL needed and maybe more? *2) Yes (using auto assigned constant) No
OO enumeration classes No Yes Yes (only nullpointer validation needed) Serialization logic must be implemented Yes (full OO class)

 

 

Combining the two different enumerations

So of course I thought “How can I have the advantages of both technologies, language level value type based enumerations with compile time checks and no nullpointers and also enumeration classes with utility methods and additional attributes?”.

So here’s a try at that, though I am not too happy with the following implementation. I don’t see a way to connect the enumeration constant with the enumeration class instance (“companion object”) without using a static accessor method.

CLASS zcl_light_state DEFINITION
  PUBLIC
  FINAL
  CREATE PRIVATE.

  PUBLIC SECTION.
    TYPES:
      BEGIN OF ENUM gte_light_state,
        green,
        yellow,
        red,
        red_and_yellow,
      END OF ENUM gte_light_state.
    CLASS-METHODS:
      get IMPORTING ie_enum               TYPE gte_light_state
          RETURNING VALUE(ro_light_state) TYPE REF TO zcl_light_state.
    METHODS:
      get_next RETURNING VALUE(ro_next) TYPE REF TO zcl_light_state.
    DATA:
      mv_name TYPE string READ-ONLY,
      me_enum TYPE gte_light_state READ-ONLY.
  PROTECTED SECTION.
  PRIVATE SECTION.
    TYPES:
      BEGIN OF gty_registry,
        enum     TYPE gte_light_state,
        instance TYPE REF TO zcl_light_state,
      END OF gty_registry.
    METHODS:
      constructor IMPORTING iv_name TYPE csequence
                            ie_enum TYPE gte_light_state.
    CLASS-DATA:
      gt_registry TYPE SORTED TABLE OF gty_registry WITH UNIQUE KEY enum.
ENDCLASS.

CLASS zcl_light_state IMPLEMENTATION.
  METHOD class_constructor.
    DEFINE init.
      NEW #( iv_name = &1 ie_enum = &2 ).
    END-OF-DEFINITION.

    init: `Green`        gte_light_state-green,
          `Yellow`       gte_light_state-yellow,
          `Red`          gte_light_state-red,
          `Red + Yellow` gte_light_state-red_and_yellow.
  ENDMETHOD.

  METHOD get.
    TRY.
        ro_light_state = gt_registry[ enum = ie_enum ].
      CATCH cx_sy_itab_line_not_found INTO DATA(lx_ex) ##NEEDED.
        ASSERT 1 = 2.
    ENDTRY.
  ENDMETHOD.

  METHOD constructor.
    mv_name = iv_name.
    me_enum = ie_enum.
    INSERT VALUE #( instance = me enum = ie_enum ) INTO TABLE gt_registry.
    ASSERT sy-subrc = 0.
  ENDMETHOD.

  METHOD get_next.
    ro_next = SWITCH #(
      me->me_enum
      WHEN gte_light_state-green          THEN get( gte_light_state-yellow )
      WHEN gte_light_state-yellow         THEN get( gte_light_state-red )
      WHEN gte_light_state-red            THEN get( gte_light_state-red_and_yellow )
      WHEN gte_light_state-red_and_yellow THEN get( gte_light_state-green )
    ).
    ASSERT ro_next IS BOUND.
  ENDMETHOD.
ENDCLASS.

 


* that is without cheating with the usual suspects (SYSTEM-CALL objmgr CLONE, CALL TRANSFORMATION ID, CL_ABAP_TESTDOUBLE, …)
*2 In the documentation‘s last chapter under notes you can find information on illegal values being passed to enumeration data objects. According to it other values than the enumeration values and the initial value of the base type can be assigned (allowed by the compiler) to enumeration data objects using dynpros and should therefore never be used with classic dynpros (disallowed by programming guideline).

Assigned Tags

      4 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo André Schaarschmidt
      André Schaarschmidt

      Hello Fabian.

      Thanks a lot for summing up the various enum techniques.
      An excellent overview with a good introduction.

      I also like that you tried to bring both worlds (7.51 enum feature and OO-enum) together.
      You made me also aware, that I should check if my OO enums are bound.

      I gonna forward your blog to my colleagues.

      Cheers,
      André

      Author's profile photo Michelle Crapo
      Michelle Crapo

      Nice.

      Author's profile photo Solen Dogan
      Solen Dogan

      Great Blog Fabian

      It makes good sense and you show the diffrences.

      The code reminded me to log4j Logger Enum

      have a look

      /*
       * Licensed to the Apache Software Foundation (ASF) under one or more
       * contributor license agreements. See the NOTICE file distributed with
       * this work for additional information regarding copyright ownership.
       * The ASF licenses this file to You under the Apache license, Version 2.0
       * (the "License"); you may not use this file except in compliance with
       * the License. You may obtain a copy of the License at
       *
       *      http://www.apache.org/licenses/LICENSE-2.0
       *
       * Unless required by applicable law or agreed to in writing, software
       * distributed under the License is distributed on an "AS IS" BASIS,
       * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       * See the license for the specific language governing permissions and
       * limitations under the license.
       */
      package org.apache.logging.log4j;
      
      import java.io.Serializable;
      import java.util.Collection;
      import java.util.Locale;
      import java.util.Objects;
      import java.util.concurrent.ConcurrentHashMap;
      import java.util.concurrent.ConcurrentMap;
      
      import org.apache.logging.log4j.spi.StandardLevel;
      import org.apache.logging.log4j.util.Strings;
      
      /**
       * Levels used for identifying the severity of an event. Levels are organized from most specific to least:
       * <ul>
       * <li>{@link #OFF} (most specific, no logging)</li>
       * <li>{@link #FATAL} (most specific, little data)</li>
       * <li>{@link #ERROR}</li>
       * <li>{@link #WARN}</li>
       * <li>{@link #INFO}</li>
       * <li>{@link #DEBUG}</li>
       * <li>{@link #TRACE} (least specific, a lot of data)</li>
       * <li>{@link #ALL} (least specific, all data)</li>
       * </ul>
       *
       * Typically, configuring a level in a filter or on a logger will cause logging events of that level and those that are
       * more specific to pass through the filter. A special level, {@link #ALL}, is guaranteed to capture all levels when
       * used in logging configurations.
       */
      public final class Level implements Comparable<Level>, Serializable {
      
          /**
           * No events will be logged.
           */
          public static final Level OFF;
      
          /**
           * A severe error that will prevent the application from continuing.
           */
          public static final Level FATAL;
      
          /**
           * An error in the application, possibly recoverable.
           */
          public static final Level ERROR;
      
          /**
           * An event that might possible lead to an error.
           */
          public static final Level WARN;
      
          /**
           * An event for informational purposes.
           */
          public static final Level INFO;
      
          /**
           * A general debugging event.
           */
          public static final Level DEBUG;
      
          /**
           * A fine-grained debug message, typically capturing the flow through the application.
           */
          public static final Level TRACE;
      
          /**
           * All events should be logged.
           */
          public static final Level ALL;
      
          /**
           * @since 2.1
           */
          public static final String CATEGORY = "Level";
      
          private static final ConcurrentMap<String, Level> LEVELS = new ConcurrentHashMap<>(); // SUPPRESS CHECKSTYLE
      
          private static final long serialVersionUID = 1581082L;
      
          static {
              OFF = new Level("OFF", StandardLevel.OFF.intLevel());
              FATAL = new Level("FATAL", StandardLevel.FATAL.intLevel());
              ERROR = new Level("ERROR", StandardLevel.ERROR.intLevel());
              WARN = new Level("WARN", StandardLevel.WARN.intLevel());
              INFO = new Level("INFO", StandardLevel.INFO.intLevel());
              DEBUG = new Level("DEBUG", StandardLevel.DEBUG.intLevel());
              TRACE = new Level("TRACE", StandardLevel.TRACE.intLevel());
              ALL = new Level("ALL", StandardLevel.ALL.intLevel());
          }
      
          private final String name;
          private final int intLevel;
          private final StandardLevel standardLevel;
      
          private Level(final String name, final int intLevel) {
              if (Strings.isEmpty(name)) {
                  throw new IllegalArgumentException("Illegal null or empty Level name.");
              }
              if (intLevel < 0) {
                  throw new IllegalArgumentException("Illegal Level int less than zero.");
              }
              this.name = name;
              this.intLevel = intLevel;
              this.standardLevel = StandardLevel.getStandardLevel(intLevel);
              if (LEVELS.putIfAbsent(name, this) != null) {
                  throw new IllegalStateException("Level " + name + " has already been defined.");
              }
          }
      
          /**
           * Gets the integral value of this Level.
           *
           * @return the value of this Level.
           */
          public int intLevel() {
              return this.intLevel;
          }
      
          /**
           * Gets the standard Level values as an enum.
           *
           * @return an enum of the standard Levels.
           */
          public StandardLevel getStandardLevel() {
              return standardLevel;
          }
      
          /**
           * Compares this level against the levels passed as arguments and returns true if this level is in between the given
           * levels.
           *
           * @param minLevel The minimum level to test.
           * @param maxLevel The maximum level to test.
           * @return True true if this level is in between the given levels
           * @since 2.4
           */
          public boolean isInRange(final Level minLevel, final Level maxLevel) {
              return this.intLevel >= minLevel.intLevel && this.intLevel <= maxLevel.intLevel;
          }
      
          /**
           * Compares this level against the level passed as an argument and returns true if this level is the same or is less
           * specific.
           * <p>
           * Concretely, {@link #ALL} is less specific than {@link #TRACE}, which is less specific than {@link #DEBUG}, which
           * is less specific than {@link #INFO}, which is less specific than {@link #WARN}, which is less specific than
           * {@link #ERROR}, which is less specific than {@link #FATAL}, and finally {@link #OFF}, which is the most specific
           * standard level.
           * </p>
           *
           * @param level
           *            The level to test.
           * @return True if this level Level is less specific or the same as the given Level.
           */
          public boolean isLessSpecificThan(final Level level) {
              return this.intLevel >= level.intLevel;
          }
      
          /**
           * Compares this level against the level passed as an argument and returns true if this level is the same or is more
           * specific.
           * <p>
           * Concretely, {@link #FATAL} is more specific than {@link #ERROR}, which is more specific than {@link #WARN},
           * etc., until {@link #TRACE}, and finally {@link #ALL}, which is the least specific standard level.
           * The most specific level is {@link #OFF}.
           * </p>
           *
           * @param level The level to test.
           * @return True if this level Level is more specific or the same as the given Level.
           */
          public boolean isMoreSpecificThan(final Level level) {
              return this.intLevel <= level.intLevel;
          }
      
          @Override
          @SuppressWarnings("CloneDoesntCallSuperClone")
          // CHECKSTYLE:OFF
          public Level clone() throws CloneNotSupportedException {
              throw new CloneNotSupportedException();
          }
          // CHECKSTYLE:ON
      
          @Override
          public int compareTo(final Level other) {
              return intLevel < other.intLevel ? -1 : (intLevel > other.intLevel ? 1 : 0);
          }
      
          @Override
          public boolean equals(final Object other) {
              return other instanceof Level && other == this;
          }
      
          public Class<Level> getDeclaringClass() {
              return Level.class;
          }
      
          @Override
          public int hashCode() {
              return this.name.hashCode();
          }
      
          /**
           * Gets the symbolic name of this Level. Equivalent to calling {@link #toString()}.
           *
           * @return the name of this Level.
           */
          public String name() {
              return this.name;
          }
      
          @Override
          public String toString() {
              return this.name;
          }
      
          /**
           * Retrieves an existing Level or creates on if it didn't previously exist.
           *
           * @param name The name of the level.
           * @param intValue The integer value for the Level. If the level was previously created this value is ignored.
           * @return The Level.
           * @throws java.lang.IllegalArgumentException if the name is null or intValue is less than zero.
           */
          public static Level forName(final String name, final int intValue) {
              final Level level = LEVELS.get(name);
              if (level != null) {
                  return level;
              }
              try {
                  return new Level(name, intValue);
              } catch (final IllegalStateException ex) {
                  // The level was added by something else so just return that one.
                  return LEVELS.get(name);
              }
          }
      
          /**
           * Return the Level associated with the name or null if the Level cannot be found.
           *
           * @param name The name of the Level.
           * @return The Level or null.
           */
          public static Level getLevel(final String name) {
              return LEVELS.get(name);
          }
      
          /**
           * Converts the string passed as argument to a level. If the conversion fails, then this method returns
           * {@link #DEBUG}.
           *
           * @param sArg The name of the desired Level.
           * @return The Level associated with the String.
           */
          public static Level toLevel(final String sArg) {
              return toLevel(sArg, Level.DEBUG);
          }
      
          /**
           * Converts the string passed as argument to a level. If the conversion fails, then this method returns the value of
           * <code>defaultLevel</code>.
           *
           * @param name The name of the desired Level.
           * @param defaultLevel The Level to use if the String is invalid.
           * @return The Level associated with the String.
           */
          public static Level toLevel(final String name, final Level defaultLevel) {
              if (name == null) {
                  return defaultLevel;
              }
              final Level level = LEVELS.get(toUpperCase(name));
              return level == null ? defaultLevel : level;
          }
      
          private static String toUpperCase(final String name) {
              return name.toUpperCase(Locale.ENGLISH);
          }
      
          /**
           * Return an array of all the Levels that have been registered.
           *
           * @return An array of Levels.
           */
          public static Level[] values() {
              final Collection<Level> values = Level.LEVELS.values();
              return values.toArray(new Level[values.size()]);
          }
      
          /**
           * Return the Level associated with the name.
           *
           * @param name The name of the Level to return.
           * @return The Level.
           * @throws java.lang.NullPointerException if the Level name is {@code null}.
           * @throws java.lang.IllegalArgumentException if the Level name is not registered.
           */
          public static Level valueOf(final String name) {
              Objects.requireNonNull(name, "No level name given.");
              final String levelName = toUpperCase(name);
              final Level level = LEVELS.get(levelName);
              if (level != null) {
                  return level;
              }
              throw new IllegalArgumentException("Unknown level constant [" + levelName + "].");
          }
      
          /**
           * Returns the enum constant of the specified enum type with the specified name. The name must match exactly an
           * identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
           *
           * @param enumType the {@code Class} object of the enum type from which to return a constant
           * @param name the name of the constant to return
           * @param <T> The enum type whose constant is to be returned
           * @return the enum constant of the specified enum type with the specified name
           * @throws java.lang.IllegalArgumentException if the specified enum type has no constant with the specified name, or
           *             the specified class object does not represent an enum type
           * @throws java.lang.NullPointerException if {@code enumType} or {@code name} are {@code null}
           * @see java.lang.Enum#valueOf(Class, String)
           */
          public static <T extends Enum<T>> T valueOf(final Class<T> enumType, final String name) {
              return Enum.valueOf(enumType, name);
          }
      
          // for deserialization
          protected Object readResolve() {
              return Level.valueOf(this.name);
          }
      }
      Author's profile photo Hendrik Leusmann
      Hendrik Leusmann

      Hi Fabian,

      thanks for this blogpost and giving a overview to the different options of handling "constants".

       

      For your "GET_NEXT" method in the last example you programmed a SWITCH which has to be adapted if a state is added/removed. As behind the enumeration type i is used as default there is already an order.

            BEGIN OF ENUM gte_light_state,
              green,
              yellow,
              red,
              red_and_yellow,
            END OF ENUM gte_light_state.

      In this case: green = 0, yellow = 1, ....
      Therefore also the get next could be programmed generically. Which indeed would be a more complex coding.

      Regards
      Hendrik