Удобное отображение между enum и int / String

108

При работе с переменными / параметрами, которые могут принимать только конечное количество значений, я стараюсь всегда использовать Java enum, как в

public enum BonusType {
  MONTHLY, YEARLY, ONE_OFF
}

Пока я остаюсь внутри своего кода, все работает нормально. Однако мне часто нужно взаимодействовать с другим кодом, который использует простые int(или String) значения для той же цели, или мне нужно читать / писать из / в базу данных, где данные хранятся в виде числа или строки.

В этом случае я хотел бы иметь удобный способ связать каждое значение перечисления с целым числом, чтобы я мог преобразовывать оба способа (другими словами, мне нужно «обратимое перечисление»).

Перейти от enum к int очень просто:

public enum BonusType {
  public final int id;

  BonusType(int id) {
    this.id = id;
  }
  MONTHLY(1), YEARLY(2), ONE_OFF(3);
}

Затем я могу получить доступ к значению int как BonusType x = MONTHLY; int id = x.id;.

Однако я не вижу хорошего пути для обратного, то есть перехода от int к enum. В идеале что-то вроде

BonusType bt = BonusType.getById(2); 

Единственные решения, которые я мог придумать:

  • Поместите в перечисление метод поиска, который использует BonusType.values()для заполнения карты «int -> enum», затем кэширует его и использует для поиска. Будет работать, но мне придется копировать этот метод идентично в каждое используемое мной перечисление :-(.
  • Поместите метод поиска в статический служебный класс. Тогда мне понадобится только один метод поиска, но мне придется возиться с отражением, чтобы заставить его работать для произвольного перечисления.

Оба метода кажутся ужасно неудобными для такой простой (?) Задачи.

Любые другие идеи / идеи?

Sleske
источник
1
Я <3 перечисления java, но ненавижу их именно по этой причине! Всегда кажется, что они идеальны, если не считать одного действительно уродливого недостатка ...
Крис Томпсон
8
для enum-> int вы можете просто использоватьordinal()
davin
1
Ваши значения id решаются вами (то есть, не могли бы вы просто использовать .ordinal()), или они определяются внешними силами?
Paŭlo Ebermann
2
@davin: Да, и пусть ваш код сломается, когда кто-то изменит объявление enum или удалит значение посередине. Боюсь, это не надежное решение: - /.
sleske
1
@davin, использующий "ordinal ()", по возможности следует избегать, это указано в спецификации языка
DPM

Ответы:

37

http://www.javaspecialists.co.za/archive/Issue113.html

Решение начинается так же, как ваше, со значения int как части определения перечисления. Затем он переходит к созданию универсальной поисковой утилиты:

public class ReverseEnumMap<V extends Enum<V> & EnumConverter> {
    private Map<Byte, V> map = new HashMap<Byte, V>();
    public ReverseEnumMap(Class<V> valueType) {
        for (V v : valueType.getEnumConstants()) {
            map.put(v.convert(), v);
        }
    }

    public V get(byte num) {
        return map.get(num);
    }
}

Это хорошее решение, которое не требует «возни с отражением», потому что оно основано на том факте, что все типы перечислений неявно наследуют интерфейс Enum.

Джефф
источник
Разве здесь не используется порядковый номер? Слеске использует идентификатор только потому, что порядковый номер изменяется при изменении порядка значений перечисления.
extraneon
Нет, порядковый номер не используется. Он полагается на явно определенное значение типа int. Это значение int используется в качестве ключа карты (возвращается v.convert ()).
Джефф
2
Мне очень нравится это решение; кажется, это самое общее, что вы можете получить.
sleske
+1. Замечу только, что я бы использовал Numberвместо Byte, потому что моя поддерживающая стоимость может быть больше по размеру.
Ивайло Славов
3
Правильно и верно прочтите вопрос. Если вы имеете дело с устаревшей базой данных или внешней системой, в которой определены целые числа, которые вы не хотите распространять через свой собственный код, то это как раз один из таких случаев. Порядковый номер - чрезвычайно хрупкий способ сохранить значение перечисления, и, кроме того, он бесполезен в конкретном случае, упомянутом в вопросе.
Джефф
327

enum → int

yourEnum.ordinal()

int → перечисление

EnumType.values()[someInt]

Строка → перечисление

EnumType.valueOf(yourString)

перечисление → Строка

yourEnum.name()

Примечание:
как вы правильно заметили, ordinal()от версии к версии она может быть "нестабильной". Это точная причина, по которой я всегда храню константы как строки в своих базах данных. (На самом деле, при использовании MySql я сохраняю их как перечисления MySql !)

aioobe
источник
2
+1 Это очевидный правильный ответ. Однако обратите внимание, что для valueOf существует метод с одним аргументом, который принимает только String и существует, пока вы используете конкретный тип перечисления (например BonusType.valueOf("MONTHLY"))
Тим Бендер
18
Использование ordinal()кажется мне проблемным решением, потому что оно сломается, когда список значений перечисления будет переставлен или значение будет удалено. Кроме того, это практично, только если значения int равны 0 ... n (что я часто обнаруживал, что это не так).
sleske
4
@sleske, если вы начинаете удалять константы, у вас все равно проблемы с существующими постоянными данными. (Обновил мой ответ в этом отношении.)
aioobe
3
Использование values()массива будет работать, только если все ваши значения имеют индекс 0 для своего идентификатора и объявлены по порядку. (Я проверил это, чтобы убедиться, что если вы FOO(0), BAR(2), BAZ(1);values[1] == BARvalues[2] == BAZ
заявите
2
@glowcoder, ну, конечно, целочисленный аргумент - это просто поле в enum-объекте. Это не имеет ничего общего с порядковой константой, связанной с объектом перечисления (с таким же успехом это мог быть a double).
aioobe
30

Я нашел это в сети, это было очень полезно и просто реализовать. Это решение было сделано НЕ мной

http://www.ajaxonomy.com/2007/java/making-the-most-of-java-50-enum-tricks

public enum Status {
 WAITING(0),
 READY(1),
 SKIPPED(-1),
 COMPLETED(5);

 private static final Map<Integer,Status> lookup 
      = new HashMap<Integer,Status>();

 static {
      for(Status s : EnumSet.allOf(Status.class))
           lookup.put(s.getCode(), s);
 }

 private int code;

 private Status(int code) {
      this.code = code;
 }

 public int getCode() { return code; }

 public static Status get(int code) { 
      return lookup.get(code); 
 }

}

Melqkiades
источник
s / EnumSet.allOf (Status.class) /Status.values ​​()
jelinson
8

Кажется, что ответы на этот вопрос устарели с выпуском Java 8.

  1. Не используйте порядковый номер, поскольку порядковый номер нестабилен, если он сохраняется за пределами JVM, например, в базе данных.
  2. Создать статическую карту со значениями ключей относительно легко.

public enum AccessLevel {
  PRIVATE("private", 0),
  PUBLIC("public", 1),
  DEFAULT("default", 2);

  AccessLevel(final String name, final int value) {
    this.name = name;
    this.value = value;
  }

  private final String name;
  private final int value;

  public String getName() {
    return name;
  }

  public int getValue() {
    return value;
  }

  static final Map<String, AccessLevel> names = Arrays.stream(AccessLevel.values())
      .collect(Collectors.toMap(AccessLevel::getName, Function.identity()));
  static final Map<Integer, AccessLevel> values = Arrays.stream(AccessLevel.values())
      .collect(Collectors.toMap(AccessLevel::getValue, Function.identity()));

  public static AccessLevel fromName(final String name) {
    return names.get(name);
  }

  public static AccessLevel fromValue(final int value) {
    return values.get(value);
  }
}
Джон Мейер
источник
Разве второй параметр не Collectors.toMap()должен быть Functions.identity()вместо null?
Адам Михалик
да, я позаимствовал это из вспомогательного класса, который я использую с guava, который преобразует null в идентификатор.
Джон Мейер
Это отличное использование новых функций Java 8. Однако это по-прежнему означает, что код придется повторять в каждом перечислении - и мой вопрос касался того, чтобы избежать этого (структурно) повторяющегося шаблона.
sleske
5

org.apache.commons.lang.enums.ValuedEnum;

Чтобы избавить меня от написания большого количества шаблонного кода или дублирования кода для каждого Enum, я ValuedEnumвместо этого использовал Apache Commons Lang .

Определение :

public class NRPEPacketType extends ValuedEnum {    
    public static final NRPEPacketType TYPE_QUERY = new NRPEPacketType( "TYPE_QUERY", 1);
    public static final NRPEPacketType TYPE_RESPONSE = new NRPEPacketType( "TYPE_RESPONSE", 2);

    protected NRPEPacketType(String name, int value) {
        super(name, value);
    }
}

Использование:

int -> ValuedEnum:

NRPEPacketType packetType = 
 (NRPEPacketType) EnumUtils.getEnum(NRPEPacketType.class, 1);
Аластер МакКормак
источник
Хорошая идея, я не понимал, что это существует. Спасибо, что поделился!
Keith P
3

Возможно, вы могли бы использовать что-то вроде

interface EnumWithId {
    public int getId();

}


enum Foo implements EnumWithId {

   ...
}

Это уменьшит необходимость отражения в вашем служебном классе.

extraneon
источник
Вы можете привести пример того, как использовать этот фрагмент?
Игорь Ганапольский
3

В этом коде для постоянного и интенсивного поиска есть память или процесс для использования, и я выбираю память с массивом преобразователя в качестве индекса. Надеюсь, это поможет

public enum Test{ 
VALUE_ONE(101, "Im value one"),
VALUE_TWO(215, "Im value two");
private final int number;
private final byte[] desc;

private final static int[] converter = new int[216];
static{
    Test[] st = values();
    for(int i=0;i<st.length;i++){
        cv[st[i].number]=i;
    }
}

Test(int value, byte[] description) {
    this.number = value;
    this.desc = description;
}   
public int value() {
    return this.number;
}
public byte[] description(){
    return this.desc;
}

public static String description(int value) {
    return values()[converter[rps]].desc;
}

public static Test fromValue(int value){
return values()[converter[rps]];
}
}
Себастьян Сго Асеведо
источник
2

Используйте интерфейс, чтобы показать, кто здесь главный.

public interface SleskeEnum {
    int id();

    SleskeEnum[] getValues();

}

public enum BonusType implements SleskeEnum {


  MONTHLY(1), YEARLY(2), ONE_OFF(3);

  public final int id;

  BonusType(int id) {
    this.id = id;
  }

  public SleskeEnum[] getValues() {
    return values();
  }

  public int id() { return id; }


}

public class Utils {

  public static SleskeEnum getById(SleskeEnum type, int id) {
      for(SleskeEnum t : type.getValues())
          if(t.id() == id) return t;
      throw new IllegalArgumentException("BonusType does not accept id " + id);
  }

  public static void main(String[] args) {

      BonusType shouldBeMonthly = (BonusType)getById(BonusType.MONTHLY,1);
      System.out.println(shouldBeMonthly == BonusType.MONTHLY);

      BonusType shouldBeMonthly2 = (BonusType)getById(BonusType.MONTHLY,1);
      System.out.println(shouldBeMonthly2 == BonusType.YEARLY);

      BonusType shouldBeYearly = (BonusType)getById(BonusType.MONTHLY,2);
      System.out.println(shouldBeYearly  == BonusType.YEARLY);

      BonusType shouldBeOneOff = (BonusType)getById(BonusType.MONTHLY,3);
      System.out.println(shouldBeOneOff == BonusType.ONE_OFF);

      BonusType shouldException = (BonusType)getById(BonusType.MONTHLY,4);
  }
}

И результат:

C:\Documents and Settings\user\My Documents>java Utils
true
false
true
true
Exception in thread "main" java.lang.IllegalArgumentException: BonusType does not accept id 4
        at Utils.getById(Utils.java:6)
        at Utils.main(Utils.java:23)

C:\Documents and Settings\user\My Documents>
corsiKa
источник
1
Как и в случае с ответом Турда Фергюсона, это неэлегантное решение, которое я бы хотел избежать / улучшить ...
sleske
Обычно я создаю обратные сопоставления в блоке static {}, чтобы не приходилось перебирать значения () каждый раз, когда я запрашиваю значение по идентификатору. Я также обычно вызываю метод valueOf (int), чтобы он выглядел чем-то вроде метода valueOf (String), который уже существует для Strings (также является частью вопроса OP). Примерно как элемент 33 в Effective Java: tinyurl.com/4ffvc38
Фредрик,
@Sleske Обновлен более совершенным решением. @Fredrik интересно, хотя я сомневаюсь, что итерация станет серьезной проблемой.
corsiKa
@glowcoder Что ж, отсутствие необходимости повторять более одного раза означает, что не имеет значения, делаете ли вы это тысячу раз в секунду, где это может быть очень серьезной проблемой, или просто вызываете это дважды.
Фредрик,
@Fredrik Я признаю, что бывают случаи, когда может потребоваться оптимизация. Я также говорю, что пока не будет выявлена ​​проблема с производительностью, не оптимизируйте ее.
corsiKa
2

Оба .ordinal()и values()[i]являются нестабильными, поскольку они зависят от порядка перечислений. Таким образом, если вы измените порядок перечислений или добавите / удалите некоторые, ваша программа сломается.

Вот простой, но эффективный метод сопоставления enum и int.

public enum Action {
    ROTATE_RIGHT(0), ROTATE_LEFT(1), RIGHT(2), LEFT(3), UP(4), DOWN(5);

    public final int id;
    Action(int id) {
        this.id = id;
    }

    public static Action get(int id){
        for (Action a: Action.values()) {
            if (a.id == id)
                return a;
        }
        throw new IllegalArgumentException("Invalid id");
    }
}

Применить его к струнам не составит труда.

Hrzafer
источник
Да, я понимаю, что могу это сделать - или, что еще лучше, использовать карту для обратного просмотра, а не перебирать все значения. Я упомянул об этом в своем вопросе, а также упомянул, что ищу лучшее решение, чтобы избежать использования шаблонного кода в каждом перечислении.
sleske
2

Очень чистый пример использования обратного Enum

Шаг 1 Определите interfaceEnumConverter

public interface EnumConverter <E extends Enum<E> & EnumConverter<E>> {
    public String convert();
    E convert(String pKey);
}

Шаг 2

Создайте имя класса ReverseEnumMap

import java.util.HashMap;
import java.util.Map;

public class ReverseEnumMap<V extends Enum<V> & EnumConverter<V>> {
    private Map<String, V> map = new HashMap<String, V>();

    public ReverseEnumMap(Class<V> valueType) {
        for (V v : valueType.getEnumConstants()) {
            map.put(v.convert(), v);
        }
    }

    public V get(String pKey) {
        return map.get(pKey);
    }
}

Шаг 3

Перейти к вам Enumкласс и implementэто с EnumConverter<ContentType>и методов коррекции интерфейса курса. Вам также необходимо инициализировать статический ReverseEnumMap.

public enum ContentType implements EnumConverter<ContentType> {
    VIDEO("Video"), GAME("Game"), TEST("Test"), IMAGE("Image");

    private static ReverseEnumMap<ContentType> map = new ReverseEnumMap<ContentType>(ContentType.class);

    private final String mName;

    ContentType(String pName) {
        this.mName = pName;
    }

    String value() {
        return this.mName;
    }

    @Override
    public String convert() {
        return this.mName;
    }

    @Override
    public ContentType convert(String pKey) {
        return map.get(pKey);
    }
}

Шаг 4

Теперь создайте Communicationфайл класса и вызовите его новый метод для преобразования Enumв Stringи Stringв Enum. Я только что поставил основной метод для объяснения.

public class Communication<E extends Enum<E> & EnumConverter<E>> {
    private final E enumSample;

    public Communication(E enumSample) {
        this.enumSample = enumSample;
    }

    public String resolveEnumToStringValue(E e) {
        return e.convert();
    }

    public E resolveStringEnumConstant(String pName) {
        return enumSample.convert(pName);
    }

//Should not put main method here... just for explanation purpose. 
    public static void main(String... are) {
        Communication<ContentType> comm = new Communication<ContentType>(ContentType.GAME);
        comm.resolveEnumToStringValue(ContentType.GAME); //return Game
        comm.resolveStringEnumConstant("Game"); //return GAME (Enum)
    }
}

Нажмите, чтобы увидеть полное объяснение

AZ_
источник
1
Это мне очень, очень нравится - я уже некоторое время искал надежное решение этой проблемы. Единственное изменение, которое я сделал, - это ContentType convert(String pKey)статический, который устраняет необходимость в Communicationклассе и мне больше нравится. +1
Крис Мантл
1

Я не уверен, что это то же самое в Java, но типы перечислений в C также автоматически сопоставляются с целыми числами, поэтому вы можете использовать либо тип, либо целое число для доступа к нему. Вы уже пробовали просто получить к нему доступ с помощью целого числа?

Детра83
источник
2
Перечисления в Java так не ведут себя. Это явный тип.
Крис Томпсон
Каждый объект перечисления будет иметь внутренний номер (а именно позицию, в которой он был объявлен), и к нему можно получить доступ с помощью .ordinal()метода. (Другой способ - использовать BonusType.values()[i].) Но в приведенном выше примере индексы здесь и внешние значения не совпадают.
Paŭlo Ebermann
1

Действительно отличный вопрос :-) Некоторое время назад я использовал решение, похожее на решение мистера Фергюсона. Наше декомпилированное перечисление выглядит так:

final class BonusType extends Enum
{

    private BonusType(String s, int i, int id)
    {
        super(s, i);
        this.id = id;
    }

    public static BonusType[] values()
    {
        BonusType abonustype[];
        int i;
        BonusType abonustype1[];
        System.arraycopy(abonustype = ENUM$VALUES, 0, abonustype1 = new BonusType[i = abonustype.length], 0, i);
        return abonustype1;
    }

    public static BonusType valueOf(String s)
    {
        return (BonusType)Enum.valueOf(BonusType, s);
    }

    public static final BonusType MONTHLY;
    public static final BonusType YEARLY;
    public static final BonusType ONE_OFF;
    public final int id;
    private static final BonusType ENUM$VALUES[];

    static 
    {
        MONTHLY = new BonusType("MONTHLY", 0, 1);
        YEARLY = new BonusType("YEARLY", 1, 2);
        ONE_OFF = new BonusType("ONE_OFF", 2, 3);
        ENUM$VALUES = (new BonusType[] {
            MONTHLY, YEARLY, ONE_OFF
        });
    }
}

Видя это, становится очевидным, почему ordinal()он нестабилен. Это iв super(s, i);. Я также пессимистичен, что вы можете придумать более элегантное решение, чем те, которые вы уже перечислили. Ведь перечисления - это классы, как любые конечные классы.

Лачезар Балев
источник
1

Для полноты, вот общий подход к извлечению значений перечисления по индексу из любого типа перечисления. Я намеревался сделать так, чтобы метод выглядел как Enum.valueOf (Class, String) . Fyi, я скопировал этот метод отсюда .

Вопросы, связанные с индексом (уже подробно обсуждаемые здесь), все еще актуальны.

/**
 * Returns the {@link Enum} instance for a given ordinal.
 * This method is the index based alternative
 * to {@link Enum#valueOf(Class, String)}, which
 * requires the name of an instance.
 * 
 * @param <E> the enum type
 * @param type the enum class object
 * @param ordinal the index of the enum instance
 * @throws IndexOutOfBoundsException if ordinal < 0 || ordinal >= enums.length
 * @return the enum instance with the given ordinal
 */
public static <E extends Enum<E>> E valueOf(Class<E> type, int ordinal) {
    Preconditions.checkNotNull(type, "Type");
    final E[] enums = type.getEnumConstants();
    Preconditions.checkElementIndex(ordinal, enums.length, "ordinal");
    return enums[ordinal];
}
whiskeysierra
источник
Это не совсем то, что я ищу, так как это только извлекает значения перечисления по их порядковому номеру, а не по назначенному целочисленному идентификатору (см. Мой вопрос). Кроме того, если я действительно этого хочу, я могу просто использовать MyEnumType.values()- нет необходимости в статическом вспомогательном методе.
sleske
0
Int -->String :

public enum Country {

    US("US",0),
    UK("UK",2),
    DE("DE",1);


    private static Map<Integer, String> domainToCountryMapping; 
    private String country;
    private int domain;

    private Country(String country,int domain){
        this.country=country.toUpperCase();
        this.domain=domain;
    }

    public String getCountry(){
        return country;
    }


    public static String getCountry(String domain) {
        if (domainToCountryMapping == null) {
            initMapping();
        }

        if(domainToCountryMapping.get(domain)!=null){
            return domainToCountryMapping.get(domain);
        }else{
            return "US";
        }

    }

     private static void initMapping() {
         domainToCountryMapping = new HashMap<Integer, String>();
            for (Country s : values()) {
                domainToCountryMapping.put(s.domain, s.country);
            }
        }
Ран Адлер
источник
0

Мне нужно было что-то другое, потому что я хотел использовать общий подход. Я читаю перечисление байтовых массивов и обратно. Вот где я придумал:

public interface EnumConverter {
    public Number convert();
}



public class ByteArrayConverter {
@SuppressWarnings("unchecked")
public static Enum<?> convertToEnum(byte[] values, Class<?> fieldType, NumberSystem numberSystem) throws InvalidDataException {
    if (values == null || values.length == 0) {
        final String message = "The values parameter must contain the value";
        throw new IllegalArgumentException(message);
    }

    if (!dtoFieldType.isEnum()) {
        final String message = "dtoFieldType must be an Enum.";
        throw new IllegalArgumentException(message);
    }

    if (!EnumConverter.class.isAssignableFrom(fieldType)) {
        final String message = "fieldType must implement the EnumConverter interface.";
        throw new IllegalArgumentException(message);
    }

    Enum<?> result = null;
    Integer enumValue = (Integer) convertToType(values, Integer.class, numberSystem); // Our enum's use Integer or Byte for the value field.

    for (Object enumConstant : fieldType.getEnumConstants()) {
        Number ev = ((EnumConverter) enumConstant).convert();

        if (enumValue.equals(ev)) {
            result = (Enum<?>) enumConstant;
            break;
        }
    }

    if (result == null) {
        throw new EnumConstantNotPresentException((Class<? extends Enum>) fieldType, enumValue.toString());
    }

    return result;
}

public static byte[] convertEnumToBytes(Enum<?> value, int requiredLength, NumberSystem numberSystem) throws InvalidDataException {
    if (!(value instanceof EnumConverter)) {
        final String message = "dtoFieldType must implement the EnumConverter interface.";
        throw new IllegalArgumentException(message);
    }

    Number enumValue = ((EnumConverter) value).convert();
    byte[] result = convertToBytes(enumValue, requiredLength, numberSystem);
    return result;
}

public static Object convertToType(byte[] values, Class<?> type, NumberSystem numberSystem) throws InvalidDataException {
    // some logic to convert the byte array supplied by the values param to an Object.
}

public static byte[] convertToBytes(Object value, int requiredLength, NumberSystem numberSystem) throws InvalidDataException {
    // some logic to convert the Object supplied by the'value' param to a byte array.
}
}

Пример перечисления:

public enum EnumIntegerMock implements EnumConverter {
    VALUE0(0), VALUE1(1), VALUE2(2);

    private final int value;

    private EnumIntegerMock(int value) {
        this.value = value;
    }

public Integer convert() {
    return value;
}

}

public enum EnumByteMock implements EnumConverter {
    VALUE0(0), VALUE1(1), VALUE2(2);

    private final byte value;

    private EnumByteMock(int value) {
        this.value = (byte) value;
    }

    public Byte convert() {
        return value;
    }
}
Патрик Куреваар
источник
0

Просто потому, что принятый ответ не самодостаточен:

Код поддержки:

public interface EnumWithCode<E extends Enum<E> & EnumWithCode<E>> {

    public Integer getCode();

    E fromCode(Integer code);
}


public class EnumWithCodeMap<V extends Enum<V> & EnumWithCode<V>> {

    private final HashMap<Integer, V> _map = new HashMap<Integer, V>();

    public EnumWithCodeMap(Class<V> valueType) {
        for( V v : valueType.getEnumConstants() )
            _map.put(v.getCode(), v);
    }

    public V get(Integer num) {
        return _map.get(num);
    }
}

Пример использования:

public enum State implements EnumWithCode<State> {
    NOT_STARTED(0), STARTED(1), ENDED(2);

    private static final EnumWithCodeMap<State> map = new EnumWithCodeMap<State>(
            State.class);

    private final int code;

    private State(int code) {
        this.code = code;
    }

    @Override
    public Integer getCode() {
        return code;
    }

    @Override
    public State fromCode(Integer code) {
        return map.get(code);
    }

}
leonbloy
источник
0

дано:

общедоступное перечисление BonusType {MONTHLY (0), YEARLY (1), ONE_OFF (2)}

BonusType бонус = ГОД;

System.out.println (Bonus.Ordinal () + ":" + бонус)

Выход: 1: ГОД

Дэвид Урри
источник
0

Если у вас классная машина

public class Car {
    private Color externalColor;
}

А свойство Color - это класс

@Data
public class Color {
    private Integer id;
    private String name;
}

И вы хотите преобразовать Color в Enum

public class CarDTO {
    private ColorEnum externalColor;
}

Просто добавьте метод в класс Color для преобразования Color в ColorEnum

@Data
public class Color {
    private Integer id;
    private String name;

    public ColorEnum getEnum(){
        ColorEnum.getById(id);
    }
}

а внутри ColorEnum реализует метод getById ()

public enum ColorEnum {
...
    public static ColorEnum getById(int id) {
        for(ColorEnum e : values()) {
            if(e.id==id) 
                return e;
        }
    }
}

Теперь вы можете использовать classMap

private MapperFactory factory = new DefaultMapperFactory.Builder().build();
...
factory.classMap(Car.class, CarDTO.class)
    .fieldAToB("externalColor.enum","externalColor")
    .byDefault()
    .register();
...
CarDTO dto = mapper.map(car, CarDTO.class);
Данило
источник