Mapeie convenientemente entre enum e int / String

108

Ao trabalhar com variáveis ​​/ parâmetros que podem assumir apenas um número finito de valores, tento sempre usar Java enum, como em

public enum BonusType {
  MONTHLY, YEARLY, ONE_OFF
}

Contanto que eu permaneça dentro do meu código, funciona bem. No entanto, geralmente preciso fazer a interface com outro código que usa valores simples int(ou String) para o mesmo propósito, ou preciso ler / gravar de / para um banco de dados onde os dados são armazenados como um número ou string.

Nesse caso, gostaria de ter uma maneira conveniente de associar cada valor de enum a um inteiro, de modo que eu possa converter as duas formas (em outras palavras, preciso de um "enum reversível").

Ir de enum para int é fácil:

public enum BonusType {
  public final int id;

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

Então, posso acessar o valor int como BonusType x = MONTHLY; int id = x.id;.

No entanto, não consigo ver nenhuma maneira agradável de fazer o contrário, ou seja, indo de int para enum. Idealmente, algo como

BonusType bt = BonusType.getById(2); 

As únicas soluções que consegui encontrar são:

  • Coloque um método de pesquisa no enum, que usa BonusType.values()para preencher um mapa "int -> enum", então armazena isso em cache e o usa para pesquisas. Funcionaria, mas eu teria que copiar esse método de forma idêntica em cada enum que eu usar :-(.
  • Coloque o método de pesquisa em uma classe de utilitário estático. Então, eu precisaria apenas de um método de "pesquisa", mas teria que mexer com a reflexão para fazê-lo funcionar para um enum arbitrário.

Ambos os métodos parecem terrivelmente estranhos para um problema tão simples (?).

Alguma outra ideia / percepção?

Sleske
fonte
1
Eu <3 enums java, mas os odeio exatamente por esse motivo! Sempre parece que eles são perfeitos, exceto por uma falha muito feia ...
Chris Thompson
8
para enum-> int você pode simplesmente usarordinal()
davin
1
Seus valores de id .ordinal()são decididos por você (ou seja, você não poderia simplesmente usar ) ou são decididos por forças externas?
Paŭlo Ebermann
2
@davin: Sim, e seu código será quebrado no momento em que alguém reorganizar a declaração de enum ou excluir um valor no meio. Receio que não seja uma solução robusta: - /.
sleske
1
@davin usando "ordinal ()" deve ser evitado sempre que possível, está na especificação da linguagem
DPM

Respostas:

37

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

A solução começa semelhante à sua, com um valor int como parte da definição de enum. Ele então cria um utilitário de pesquisa baseado em genéricos:

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

Esta solução é boa e não requer 'mexer na reflexão' porque é baseada no fato de que todos os tipos de enum herdam implicitamente a interface Enum.

Jeff
fonte
Isso não usa o ordinal? Sleske usa um id apenas porque o ordinal muda quando os valores enum são reordenados.
extraneon
Não, não usa ordinal. Ele depende de um valor int definido explicitamente. Esse valor int é usado como a chave do mapa (retornada por v.convert ()).
Jeff
2
Gosto muito desta solução; parece que é o mais geral que você pode obter.
sleske
+1. Minha única observação é que eu usaria em Numbervez de Byte, porque meu valor de apoio pode ser maior em tamanho.
Ivaylo Slavov
3
Realmente leia a pergunta. Se você estiver lidando com um banco de dados legado ou sistema externo que possui números inteiros definidos que você não deseja propagar por meio de seu próprio código, este é exatamente um desses casos. O ordinal é uma forma extremamente frágil de persistir o valor de um enum e, além disso, é inútil no caso específico mencionado na pergunta.
Jeff
327

enum → int

yourEnum.ordinal()

int → enum

EnumType.values()[someInt]

String → enum

EnumType.valueOf(yourString)

enum → String

yourEnum.name()

Uma observação lateral:
como você assinalou corretamente, o ordinal()pode ser "instável" de versão para versão. Esta é a razão exata pela qual sempre armazeno constantes como strings em meus bancos de dados. (Na verdade, ao usar o MySql, eu os armazeno como enums do MySql !)

aioobe
fonte
2
+1 Esta é a resposta correta óbvia. Observe, porém, que há um método de argumento único para valueOf que leva apenas uma String e existe enquanto você estiver usando o tipo enum concreto (por exemplo BonusType.valueOf("MONTHLY"))
Tim Bender
18
Usar ordinal()me parece uma solução problemática, porque vai quebrar quando a lista de valores enum for reorganizada ou um valor for excluído. Além disso, isso só é prático se os valores int forem 0 ... n (o que muitas vezes descobri não ser o caso).
sleske
4
@sleske, se você começar a deletar constantes, terá problemas com os dados persistentes existentes. (
Atualizei
3
O uso da values()matriz só funcionará se todos os seus valores forem 0 indexados para seus ids e declarados em ordem. (Eu testei isso para verificar se você declara FOO(0), BAR(2), BAZ(1);isso values[1] == BARe values[2] == BAZapesar dos ids terem passado.)
corsiKa
2
@glowcoder, bem, é claro, o argumento inteiro é meramente um campo no objeto enum. Não tem nada a ver com a constante ordinal associada ao objeto enum (poderia muito bem ter sido a double).
aioobe
29

Achei isso na web, foi muito útil e simples de implementar. Esta solução NÃO foi feita por mim

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
fonte
s / EnumSet.allOf (Status.class) /Status.values ​​()
jelinson
8

Parece que a (s) resposta (s) a esta pergunta estão desatualizadas com o lançamento do Java 8.

  1. Não use ordinal porque ordinal é instável se persistir fora da JVM, como um banco de dados.
  2. É relativamente fácil criar um mapa estático com os valores-chave.

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);
  }
}
John Meyer
fonte
O segundo parâmetro não deveria Collectors.toMap()ser em Functions.identity()vez de null?
Adam Michalik
sim, eu adotei isso de uma classe auxiliar que uso com goiaba que converte nulo em identidade.
John Meyer
Esse é um ótimo uso dos novos recursos do Java 8. No entanto, isso ainda significa que o código teria que ser repetido em cada enum - e minha pergunta era sobre como evitar esse boilerplate (estruturalmente) repetido.
sleske
5

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

Para me poupar escrevendo um monte de código clichê ou duplicando código para cada Enum, usei o Apache Commons Lang ValuedEnum.

Definição :

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

Uso:

int -> ValuedEnum:

NRPEPacketType packetType = 
 (NRPEPacketType) EnumUtils.getEnum(NRPEPacketType.class, 1);
Alastair McCormack
fonte
Boa ideia, não sabia que isso existia. Obrigado por compartilhar!
Keith P
3

Você poderia talvez usar algo como

interface EnumWithId {
    public int getId();

}


enum Foo implements EnumWithId {

   ...
}

Isso reduziria a necessidade de reflexão em sua classe de utilitários.

extrânon
fonte
Você pode dar um exemplo de como usar este snippet?
IgorGanapolsky
3

Neste código, para busca permanente e intensa, tenho memória ou processo para uso, e seleciono memória, com array conversor como índice. Espero que seja útil

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]];
}
}
Sebastian Sgo Acevedo
fonte
2

Use uma interface para mostrar quem é o chefe.

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

E o resultado:

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
fonte
1
Assim como a resposta de Turd Ferguson, essa é a solução pouco elegante que eu gostaria de evitar / melhorar ...
sleske
Eu geralmente crio os mapeamentos reversos em um bloco {} estático para não ter que repetir os valores () toda vez que peço um valor por id. Eu também costumo chamar o método valueOf (int) para fazê-lo parecer um pouco como o método valueOf (String) já existente para Strings (também parte da questão do OP). Algo parecido com o item 33 em Effective Java: tinyurl.com/4ffvc38
Fredrik
@Sleske Atualizado com uma solução mais refinada. @Fredrik interessante, embora eu duvide que a iteração seja um problema significativo.
corsiKa
@glowcoder Bem, não ter que iterar mais de uma vez significa que não importa se você faz isso mil vezes por segundo, onde pode ser um problema muito significativo ou apenas chamá-lo duas vezes.
Fredrik de
@Fredrik Admito que há momentos em que pode ser necessário otimizar. Também estou dizendo que, até que seja um problema de desempenho identificado, não otimize para isso.
corsiKa
2

Ambos os .ordinal()e values()[i]são instáveis, pois são dependentes da ordem dos enums. Portanto, se você alterar a ordem dos enums ou adicionar / deletar alguns, o programa será interrompido.

Aqui está um método simples, mas eficaz, para mapear entre enum e 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");
    }
}

Aplicá-lo para strings não deve ser difícil.

hrzafer
fonte
Sim, eu sei que posso fazer isso - ou melhor ainda, usar um mapa para a pesquisa inversa, em vez de iterar por todos os valores. Mencionei isso em minha pergunta e também mencionei que estou procurando uma solução melhor, para evitar ter código clichê em cada enum.
sleske
2

Um exemplo de uso muito claro de Enum reverso

Etapa 1 Definir um interfaceEnumConverter

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

Passo 2

Crie um nome de classe 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);
    }
}

etapa 3

Vá até sua Enumclasse e, implementé EnumConverter<ContentType>claro, substitua os métodos de interface. Você também precisa inicializar um ReverseEnumMap estático.

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

Passo 4

Agora crie um Communicationarquivo de classe e chame seu novo método para converter um Enumpara Stringe Stringpara Enum. Acabei de colocar o método principal para fins de explicação.

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

Clique para obter uma explicação completa

AZ_
fonte
1
Isso eu realmente gosto - há algum tempo venho procurando uma solução sólida para esse problema. A única alteração que fiz foi fazer ContentType convert(String pKey)estática, o que tira a necessidade da Communicationaula e ficou mais do meu agrado. +1
Chris Mantle
1

Não tenho certeza se é o mesmo em Java, mas os tipos de enum em C também são mapeados automaticamente para inteiros, portanto, você pode usar o tipo ou o inteiro para acessá-lo. Você já tentou acessá-lo simplesmente com um número inteiro?

Detra83
fonte
2
Enums em Java não se comportam dessa maneira. Eles são um tipo explícito.
Chris Thompson
Cada objeto enum teria um número interno (ou seja, a posição em que foi declarado) e pode ser acessado pelo .ordinal()método. (Da outra maneira, use BonusType.values()[i].) Mas no exemplo citado acima, os índices aqui e os valores externos não coincidem.
Paŭlo Ebermann
1

Ótima pergunta :-) Eu usei uma solução parecida com a do Sr. Ferguson há algum tempo. Nosso enum descompilado é assim:

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

Vendo isso é evidente porque ordinal()é instável. É o identro super(s, i);. Também estou pessimista de que você possa pensar em uma solução mais elegante do que essas que você já enumerou. Afinal, enums são classes como quaisquer classes finais.

Lachezar Balev
fonte
1

Para fins de integridade, aqui está uma abordagem genérica para recuperar valores enum por índice de qualquer tipo de enum. Minha intenção era fazer com que o método se parecesse com Enum.valueOf (Class, String) . Fyi, copiei este método daqui .

Questões relacionadas ao índice (já discutidas em detalhes aqui) ainda se aplicam.

/**
 * 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
fonte
Isso não é realmente o que estou procurando, pois isso só recupera valores de enum por seu ordinal, não por um id de inteiro atribuído (veja minha pergunta). Além disso, se eu quiser, posso apenas usar MyEnumType.values()- sem necessidade de um método auxiliar estático.
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);
            }
        }
Ran Adler
fonte
0

Eu precisava de algo diferente porque queria usar uma abordagem genérica. Estou lendo o enum de e para matrizes de bytes. É aqui que eu penso:

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

Exemplo de enum:

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;
    }
}
Patrick Koorevaar
fonte
0

Só porque a resposta aceita não é independente:

Código de suporte:

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

Exemplo de uso:

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
fonte
0

dado:

public enum BonusType {MONTHLY (0), YEARLY (1), ONE_OFF (2)}

BonusType bonus = YEARLY;

System.out.println (bonus.Ordinal () + ":" + bônus)

Produto: 1: ANO

David Urry
fonte
0

Se você tem um carro de classe

public class Car {
    private Color externalColor;
}

E a propriedade Color é uma classe

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

E você deseja converter Color para um Enum

public class CarDTO {
    private ColorEnum externalColor;
}

Basta adicionar um método na classe Color para converter Color em ColorEnum

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

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

e dentro de ColorEnum implementa o método getById ()

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

Agora você pode usar um 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);
danilo
fonte